├── .gitignore ├── CODE_OF_CONDUCT.md ├── LICENSE ├── Package.resolved ├── Package.swift ├── README.md ├── Sources ├── PatternKit │ ├── Alternation │ │ ├── Alternation Operators.swift │ │ ├── Alternation.swift │ │ ├── Backward Alternation Match Collection.swift │ │ ├── Forward Alternation Match Collection.swift │ │ └── Homogeneous │ │ │ ├── Backward Homogeneous Alternation Match Collection.swift │ │ │ ├── Forward Homogeneous Alternation Match Collection.swift │ │ │ └── Homogeneous Alternation.swift │ ├── Any Pattern.swift │ ├── Assertion │ │ ├── Assertion Operators.swift │ │ ├── Backward Assertion Match Collection.swift │ │ ├── Backward Assertion.swift │ │ ├── Forward Assertion Match Collection.swift │ │ ├── Forward Assertion.swift │ │ ├── Negated Backward Assertion Match Collection.swift │ │ ├── Negated Backward Assertion.swift │ │ ├── Negated Forward Assertion Match Collection.swift │ │ └── Negated Forward Assertion.swift │ ├── Bin.swift │ ├── Character Set │ │ ├── Character Set Operators.swift │ │ └── Character Set.swift │ ├── Concatenation │ │ ├── Backward Concatenation Match Collection.swift │ │ ├── Concatenation Operators.swift │ │ ├── Concatenation.swift │ │ ├── Forward Concatenation Match Collection.swift │ │ └── Homogeneous │ │ │ ├── Backward Homogeneous Concatenation Match Collection.swift │ │ │ ├── Forward Homogeneous Concatenation Match Collection.swift │ │ │ └── Homogeneous Concatenation.swift │ ├── Exports.swift │ ├── Literal.swift │ ├── Range Pattern │ │ ├── Range Pattern Operators.swift │ │ └── Range Pattern.swift │ ├── Repeating │ │ ├── Backward Ring.swift │ │ ├── Eagerly Repeating Operators.swift │ │ ├── Eagerly Repeating.swift │ │ ├── Forward Ring.swift │ │ ├── Lazily Repeating Operators.swift │ │ └── Lazily Repeating.swift │ ├── Singular Match Collection.swift │ ├── TODO.swift │ └── Wildcard.swift ├── PatternKitCore │ ├── Extensions on Range.swift │ ├── Match.swift │ ├── Matching Direction.swift │ ├── Pattern.swift │ └── Token.swift └── RegexKit │ ├── Alternation │ ├── Alternation Expression Realisation.swift │ ├── Alternation Expression.swift │ ├── Homogeneous Alternation Expression Realisation.swift │ └── Homogeneous Alternation Expression.swift │ ├── Assertion │ ├── Backward Assertion Expression.swift │ ├── Forward Assertion Expression.swift │ ├── Negated Backward Assertion Expression.swift │ └── Negated Forward Assertion Expression.swift │ ├── Character Set │ └── Character Set Expression.swift │ ├── Comment Expression.swift │ ├── Concatenated │ ├── Concatenated Expression Realisation.swift │ ├── Concatenated Expression.swift │ ├── Homogeneous Concatenated Expression Realisation.swift │ └── Homogenous Concatenated Expression.swift │ ├── Core │ ├── Binding Class.swift │ ├── Expression │ │ ├── Bounded Unary Expression.swift │ │ ├── Expression.swift │ │ ├── Postfix Operator Expression.swift │ │ └── Unary Expression.swift │ ├── Language.swift │ ├── Realisation.swift │ └── Symbol │ │ ├── Noncapturing Group Boundary Symbol.swift │ │ └── Symbol.swift │ ├── Empty │ └── Empty Expression.swift │ ├── Literal │ ├── Literal Expression Realisation.swift │ └── Literal Expression.swift │ └── Repeating │ ├── Eagerly Repeating Expression.swift │ ├── Lazily Repeating Expression.swift │ └── Multiplication Range.swift └── Tests ├── LinuxMain.swift ├── PatternKitTests ├── Alternations Test Case.swift ├── Assertions │ ├── Backward Assertions Test Case.swift │ ├── Forward Assertions Test Case.swift │ ├── Negated Backward Assertions Test Case.swift │ └── Negated Forward Assertions Test Case.swift ├── Character Sets Test Case.swift ├── Concatenations Test Case.swift ├── Literals Test Case.swift ├── Practical Test Case.swift ├── Range Patterns Test Case.swift ├── Repeating │ ├── Eagerly Repeating Test Case.swift │ └── Lazily Repeating Test Case.swift ├── Token Test Case.swift └── Wildcards Test Case.swift └── RegexKitTests ├── Alternation ├── Alternation Expression Test Case.swift └── Homogeneous Alternation Expression Test Case.swift ├── Assertion ├── Backward Assertion Expression Test Case.swift ├── Forward Assertion Expression Test Case.swift ├── Negated Backward Assertion Expression Test Case.swift └── Negated Forward Assertion Expression Test Case.swift ├── Character Set Expression Test Case.swift ├── Comment Expression Test Case.swift └── Concatenated ├── Concatenated Expression Test Case.swift └── Homogeneous Concatenated Expression Test Case.swift /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /.build 3 | .swiftpm 4 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. 6 | 7 | ## Our Standards 8 | 9 | Examples of behaviour that contributes to creating a positive environment include: 10 | 11 | * Using welcoming and inclusive language 12 | * Being respectful of differing viewpoints and experiences 13 | * Gracefully accepting constructive criticism 14 | * Focusing on what is best for the community 15 | * Showing empathy towards other community members 16 | 17 | Examples of unacceptable behaviour by participants include: 18 | 19 | * The use of sexualised language or imagery and unwelcome sexual attention or advances 20 | * Trolling, insulting/derogatory comments, and personal or political attacks 21 | * Public or private harassment 22 | * Publishing others' private information, such as a physical or electronic address, without explicit permission 23 | * Other conduct which could reasonably be considered inappropriate in a professional setting 24 | 25 | ## Our Responsibilities 26 | 27 | Project maintainers are responsible for clarifying the standards of acceptable behaviour and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behaviour. 28 | 29 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviours that they deem inappropriate, threatening, offensive, or harmful. 30 | 31 | ## Scope 32 | 33 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. 34 | 35 | ## Enforcement 36 | 37 | Instances of abusive, harassing, or otherwise unacceptable behaviour may be reported by contacting the project team at **c@tsarouhas.eu**. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. 38 | 39 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. 40 | 41 | ## Attribution 42 | 43 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] 44 | 45 | [homepage]: http://contributor-covenant.org 46 | [version]: http://contributor-covenant.org/version/1/4/ 47 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Constantino Tsarouhas 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "object": { 3 | "pins": [ 4 | { 5 | "package": "DepthKit", 6 | "repositoryURL": "https://github.com/ctxppc/DepthKit.git", 7 | "state": { 8 | "branch": null, 9 | "revision": "5a05be3926680a9fd99c411d41bfd95f835ec419", 10 | "version": "0.10.0" 11 | } 12 | } 13 | ] 14 | }, 15 | "version": 1 16 | } 17 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.5 2 | // PatternKit © 2017–21 Constantino Tsarouhas 3 | 4 | import PackageDescription 5 | 6 | let package = Package( 7 | name: "PatternKit", 8 | products: [ 9 | .library(name: "PatternKit", targets: ["PatternKit"]), 10 | .library(name: "RegexKit", targets: ["RegexKit"]), 11 | ], 12 | dependencies: [ 13 | .package(url: "https://github.com/ctxppc/DepthKit.git", .upToNextMinor(from: "0.10.0")), 14 | ], 15 | targets: [ 16 | 17 | // The PatternKitCore target contains primitive types from which all kinds of patterns can be defined. 18 | .target(name: "PatternKitCore", dependencies: [ 19 | .product(name: "DepthKit", package: "DepthKit"), 20 | ]), 21 | 22 | // The PatternKit target extends PatternKitCore with a set of concrete Pattern types. 23 | .target(name: "PatternKit", dependencies: ["PatternKitCore"]), 24 | .testTarget(name: "PatternKitTests", dependencies: [ 25 | "PatternKit", 26 | .product(name: "DepthKit", package: "DepthKit"), 27 | ]), 28 | 29 | // The RegexKit target extends PatternKit with regular expression support. 30 | .target(name: "RegexKit", dependencies: ["PatternKit"]), 31 | .testTarget(name: "RegexKitTests", dependencies: [ 32 | "RegexKit", 33 | .product(name: "DepthKit", package: "DepthKit"), 34 | ]) 35 | 36 | ], 37 | swiftLanguageVersions: [.v5] 38 | ) 39 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # The *PatternKit* library 2 | **PatternKit** is a Swift-based nondeterministic automaton parser and regular expression engine. 3 | 4 | + Fully designed for and written in Swift 5 | + Supports arbitrary `Sequence`s with `Equatable` elements (`String`, `[Int]`, and so on) 6 | + Batteries included — including full support for matching over Unicode extended grapheme clusters 7 | + Type-safe — syntax checked at build time by the compiler 8 | + No parsing/building overhead 9 | + Value semantics 10 | + Extensible architecture 11 | + Backreferences 12 | + Backtracking 13 | + Shorthand and longhand notation 14 | 15 | PatternKit is still a pre-1.0 project — please use with care. :-) 16 | 17 | ## Usage 18 | (Author's note: Some examples in this document might become outdated as PatternKit evolves.) 19 | 20 | The following matches very simple e-mail addresses. 21 | 22 | import PatternKit 23 | 24 | let alphanumeric = ("A"..."Z") | ("a"..."z") | ("0"..."9") 25 | let part = alphanumeric+ • "." • alphanumeric+ 26 | 27 | let userToken = Token(part) 28 | let hostToken = Token(part) 29 | 30 | let emailAddress = userToken • ("@" as Literal) • hostToken 31 | 32 | if let match = emailAddress.matches(over: "johnny.appleseed@example.com").first { 33 | let userPart = match.captures(for: userToken)[0] 34 | let hostPart = match.captures(for: hostToken)[0] 35 | print("User \(userPart) at \(hostPart)") // Prints "User johnny.appleseed at example.com" 36 | } 37 | 38 | Note that the `Match.captures(for:)` method returns an array of captured strings instead of a single string. Like in other regex engines, a token can be used within a repeating pattern. However, unlike most engines, every new capture by that token is appended to the array instead of discarded. 39 | 40 | The following matches the request line of a simple HTTP request. 41 | 42 | import PatternKit 43 | 44 | let verb = "GET" | "POST" | "PUT" | "HEAD" | "DELETE" | "OPTIONS" | "TRACE" 45 | let version = "0"..."9" • ("." • "0"..."9")+ 46 | 47 | let pathComponent = "A"..."Z" | "a"..."z" | "0"..."9" | "_" | "+" 48 | let path = ("/" • pathComponent*)+ 49 | let pathToken = Token(path) 50 | 51 | let requestLine = Anchor.leading • "GET" • CharacterSet.whitespace+ • pathToken • "HTTP/" • version • "\x10\x13" 52 | 53 | The following matches subsequences of three or more 6s, followed by two rolls greater than or equal to 3, and followed by three or more 6s again. 54 | 55 | let snakes = 6.repeated(min: 3) 56 | let pattern = snakes • (3...6).repeated(exactly: 2) • snakes 57 | let diceRolls = [1, 5, 2, 6, 6, 6, 4, 4, 6, 6, 6, 4, 3, 6, 6, 6, 3, 5, 6, 6, 6, 6] 58 | 59 | for match in pattern.matches(in: diceRolls) { 60 | let diceRollsNumber = match.startIndex + 1 61 | let foundPattern = match.subsequence.joined(separator: " ,") 62 | print("Pattern \(foundPattern) found at roll (diceRollsNumber)") 63 | } 64 | 65 | // Prints "Pattern 6, 6, 6, 4, 4, 6, 6, 6 found at roll 4" and "Pattern 6, 6, 6, 3, 5, 6, 6, 6, 6 found at roll 14" 66 | 67 | The following matches any subsequence of a's, followed by b's, and followed by the same number of a's. 68 | 69 | let firstPart = Token("a"+) 70 | let secondPart = "b"+ 71 | let thirdPart = Referencing(firstPart) 72 | let pattern = firstPart • secondPart • thirdPart 73 | 74 | print(pattern.hasMatches(in: "aaaabbbaaaa")) // Prints "true" 75 | 76 | PatternKit supports all target `Sequence`s with `Equatable` elements. Non-`Comparable` elements which are `Equatable` are also supported, but without support of ranges (`...` and `..<`). 77 | 78 | ## Available patterns & extensibility 79 | PatternKit is designed from the ground up to be extensible with more patterns. Patterns are values that conform to the `Pattern` protocol. PatternKit provides the following pattern types, and clients can implement more themselves as needed. 80 | 81 | * A literal pattern (`Literal`) matches an exact sequence. PatternKit includes literal-convertible conformances so that string, character, and integer literals can be used directly within pattern expressions, without having to resort to the `Literal` initialiser. 82 | 83 | * A repeating pattern (`EagerlyRepeating` and `LazilyRepeating`) matches a subpattern consecutively, with an optional minimum and maximum. PatternKit extends `Equatable` and `Pattern` with the `repeated(min:)`, `repeated(min:max:)`, and `repeated(exactly:)` methods as well as the `*`, `+`, and `¿` postfix operators which form a repeating pattern on the element, sequence, or subpattern it's called on. 84 | 85 | * An anchor (`Anchor`) matches a boundary or position, such as a word boundary or the beginning of the sequence. By default, pattern matching begins and ends anywhere in a sequence, so leading and trailing anchors are required if a pattern must match the whole sequence at once. 86 | 87 | * A token (`Token`) matches what its subpattern matches, but also captures it. The captured subsequences can be retrieved using `match.captures(for: token)`. 88 | 89 | * A referencing pattern (`Referencing`) matches a subsequence previously captured by a token. 90 | 91 | In addition, PatternKit adds conformance to Swift's range types and Foundation's `CharacterSet` so that they can be used readily in patterns. -------------------------------------------------------------------------------- /Sources/PatternKit/Alternation/Alternation Operators.swift: -------------------------------------------------------------------------------- 1 | // PatternKit © 2017–21 Constantino Tsarouhas 2 | 3 | /// Returns an alternation between two arbitrary patterns. 4 | /// 5 | /// - Parameter mainPattern: The pattern whose matches are generated first. 6 | /// - Parameter alternativePattern: The pattern whose matches are generated after those of the main pattern. 7 | /// 8 | /// - Returns: `Alternation(mainPattern, alternativePattern)` 9 | public func | (mainPattern: L, alternativePattern: R) -> Alternation { 10 | .init(mainPattern, alternativePattern) 11 | } 12 | -------------------------------------------------------------------------------- /Sources/PatternKit/Alternation/Alternation.swift: -------------------------------------------------------------------------------- 1 | // PatternKit © 2017–21 Constantino Tsarouhas 2 | 3 | /// A pattern that matches two patterns separately. 4 | public struct Alternation where MainPattern.Subject == AlternativePattern.Subject { 5 | 6 | public typealias Subject = MainPattern.Subject 7 | 8 | /// Creates a pattern that matches two patterns separately and sequentially. 9 | /// 10 | /// - Parameter mainPattern: The pattern whose matches are generated first. 11 | /// - Parameter alternativePattern: The pattern whose matches are generated after those of the main pattern. 12 | public init(_ mainPattern: MainPattern, _ alternativePattern: AlternativePattern) { 13 | self.mainPattern = mainPattern 14 | self.alternativePattern = alternativePattern 15 | } 16 | 17 | /// The pattern whose matches are generated first. 18 | public var mainPattern: MainPattern 19 | 20 | /// The pattern whose matches are generated after those of the main pattern. 21 | public var alternativePattern: AlternativePattern 22 | 23 | } 24 | 25 | extension Alternation : Pattern { 26 | 27 | public func forwardMatches(enteringFrom base: Match) -> ForwardAlternationMatchCollection { 28 | .init(mainPattern: mainPattern, alternativePattern: alternativePattern, baseMatch: base) 29 | } 30 | 31 | public func backwardMatches(recedingFrom base: Match) -> BackwardAlternationMatchCollection { 32 | .init(mainPattern: mainPattern, alternativePattern: alternativePattern, baseMatch: base) 33 | } 34 | 35 | public func underestimatedSmallestInputPositionForForwardMatching(on subject: Subject, fromIndex inputPosition: Subject.Index) -> Subject.Index { 36 | Swift.min(mainPattern.underestimatedSmallestInputPositionForForwardMatching(on: subject, fromIndex: inputPosition), alternativePattern.underestimatedSmallestInputPositionForForwardMatching(on: subject, fromIndex: inputPosition)) 37 | } 38 | 39 | public func overestimatedLargestInputPositionForBackwardMatching(on subject: Subject, fromIndex inputPosition: Subject.Index) -> Subject.Index { 40 | Swift.max(mainPattern.overestimatedLargestInputPositionForBackwardMatching(on: subject, fromIndex: inputPosition), alternativePattern.overestimatedLargestInputPositionForBackwardMatching(on: subject, fromIndex: inputPosition)) 41 | } 42 | 43 | } 44 | 45 | extension Alternation : BidirectionalCollection { 46 | 47 | public enum Index : Int, Hashable { 48 | 49 | /// The position of the main pattern. 50 | case mainPattern = 0 51 | 52 | /// The position of the alternative pattern. 53 | case alternativePattern 54 | 55 | /// The past-the-end position. 56 | case end 57 | 58 | } 59 | 60 | public enum Element { 61 | 62 | /// The main pattern. 63 | case mainPattern(MainPattern) 64 | 65 | /// The alternative pattern. 66 | case alternativePattern(AlternativePattern) 67 | 68 | } 69 | 70 | public var startIndex: Index { .mainPattern } 71 | 72 | public var endIndex: Index { .end } 73 | 74 | public subscript (index: Index) -> Element { 75 | switch index { 76 | case .mainPattern: return .mainPattern(mainPattern) 77 | case .alternativePattern: return .alternativePattern(alternativePattern) 78 | case .end: preconditionFailure("Index out of bounds") 79 | } 80 | } 81 | 82 | public func index(before index: Index) -> Index { 83 | switch index { 84 | case .mainPattern: preconditionFailure("Index out of bounds") 85 | case .alternativePattern: return .mainPattern 86 | case .end: return .alternativePattern 87 | } 88 | } 89 | 90 | public func index(after index: Index) -> Index { 91 | switch index { 92 | case .mainPattern: return .alternativePattern 93 | case .alternativePattern: return .end 94 | case .end: preconditionFailure("Index out of bounds") 95 | } 96 | } 97 | 98 | } 99 | 100 | extension Alternation.Index : Comparable { 101 | public static func <(leftIndex: Self, rightIndex: Self) -> Bool { 102 | leftIndex.rawValue < rightIndex.rawValue 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /Sources/PatternKit/Alternation/Backward Alternation Match Collection.swift: -------------------------------------------------------------------------------- 1 | // PatternKit © 2017–21 Constantino Tsarouhas 2 | 3 | /// A collection of matches of a concatenation pattern. 4 | public struct BackwardAlternationMatchCollection where MainPattern.Subject == AlternativePattern.Subject { 5 | 6 | public typealias Subject = MainPattern.Subject 7 | 8 | /// Creates a concatenation match pattern. 9 | /// 10 | /// - Parameter mainPattern: The matches that are generated first. 11 | /// - Parameter alternativePattern: The matches that are generated after all matches of the main pattern have been generated. 12 | /// - Parameter baseMatch: The base match. 13 | internal init(mainPattern: MainPattern, alternativePattern: AlternativePattern, baseMatch: Match) { 14 | self.matchesOfMainPattern = mainPattern.backwardMatches(recedingFrom: baseMatch) 15 | self.matchesOfAlternativePattern = alternativePattern.backwardMatches(recedingFrom: baseMatch) 16 | self.baseMatch = baseMatch 17 | } 18 | 19 | /// The matches that are generated first. 20 | public let matchesOfMainPattern: MainPattern.BackwardMatchCollection 21 | 22 | /// The matches that are generated after all matches of the main pattern have been generated. 23 | public let matchesOfAlternativePattern: AlternativePattern.BackwardMatchCollection 24 | 25 | /// The base match. 26 | public let baseMatch: Match 27 | 28 | } 29 | 30 | extension BackwardAlternationMatchCollection : BidirectionalCollection { 31 | 32 | public typealias Index = BackwardAlternationMatchCollectionIndex 33 | 34 | public var startIndex: Index { 35 | if matchesOfAlternativePattern.startIndex != matchesOfAlternativePattern.endIndex { 36 | return .inAlternativePattern(innerIndex: matchesOfAlternativePattern.startIndex) 37 | } else if matchesOfMainPattern.startIndex != matchesOfMainPattern.endIndex { 38 | return .inMainPattern(innerIndex: matchesOfMainPattern.startIndex) 39 | } else { 40 | return .end 41 | } 42 | } 43 | 44 | public var endIndex: Index { .end } 45 | 46 | public subscript (index: Index) -> Match { 47 | switch index { 48 | case .inMainPattern(innerIndex: let innerIndex): return matchesOfMainPattern[innerIndex] 49 | case .inAlternativePattern(innerIndex: let innerIndex): return matchesOfAlternativePattern[innerIndex] 50 | case .end: preconditionFailure("Index out of bounds") 51 | } 52 | } 53 | 54 | public func index(before index: Index) -> Index { 55 | switch index { 56 | 57 | case .end: 58 | if matchesOfMainPattern.startIndex != matchesOfMainPattern.endIndex { 59 | return .inMainPattern(innerIndex: matchesOfMainPattern.index(before: matchesOfMainPattern.endIndex)) 60 | } else if matchesOfAlternativePattern.startIndex != matchesOfAlternativePattern.endIndex { 61 | return .inAlternativePattern(innerIndex: matchesOfAlternativePattern.index(before: matchesOfAlternativePattern.endIndex)) 62 | } else { 63 | preconditionFailure("Index out of bounds") 64 | } 65 | 66 | case .inAlternativePattern(innerIndex: let innerIndex): 67 | if innerIndex != matchesOfAlternativePattern.startIndex { 68 | return .inAlternativePattern(innerIndex: matchesOfAlternativePattern.index(before: innerIndex)) 69 | } else { 70 | preconditionFailure("Index out of bounds") 71 | } 72 | 73 | case .inMainPattern(innerIndex: let innerIndex): 74 | if innerIndex != matchesOfMainPattern.startIndex { 75 | return .inMainPattern(innerIndex: matchesOfMainPattern.index(before: innerIndex)) 76 | } else { 77 | precondition(matchesOfAlternativePattern.endIndex != matchesOfAlternativePattern.startIndex, "Index out of bounds") 78 | return .inAlternativePattern(innerIndex: matchesOfAlternativePattern.index(before: matchesOfAlternativePattern.endIndex)) 79 | } 80 | 81 | } 82 | } 83 | 84 | public func index(after index: Index) -> Index { 85 | switch index { 86 | 87 | case .inMainPattern(innerIndex: let innerIndex): 88 | let nextInnerIndex = matchesOfMainPattern.index(after: innerIndex) 89 | if nextInnerIndex != matchesOfMainPattern.endIndex { 90 | return .inMainPattern(innerIndex: nextInnerIndex) 91 | } else { 92 | return .end 93 | } 94 | 95 | case .inAlternativePattern(innerIndex: let innerIndex): 96 | let nextInnerIndex = matchesOfAlternativePattern.index(after: innerIndex) 97 | guard nextInnerIndex != matchesOfAlternativePattern.endIndex else { return .end } 98 | return .inAlternativePattern(innerIndex: nextInnerIndex) 99 | 100 | case .end: 101 | preconditionFailure("Index out of bounds") 102 | 103 | } 104 | } 105 | 106 | } 107 | 108 | public enum BackwardAlternationMatchCollectionIndex : Equatable where MainPattern.Subject == AlternativePattern.Subject { 109 | 110 | /// A position within the main pattern. 111 | /// 112 | /// - Invariant: `innerIndex` is not equal to `matchesOfMainPattern.endIndex` of the alternation match collection. 113 | /// 114 | /// - Parameter innerIndex: The index within the main pattern's match collection. 115 | case inMainPattern(innerIndex: MainPattern.BackwardMatchCollection.Index) 116 | 117 | /// A position within the alternative pattern. 118 | /// 119 | /// - Invariant: `innerIndex` is not equal to `matchesOfAlternativePattern.endIndex` of the alternation match collection. 120 | /// 121 | /// - Parameter innerIndex: The index within the alternative pattern's match collection. 122 | case inAlternativePattern(innerIndex: AlternativePattern.BackwardMatchCollection.Index) 123 | 124 | /// The position after the last element of the match collection. 125 | case end 126 | 127 | } 128 | 129 | extension BackwardAlternationMatchCollectionIndex : Comparable { 130 | public static func <(leftIndex: Self, rightIndex: Self) -> Bool { 131 | switch (leftIndex, rightIndex) { 132 | 133 | case (.inMainPattern(innerIndex: let innerIndexOfLeftIndex), .inMainPattern(innerIndex: let innerIndexOfRightIndex)): 134 | return innerIndexOfLeftIndex < innerIndexOfRightIndex 135 | 136 | case (.inMainPattern(innerIndex: _), .inAlternativePattern(innerIndex: _)): 137 | return false 138 | 139 | case (.inMainPattern, .end): 140 | return true 141 | 142 | case (.inAlternativePattern(innerIndex: _), .inMainPattern(innerIndex: _)): 143 | return true 144 | 145 | case (.inAlternativePattern(innerIndex: let innerIndexOfLeftIndex), .inAlternativePattern(innerIndex: let innerIndexOfRightIndex)): 146 | return innerIndexOfLeftIndex < innerIndexOfRightIndex 147 | 148 | case (.inAlternativePattern, .end): 149 | return true 150 | 151 | default: 152 | return false 153 | 154 | } 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /Sources/PatternKit/Alternation/Homogeneous/Backward Homogeneous Alternation Match Collection.swift: -------------------------------------------------------------------------------- 1 | // PatternKit © 2017–21 Constantino Tsarouhas 2 | 3 | /// A collection of backward matches of a homogeneous alternation pattern. 4 | public struct BackwardHomogeneousAlternationMatchCollection { 5 | 6 | public typealias Subject = Subpattern.Subject 7 | 8 | /// Creates a homogeneous alternation match collection. 9 | /// 10 | /// - Parameter subpatterns: The subpatterns. 11 | /// - Parameter baseMatch: The base match. 12 | internal init(subpatterns: [Subpattern], baseMatch: Match) { 13 | assert(subpatterns.count >= 2) 14 | self.subpatterns = subpatterns 15 | self.baseMatch = baseMatch 16 | } 17 | 18 | /// The subpatterns of the alternation. 19 | /// 20 | /// - Invariant: `subpatterns` contains at least two subpatterns. 21 | public let subpatterns: [Subpattern] 22 | 23 | /// The base match. 24 | public let baseMatch: Match 25 | 26 | } 27 | 28 | extension BackwardHomogeneousAlternationMatchCollection : BidirectionalCollection { 29 | 30 | public enum Index : Equatable { 31 | 32 | /// A position to a valid match. 33 | /// 34 | /// - Invariant: No index in `indicesBySubpattern` is not equal to `endIndex` of their respective subpattern's match collection. 35 | /// 36 | /// - Parameter indicesBySubpattern: The index within the leading pattern's match collection. 37 | case some(indicesBySubpattern: [Subpattern.BackwardMatchCollection.Index]) 38 | 39 | /// The position after the last element of the match collection. 40 | case end 41 | 42 | } 43 | 44 | public var startIndex: Index { 45 | TODO.unimplemented 46 | } 47 | 48 | public var endIndex: Index { 49 | TODO.unimplemented 50 | } 51 | 52 | public subscript (index: Index) -> Match { 53 | TODO.unimplemented 54 | } 55 | 56 | public func index(before index: Index) -> Index { 57 | TODO.unimplemented 58 | } 59 | 60 | public func index(after index: Index) -> Index { 61 | TODO.unimplemented 62 | } 63 | 64 | } 65 | 66 | extension BackwardHomogeneousAlternationMatchCollection.Index : Comparable { 67 | public static func <(lhs: Self, rhs: Self) -> Bool { 68 | TODO.unimplemented 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /Sources/PatternKit/Alternation/Homogeneous/Forward Homogeneous Alternation Match Collection.swift: -------------------------------------------------------------------------------- 1 | // PatternKit © 2017–21 Constantino Tsarouhas 2 | 3 | /// A collection of forward matches of a homogeneous alternation pattern. 4 | public struct ForwardHomogeneousAlternationMatchCollection { 5 | 6 | public typealias Subject = Subpattern.Subject 7 | 8 | /// Creates a homogeneous alternation match collection. 9 | /// 10 | /// - Parameter subpatterns: The subpatterns. 11 | /// - Parameter baseMatch: The base match. 12 | internal init(subpatterns: [Subpattern], baseMatch: Match) { 13 | assert(subpatterns.count >= 2) 14 | self.subpatterns = subpatterns 15 | self.baseMatch = baseMatch 16 | } 17 | 18 | /// The subpatterns of the alternation. 19 | /// 20 | /// - Invariant: `subpatterns` contains at least two subpatterns. 21 | public let subpatterns: [Subpattern] 22 | 23 | /// The base match. 24 | public let baseMatch: Match 25 | 26 | } 27 | 28 | extension ForwardHomogeneousAlternationMatchCollection : BidirectionalCollection { 29 | 30 | public enum Index : Equatable { 31 | 32 | /// A position to a valid match. 33 | /// 34 | /// - Invariant: No index in `indicesBySubpattern` is not equal to `endIndex` of their respective subpattern's match collection. 35 | /// 36 | /// - Parameter indicesBySubpattern: The index within the leading pattern's match collection. 37 | case some(indicesBySubpattern: [Subpattern.ForwardMatchCollection.Index]) 38 | 39 | /// The position after the last element of the match collection. 40 | case end 41 | 42 | } 43 | 44 | public var startIndex: Index { 45 | TODO.unimplemented 46 | } 47 | 48 | public var endIndex: Index { 49 | TODO.unimplemented 50 | } 51 | 52 | public subscript (index: Index) -> Match { 53 | TODO.unimplemented 54 | } 55 | 56 | public func index(before index: Index) -> Index { 57 | TODO.unimplemented 58 | } 59 | 60 | public func index(after index: Index) -> Index { 61 | TODO.unimplemented 62 | } 63 | 64 | } 65 | 66 | extension ForwardHomogeneousAlternationMatchCollection.Index : Comparable { 67 | public static func <(lhs: Self, rhs: Self) -> Bool { 68 | TODO.unimplemented 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /Sources/PatternKit/Alternation/Homogeneous/Homogeneous Alternation.swift: -------------------------------------------------------------------------------- 1 | // PatternKit © 2017–21 Constantino Tsarouhas 2 | 3 | /// A pattern that matches a set of same-typed patterns separately. 4 | public struct HomogeneousAlternation { 5 | 6 | public typealias Subject = Subpattern.Subject 7 | 8 | /// Creates a homogeneous alternation pattern with given subpatterns. 9 | /// 10 | /// - Parameter mainPattern: The main pattern. 11 | /// - Parameter otherPatterns: The alternative patterns. 12 | public init(_ mainPattern: Subpattern, _ otherPatterns: Subpattern...) { 13 | self.subpatterns = [mainPattern] + otherPatterns 14 | } 15 | 16 | /// Creates a homogeneous alternation pattern with given subpatterns. 17 | /// 18 | /// - Requires: `subpatterns` contains at least two subpatterns. 19 | /// 20 | /// - Parameter subpatterns: The subpatterns. 21 | public init(_ subpatterns: [Subpattern]) { 22 | precondition(subpatterns.count >= 2, "Fewer than 2 subpatterns in alternation") 23 | self.subpatterns = subpatterns 24 | } 25 | 26 | /// The subpatterns, in order of matching. 27 | /// 28 | /// - Invariant: `subpatterns` contains at least two subpatterns. 29 | public var subpatterns: [Subpattern] { 30 | willSet { precondition(newValue.count >= 2, "Fewer than 2 subpatterns in alternation") } 31 | } 32 | 33 | } 34 | 35 | extension HomogeneousAlternation : Pattern { 36 | 37 | public func forwardMatches(enteringFrom base: Match) -> ForwardHomogeneousAlternationMatchCollection { 38 | .init(subpatterns: subpatterns, baseMatch: base) 39 | } 40 | 41 | public func backwardMatches(recedingFrom base: Match) -> BackwardHomogeneousAlternationMatchCollection { 42 | .init(subpatterns: subpatterns, baseMatch: base) 43 | } 44 | 45 | public func underestimatedSmallestInputPositionForForwardMatching(on subject: Subpattern.Subject, fromIndex inputPosition: Subpattern.Subject.Index) -> Subpattern.Subject.Index { 46 | subpatterns.map { 47 | $0.underestimatedSmallestInputPositionForForwardMatching(on: subject, fromIndex: inputPosition) 48 | }.min()! 49 | } 50 | 51 | public func overestimatedLargestInputPositionForBackwardMatching(on subject: Subpattern.Subject, fromIndex inputPosition: Subpattern.Subject.Index) -> Subpattern.Subject.Index { 52 | subpatterns.map { 53 | $0.overestimatedLargestInputPositionForBackwardMatching(on: subject, fromIndex: inputPosition) 54 | }.max()! 55 | } 56 | 57 | } 58 | -------------------------------------------------------------------------------- /Sources/PatternKit/Any Pattern.swift: -------------------------------------------------------------------------------- 1 | // PatternKit © 2017–21 Constantino Tsarouhas 2 | 3 | /// A type-erased pattern for some subject type. 4 | /// 5 | /// This pattern forwards its `matches(base:direction:)` method to an arbitrary, underlying pattern on `Collection`, hiding the specifics of the underlying `Pattern` conformance. 6 | /// 7 | /// Type-erased patterns are useful in dynamic contexts, e.g., when patterns are formed at runtime by an end user. Typed patterns (with typed subpatterns and so on) may be more efficient as they present visible optimisation opportunities to the compiler. 8 | public struct AnyPattern where Subject.Element : Equatable { 9 | 10 | /// Creates a type-erased container for a given pattern. 11 | /// 12 | /// - Parameter pattern: The pattern. 13 | public init

(_ pattern: P) where P.Subject == Subject { 14 | 15 | forwardMatchCollectionGenerator = { base in 16 | .init(pattern.forwardMatches(enteringFrom: base)) 17 | } 18 | 19 | backwardMatchCollectionGenerator = { base in 20 | .init(pattern.backwardMatches(recedingFrom: base)) 21 | } 22 | 23 | forwardEstimator = { subject, index in 24 | pattern.underestimatedSmallestInputPositionForForwardMatching(on: subject, fromIndex: index) 25 | } 26 | 27 | backwardEstimator = { subject, index in 28 | pattern.overestimatedLargestInputPositionForBackwardMatching(on: subject, fromIndex: index) 29 | } 30 | 31 | self.pattern = pattern 32 | 33 | } 34 | 35 | /// Creates a type-erased container for a given pattern. 36 | /// 37 | /// - Parameter pattern: The pattern. 38 | public init(_ pattern: AnyPattern) { 39 | self = pattern 40 | } 41 | 42 | /// A generator of forward match collections, given a base match. 43 | fileprivate let forwardMatchCollectionGenerator: (Match) -> AnyBidirectionalCollection> 44 | 45 | /// A generator of backward match collections, given a base match. 46 | fileprivate let backwardMatchCollectionGenerator: (Match) -> AnyBidirectionalCollection> 47 | 48 | /// The underestimator heuristic function of the pattern for forward matching. 49 | fileprivate let forwardEstimator: (Subject, Subject.Index) -> Subject.Index 50 | 51 | /// The underestimator heuristic function of the pattern for backward matching. 52 | fileprivate let backwardEstimator: (Subject, Subject.Index) -> Subject.Index 53 | 54 | /// The type-erased pattern. 55 | /// 56 | /// Clients can add type information back by casting `pattern` to a reified type, e.g., `pattern as? Literal`. 57 | public let pattern: Any 58 | 59 | } 60 | 61 | extension AnyPattern : Pattern { 62 | 63 | public func forwardMatches(enteringFrom base: Match) -> AnyBidirectionalCollection> { 64 | forwardMatchCollectionGenerator(base) 65 | } 66 | 67 | public func backwardMatches(recedingFrom base: Match) -> AnyBidirectionalCollection> { 68 | backwardMatchCollectionGenerator(base) 69 | } 70 | 71 | public func underestimatedSmallestInputPositionForForwardMatching(on subject: Subject, fromIndex inputPosition: Subject.Index) -> Subject.Index { 72 | forwardEstimator(subject, inputPosition) 73 | } 74 | 75 | public func overestimatedLargestInputPositionForBackwardMatching(on subject: Subject, fromIndex inputPosition: Subject.Index) -> Subject.Index { 76 | backwardEstimator(subject, inputPosition) 77 | } 78 | 79 | } 80 | -------------------------------------------------------------------------------- /Sources/PatternKit/Assertion/Assertion Operators.swift: -------------------------------------------------------------------------------- 1 | // PatternKit © 2017–21 Constantino Tsarouhas 2 | 3 | /// The forward-matching pattern assertion operator. 4 | prefix operator ?= 5 | 6 | /// Returns a forward assertion. 7 | /// 8 | /// - Parameter assertedPattern: The asserted pattern. 9 | /// 10 | /// - Returns: A forward-matching assertion. 11 | public prefix func ?=

(assertedPattern: P) -> ForwardAssertion

{ 12 | .init(assertedPattern) 13 | } 14 | 15 | /// The negated forward-matching pattern assertion operator. 16 | prefix operator ?! 17 | 18 | /// Returns a negated forward-matching assertion. 19 | /// 20 | /// - Parameter assertedPattern: The asserted pattern. 21 | /// 22 | /// - Returns: A negated forward-matching assertion. 23 | public prefix func ?!

(assertedPattern: P) -> NegatedForwardAssertion

{ 24 | .init(assertedPattern) 25 | } 26 | 27 | /// The backward-matching pattern assertion operator. 28 | prefix operator ?<= 29 | 30 | /// Returns a backward-matching assertion. 31 | /// 32 | /// - Parameter assertedPattern: The asserted pattern. 33 | /// 34 | /// - Returns: A backward-matching assertion. 35 | public prefix func ?<=

(assertedPattern: P) -> BackwardAssertion

{ 36 | .init(assertedPattern) 37 | } 38 | 39 | /// The negated backward-matching pattern assertion operator. 40 | prefix operator ?(assertedPattern: P) -> NegatedBackwardAssertion

{ 48 | .init(assertedPattern) 49 | } 50 | -------------------------------------------------------------------------------- /Sources/PatternKit/Assertion/Backward Assertion Match Collection.swift: -------------------------------------------------------------------------------- 1 | // PatternKit © 2017–21 Constantino Tsarouhas 2 | 3 | public struct BackwardAssertionMatchCollection { 4 | 5 | public typealias Subject = AssertedPattern.Subject 6 | 7 | /// Creates a backward assertion match collection. 8 | /// 9 | /// - Parameter assertedPattern: The pattern that must produce at least one match for the assertion to hold. 10 | /// - Parameter baseMatch: The base match. 11 | internal init(assertedPattern: AssertedPattern, baseMatch: Match) { 12 | self.assertedPattern = assertedPattern 13 | self.baseMatch = baseMatch 14 | } 15 | 16 | /// The pattern that must produce at least one match for the assertion to hold. 17 | public let assertedPattern: AssertedPattern 18 | 19 | /// The base match. 20 | public let baseMatch: Match 21 | 22 | } 23 | 24 | extension BackwardAssertionMatchCollection : BidirectionalCollection { 25 | 26 | public enum Index : Equatable { 27 | 28 | /// A position within the asserted pattern. 29 | /// 30 | /// - Invariant: `innerIndex` is not equal to `endIndex` of the asserted pattern's match collection. 31 | /// 32 | /// - Parameter innerIndex: The index on the asserted pattern's match collection referring to the match that caused the assertion to hold. 33 | case some(innerIndex: AssertedPattern.BackwardMatchCollection.Index) 34 | 35 | /// The position after the last element of the collection. 36 | case end 37 | 38 | } 39 | 40 | public var startIndex: Index { 41 | let matchesOfAssertedPattern = assertedPattern.backwardMatches(recedingFrom: baseMatch) 42 | let indexOfFirstMatchOfAssertedPattern = matchesOfAssertedPattern.startIndex 43 | guard indexOfFirstMatchOfAssertedPattern != matchesOfAssertedPattern.endIndex else { return .end } 44 | return .some(innerIndex: indexOfFirstMatchOfAssertedPattern) 45 | } 46 | 47 | public var endIndex: Index { .end } 48 | 49 | public subscript (index: Index) -> Match { 50 | let matchesOfAssertedPattern = assertedPattern.backwardMatches(recedingFrom: baseMatch) 51 | guard case .some(innerIndex: let innerIndex) = index else { preconditionFailure("Index out of bounds") } 52 | return matchesOfAssertedPattern[innerIndex].resuming(from: baseMatch) 53 | } 54 | 55 | public func index(before index: Index) -> Index { 56 | 57 | precondition(index == .end, "Index out of bounds") 58 | 59 | let matchesOfAssertedPattern = assertedPattern.backwardMatches(recedingFrom: baseMatch) 60 | let indexOfFirstMatchOfAssertedPattern = matchesOfAssertedPattern.startIndex 61 | 62 | precondition(indexOfFirstMatchOfAssertedPattern != matchesOfAssertedPattern.endIndex, "Index out of bounds") 63 | return .some(innerIndex: indexOfFirstMatchOfAssertedPattern) 64 | 65 | } 66 | 67 | public func index(after index: Index) -> Index { 68 | precondition(index != .end, "Index out of bounds") 69 | return .end 70 | } 71 | 72 | } 73 | 74 | extension BackwardAssertionMatchCollection.Index : Comparable { 75 | public static func <(leftIndex: Self, rightIndex: Self) -> Bool { 76 | if case (.some, .end) = (leftIndex, rightIndex) { 77 | return true 78 | } else { 79 | return false 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /Sources/PatternKit/Assertion/Backward Assertion.swift: -------------------------------------------------------------------------------- 1 | // PatternKit © 2017–21 Constantino Tsarouhas 2 | 3 | /// A pattern that asserts that an asserted pattern matches the part of the subject preceding the input position, without actually moving the input position beyond the assertion; also known as a positive lookbehind. 4 | /// 5 | /// While the assertion does not change the input position, it does preserve captures by tokens contained within the asserted pattern. However, the assertion only produces one match from the asserted pattern. 6 | /// 7 | /// Note that a backward assertion can also be used within a forward matching context; this does not affect the matching direction of the asserted pattern since the assertion itself is strictly backward-matching. 8 | public struct BackwardAssertion { 9 | 10 | public typealias Subject = AssertedPattern.Subject 11 | 12 | /// Creates a backward assertion. 13 | /// 14 | /// - Parameter assertedPattern: The pattern that must produce at least one match for the assertion to hold. 15 | public init(_ assertedPattern: AssertedPattern) { 16 | self.assertedPattern = assertedPattern 17 | } 18 | 19 | /// The pattern that must produce at least one match for the assertion to hold. 20 | public var assertedPattern: AssertedPattern 21 | 22 | } 23 | 24 | extension BackwardAssertion : Pattern { 25 | 26 | public func forwardMatches(enteringFrom base: Match) -> BackwardAssertionMatchCollection { 27 | .init(assertedPattern: assertedPattern, baseMatch: base) 28 | } 29 | 30 | public func backwardMatches(recedingFrom base: Match) -> BackwardAssertionMatchCollection { 31 | .init(assertedPattern: assertedPattern, baseMatch: base) 32 | } 33 | 34 | } 35 | 36 | extension BackwardAssertion : BidirectionalCollection { 37 | 38 | public enum Index : Int, Hashable { 39 | 40 | /// The position of the asserted pattern. 41 | case assertedPattern = 0 42 | 43 | /// The past-the-end position. 44 | case end 45 | 46 | } 47 | 48 | public var startIndex: Index { .assertedPattern } 49 | 50 | public var endIndex: Index { .end } 51 | 52 | public subscript (index: Index) -> AssertedPattern { 53 | precondition(index == .assertedPattern, "Index out of bounds") 54 | return assertedPattern 55 | } 56 | 57 | public func index(before index: Index) -> Index { 58 | precondition(index == .end, "Index out of bounds") 59 | return .assertedPattern 60 | } 61 | 62 | public func index(after index: Index) -> Index { 63 | precondition(index == .assertedPattern, "Index out of bounds") 64 | return .end 65 | } 66 | 67 | } 68 | 69 | extension BackwardAssertion.Index : Comparable { 70 | public static func <(leftIndex: Self, rightIndex: Self) -> Bool { 71 | leftIndex.rawValue < rightIndex.rawValue 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /Sources/PatternKit/Assertion/Forward Assertion Match Collection.swift: -------------------------------------------------------------------------------- 1 | // PatternKit © 2017–21 Constantino Tsarouhas 2 | 3 | public struct ForwardAssertionMatchCollection { 4 | 5 | public typealias Subject = AssertedPattern.Subject 6 | 7 | /// Creates a forward assertion match collection. 8 | /// 9 | /// - Parameter assertedPattern: The pattern that must produce at least one match for the assertion to hold. 10 | /// - Parameter baseMatch: The base match. 11 | internal init(assertedPattern: AssertedPattern, baseMatch: Match) { 12 | self.assertedPattern = assertedPattern 13 | self.baseMatch = baseMatch 14 | } 15 | 16 | /// The pattern that must produce at least one match for the assertion to hold. 17 | public let assertedPattern: AssertedPattern 18 | 19 | /// The base match. 20 | public let baseMatch: Match 21 | 22 | } 23 | 24 | extension ForwardAssertionMatchCollection : BidirectionalCollection { 25 | 26 | public enum Index : Equatable { 27 | 28 | /// A position within the asserted pattern. 29 | /// 30 | /// - Invariant: `innerIndex` is not equal to `endIndex` of the asserted pattern's match collection. 31 | /// 32 | /// - Parameter innerIndex: The index on the asserted pattern's match collection referring to the match that caused the assertion to hold. 33 | case some(innerIndex: AssertedPattern.ForwardMatchCollection.Index) 34 | 35 | /// The position after the last element of the collection. 36 | case end 37 | 38 | } 39 | 40 | public var startIndex: Index { 41 | let matchesOfAssertedPattern = assertedPattern.forwardMatches(enteringFrom: baseMatch) 42 | let indexOfFirstMatchOfAssertedPattern = matchesOfAssertedPattern.startIndex 43 | guard indexOfFirstMatchOfAssertedPattern != matchesOfAssertedPattern.endIndex else { return .end } 44 | return .some(innerIndex: indexOfFirstMatchOfAssertedPattern) 45 | } 46 | 47 | public var endIndex: Index { .end } 48 | 49 | public subscript (index: Index) -> Match { 50 | let matchesOfAssertedPattern = assertedPattern.forwardMatches(enteringFrom: baseMatch) 51 | guard case .some(innerIndex: let innerIndex) = index else { preconditionFailure("Index out of bounds") } 52 | return matchesOfAssertedPattern[innerIndex].resuming(from: baseMatch) 53 | } 54 | 55 | public func index(before index: Index) -> Index { 56 | 57 | precondition(index == .end, "Index out of bounds") 58 | 59 | let matchesOfAssertedPattern = assertedPattern.forwardMatches(enteringFrom: baseMatch) 60 | let indexOfFirstMatchOfAssertedPattern = matchesOfAssertedPattern.startIndex 61 | 62 | precondition(indexOfFirstMatchOfAssertedPattern != matchesOfAssertedPattern.endIndex, "Index out of bounds") 63 | return .some(innerIndex: indexOfFirstMatchOfAssertedPattern) 64 | 65 | } 66 | 67 | public func index(after index: Index) -> Index { 68 | precondition(index != .end, "Index out of bounds") 69 | return .end 70 | } 71 | 72 | } 73 | 74 | extension ForwardAssertionMatchCollection.Index : Comparable { 75 | public static func <(leftIndex: Self, rightIndex: Self) -> Bool { 76 | if case (.some, .end) = (leftIndex, rightIndex) { 77 | return true 78 | } else { 79 | return false 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /Sources/PatternKit/Assertion/Forward Assertion.swift: -------------------------------------------------------------------------------- 1 | // PatternKit © 2017–21 Constantino Tsarouhas 2 | 3 | /// A pattern that asserts that an asserted pattern matches the part of the subject following the input position, without actually moving the input position beyond the assertion; also known as a positive lookahead. 4 | /// 5 | /// For example, `ForwardAssertion(Repeating(1...9, min: 5)) • Literal([1, 2, 3]) • any()+` matches all arrays starting with at least 5 elements between 1 and 9; and starting with the elements 1, 2, and 3. 6 | /// 7 | /// While the assertion does not change the input position, it does preserve captures by tokens contained within the asserted pattern. However, the assertion only produces one match from the asserted pattern. 8 | /// 9 | /// Note that a forward assertion can also be used within a backward matching context; this does not affect the matching direction of the asserted pattern since the assertion itself is strictly forward-matching. 10 | public struct ForwardAssertion { 11 | 12 | public typealias Subject = AssertedPattern.Subject 13 | 14 | /// Creates a forward assertion. 15 | /// 16 | /// - Parameter assertedPattern: The pattern that must produce at least one match for the assertion to hold. 17 | public init(_ assertedPattern: AssertedPattern) { 18 | self.assertedPattern = assertedPattern 19 | } 20 | 21 | /// The pattern that must produce at least one match for the assertion to hold. 22 | public var assertedPattern: AssertedPattern 23 | 24 | } 25 | 26 | extension ForwardAssertion : Pattern { 27 | 28 | public func forwardMatches(enteringFrom base: Match) -> ForwardAssertionMatchCollection { 29 | .init(assertedPattern: assertedPattern, baseMatch: base) 30 | } 31 | 32 | public func backwardMatches(recedingFrom base: Match) -> ForwardAssertionMatchCollection { 33 | .init(assertedPattern: assertedPattern, baseMatch: base) 34 | } 35 | 36 | public func underestimatedSmallestInputPositionForForwardMatching(on subject: Subject, fromIndex inputPosition: Subject.Index) -> Subject.Index { 37 | assertedPattern.underestimatedSmallestInputPositionForForwardMatching(on: subject, fromIndex: inputPosition) 38 | } 39 | 40 | public func overestimatedLargestInputPositionForBackwardMatching(on subject: Subject, fromIndex inputPosition: Subject.Index) -> Subject.Index { 41 | assertedPattern.overestimatedLargestInputPositionForBackwardMatching(on: subject, fromIndex: inputPosition) 42 | } 43 | 44 | } 45 | 46 | extension ForwardAssertion : BidirectionalCollection { 47 | 48 | public enum Index : Int, Hashable { 49 | 50 | /// The position of the asserted pattern. 51 | case assertedPattern = 0 52 | 53 | /// The past-the-end position. 54 | case end 55 | 56 | } 57 | 58 | public var startIndex: Index { .assertedPattern } 59 | 60 | public var endIndex: Index { .end } 61 | 62 | public subscript (index: Index) -> AssertedPattern { 63 | precondition(index == .assertedPattern, "Index out of bounds") 64 | return assertedPattern 65 | } 66 | 67 | public func index(before index: Index) -> Index { 68 | precondition(index == .end, "Index out of bounds") 69 | return .assertedPattern 70 | } 71 | 72 | public func index(after index: Index) -> Index { 73 | precondition(index == .assertedPattern, "Index out of bounds") 74 | return .end 75 | } 76 | 77 | } 78 | 79 | extension ForwardAssertion.Index : Comparable { 80 | public static func <(leftIndex: Self, rightIndex: Self) -> Bool { 81 | leftIndex.rawValue < rightIndex.rawValue 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /Sources/PatternKit/Assertion/Negated Backward Assertion Match Collection.swift: -------------------------------------------------------------------------------- 1 | // PatternKit © 2017–21 Constantino Tsarouhas 2 | 3 | public struct NegatedBackwardAssertionMatchCollection { 4 | 5 | public typealias Subject = AssertedPattern.Subject 6 | 7 | /// Creates a negated backward assertion match collection. 8 | /// 9 | /// - Parameter assertedPattern: The pattern that must produce no match for the assertion to hold. 10 | /// - Parameter baseMatch: The base match. 11 | internal init(assertedPattern: AssertedPattern, baseMatch: Match) { 12 | self.assertedPattern = assertedPattern 13 | self.matchesOfAssertedPattern = assertedPattern.backwardMatches(recedingFrom: baseMatch) 14 | self.baseMatch = baseMatch 15 | } 16 | 17 | /// The pattern that must produce no match for the assertion to hold. 18 | public let assertedPattern: AssertedPattern 19 | 20 | /// The matches of the asserted pattern. 21 | /// 22 | /// If empty, the negated assertion match collection contains the base match. Otherwise, it is empty. 23 | public let matchesOfAssertedPattern: AssertedPattern.BackwardMatchCollection 24 | 25 | /// The base match. 26 | public let baseMatch: Match 27 | 28 | } 29 | 30 | extension NegatedBackwardAssertionMatchCollection : BidirectionalCollection { 31 | 32 | public enum Index : Equatable { 33 | 34 | /// The position of the base match if the negated assertion holds, otherwise the end index of the collection. 35 | case baseMatch 36 | 37 | /// The position after the base match of the collection if the negated assertion holds, otherwise an invalid index. 38 | case afterBaseMatch 39 | 40 | } 41 | 42 | public var startIndex: Index { .baseMatch } 43 | 44 | public var endIndex: Index { matchesOfAssertedPattern.isEmpty ? .afterBaseMatch : .baseMatch } 45 | 46 | public subscript (index: Index) -> Match { 47 | guard matchesOfAssertedPattern.isEmpty else { preconditionFailure("Index out of bounds: negated assertion does not hold") } 48 | return baseMatch 49 | } 50 | 51 | public func index(before index: Index) -> Index { 52 | switch index { 53 | case .baseMatch: preconditionFailure("Index out of bounds") 54 | case .afterBaseMatch: return .baseMatch 55 | } 56 | } 57 | 58 | public func index(after index: Index) -> Index { 59 | switch index { 60 | case .baseMatch: return .afterBaseMatch 61 | case .afterBaseMatch: preconditionFailure("Index out of bounds") 62 | } 63 | } 64 | 65 | } 66 | 67 | extension NegatedBackwardAssertionMatchCollection.Index : Comparable { 68 | public static func <(leftIndex: Self, rightIndex: Self) -> Bool { 69 | (leftIndex, rightIndex) == (.baseMatch, .afterBaseMatch) 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /Sources/PatternKit/Assertion/Negated Backward Assertion.swift: -------------------------------------------------------------------------------- 1 | // PatternKit © 2017–21 Constantino Tsarouhas 2 | 3 | /// A pattern that asserts that an asserted pattern does *not* match the part of the subject preceding the input position; also known as a negative lookbehind. 4 | /// 5 | /// A negated assertion does not change the input position nor does it preserve any captures from tokens within the asserted pattern. Tokens within the asserted pattern essentially do not affect matching outside of the assertion, but can be used for reference patterns also contained within the assertion. 6 | public struct NegatedBackwardAssertion { 7 | 8 | public typealias Subject = AssertedPattern.Subject 9 | 10 | /// Creates a negated forward assertion. 11 | /// 12 | /// - Parameter assertedPattern: The pattern that must produce at least one match for the assertion to hold. 13 | public init(_ assertedPattern: AssertedPattern) { 14 | self.assertedPattern = assertedPattern 15 | } 16 | 17 | /// The pattern that must not produce a match for the negated assertion to hold. 18 | public var assertedPattern: AssertedPattern 19 | 20 | } 21 | 22 | extension NegatedBackwardAssertion : Pattern { 23 | 24 | public func forwardMatches(enteringFrom base: Match) -> NegatedBackwardAssertionMatchCollection { 25 | .init(assertedPattern: assertedPattern, baseMatch: base) 26 | } 27 | 28 | public func backwardMatches(recedingFrom base: Match) -> NegatedBackwardAssertionMatchCollection { 29 | .init(assertedPattern: assertedPattern, baseMatch: base) 30 | } 31 | 32 | } 33 | 34 | extension NegatedBackwardAssertion : BidirectionalCollection { 35 | 36 | public enum Index : Int, Hashable { 37 | 38 | /// The position of the asserted pattern. 39 | case assertedPattern = 0 40 | 41 | /// The past-the-end position. 42 | case end 43 | 44 | } 45 | 46 | public var startIndex: Index { .assertedPattern } 47 | 48 | public var endIndex: Index { .end } 49 | 50 | public subscript (index: Index) -> AssertedPattern { 51 | precondition(index == .assertedPattern, "Index out of bounds") 52 | return assertedPattern 53 | } 54 | 55 | public func index(before index: Index) -> Index { 56 | precondition(index == .end, "Index out of bounds") 57 | return .assertedPattern 58 | } 59 | 60 | public func index(after index: Index) -> Index { 61 | precondition(index == .assertedPattern, "Index out of bounds") 62 | return .end 63 | } 64 | 65 | } 66 | 67 | extension NegatedBackwardAssertion.Index : Comparable { 68 | public static func <(leftIndex: Self, rightIndex: Self) -> Bool { 69 | leftIndex.rawValue < rightIndex.rawValue 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /Sources/PatternKit/Assertion/Negated Forward Assertion Match Collection.swift: -------------------------------------------------------------------------------- 1 | // PatternKit © 2017–21 Constantino Tsarouhas 2 | 3 | public struct NegatedForwardAssertionMatchCollection { 4 | 5 | public typealias Subject = AssertedPattern.Subject 6 | 7 | /// Creates a negated forward assertion match collection. 8 | /// 9 | /// - Parameter assertedPattern: The pattern that must produce no match for the assertion to hold. 10 | /// - Parameter baseMatch: The base match. 11 | internal init(assertedPattern: AssertedPattern, baseMatch: Match) { 12 | self.assertedPattern = assertedPattern 13 | self.matchesOfAssertedPattern = assertedPattern.forwardMatches(enteringFrom: baseMatch) 14 | self.baseMatch = baseMatch 15 | } 16 | 17 | /// The pattern that must produce no match for the assertion to hold. 18 | public let assertedPattern: AssertedPattern 19 | 20 | /// The matches of the asserted pattern. 21 | /// 22 | /// If empty, the negated assertion match collection contains the base match. Otherwise, it is empty. 23 | public let matchesOfAssertedPattern: AssertedPattern.ForwardMatchCollection 24 | 25 | /// The base match. 26 | public let baseMatch: Match 27 | 28 | } 29 | 30 | extension NegatedForwardAssertionMatchCollection : BidirectionalCollection { 31 | 32 | public enum Index : Equatable { 33 | 34 | /// The position of the base match if the negated assertion holds, otherwise the end index of the collection. 35 | case baseMatch 36 | 37 | /// The position after the base match of the collection if the negated assertion holds, otherwise an invalid index. 38 | case afterBaseMatch 39 | 40 | } 41 | 42 | public var startIndex: Index { .baseMatch } 43 | 44 | public var endIndex: Index { matchesOfAssertedPattern.isEmpty ? .afterBaseMatch : .baseMatch } 45 | 46 | public subscript (index: Index) -> Match { 47 | guard matchesOfAssertedPattern.isEmpty else { preconditionFailure("Index out of bounds: negated assertion does not hold") } 48 | return baseMatch 49 | } 50 | 51 | public func index(before index: Index) -> Index { 52 | switch index { 53 | case .baseMatch: preconditionFailure("Index out of bounds") 54 | case .afterBaseMatch: return .baseMatch 55 | } 56 | } 57 | 58 | public func index(after index: Index) -> Index { 59 | switch index { 60 | case .baseMatch: return .afterBaseMatch 61 | case .afterBaseMatch: preconditionFailure("Index out of bounds") 62 | } 63 | } 64 | 65 | } 66 | 67 | extension NegatedForwardAssertionMatchCollection.Index : Comparable { 68 | public static func <(leftIndex: Self, rightIndex: Self) -> Bool { 69 | (leftIndex, rightIndex) == (.baseMatch, .afterBaseMatch) 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /Sources/PatternKit/Assertion/Negated Forward Assertion.swift: -------------------------------------------------------------------------------- 1 | // PatternKit © 2017–21 Constantino Tsarouhas 2 | 3 | /// A pattern that asserts that an asserted pattern does *not* match the part of the subject following the input position; also known as a negative lookahead. 4 | /// 5 | /// For example, `NegatedForwardAssertion(Repeating(1...9, min: 5)) • Literal([1, 2, 3]) • any()+` matches all arrays starting with the elements 1, 2, and 3 that do not start with 5 elements between 1 and 9. 6 | /// 7 | /// A negated assertion does not change the input position nor does it preserve any captures from tokens within the asserted pattern. Tokens within the asserted pattern essentially do not affect matching outside of the assertion, but can be used for reference patterns also contained within the assertion. 8 | public struct NegatedForwardAssertion { 9 | 10 | public typealias Subject = AssertedPattern.Subject 11 | 12 | /// Creates a negated forward assertion. 13 | /// 14 | /// - Parameter assertedPattern: The pattern that must produce at least one match for the assertion to hold. 15 | public init(_ assertedPattern: AssertedPattern) { 16 | self.assertedPattern = assertedPattern 17 | } 18 | 19 | /// The pattern that must not produce a match for the negated assertion to hold. 20 | public var assertedPattern: AssertedPattern 21 | 22 | } 23 | 24 | extension NegatedForwardAssertion : Pattern { 25 | 26 | public func forwardMatches(enteringFrom base: Match) -> NegatedForwardAssertionMatchCollection { 27 | .init(assertedPattern: assertedPattern, baseMatch: base) 28 | } 29 | 30 | public func backwardMatches(recedingFrom base: Match) -> NegatedForwardAssertionMatchCollection { 31 | .init(assertedPattern: assertedPattern, baseMatch: base) 32 | } 33 | 34 | } 35 | 36 | extension NegatedForwardAssertion : BidirectionalCollection { 37 | 38 | public enum Index : Int, Hashable { 39 | 40 | /// The position of the asserted pattern. 41 | case assertedPattern = 0 42 | 43 | /// The past-the-end position. 44 | case end 45 | 46 | } 47 | 48 | public var startIndex: Index { .assertedPattern } 49 | 50 | public var endIndex: Index { .end } 51 | 52 | public subscript (index: Index) -> AssertedPattern { 53 | precondition(index == .assertedPattern, "Index out of bounds") 54 | return assertedPattern 55 | } 56 | 57 | public func index(before index: Index) -> Index { 58 | precondition(index == .end, "Index out of bounds") 59 | return .assertedPattern 60 | } 61 | 62 | public func index(after index: Index) -> Index { 63 | precondition(index == .assertedPattern, "Index out of bounds") 64 | return .end 65 | } 66 | 67 | } 68 | 69 | extension NegatedForwardAssertion.Index : Comparable { 70 | public static func <(leftIndex: Self, rightIndex: Self) -> Bool { 71 | leftIndex.rawValue < rightIndex.rawValue 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /Sources/PatternKit/Bin.swift: -------------------------------------------------------------------------------- 1 | // PatternKit © 2017–21 Constantino Tsarouhas 2 | 3 | /// A pattern that never produces matches. 4 | /// 5 | /// Use a bin pattern on paths that are to be discarded early, e.g., for performance reasons. 6 | public struct Bin where Subject.Element : Equatable {} 7 | 8 | extension Bin : Pattern { 9 | 10 | public func forwardMatches(enteringFrom base: Match) -> EmptyCollection> { 11 | .init() 12 | } 13 | 14 | public func backwardMatches(recedingFrom base: Match) -> EmptyCollection> { 15 | .init() 16 | } 17 | 18 | } 19 | 20 | extension Bin : ExpressibleByNilLiteral { 21 | public init(nilLiteral: ()) { 22 | self.init() 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Sources/PatternKit/Character Set/Character Set Operators.swift: -------------------------------------------------------------------------------- 1 | // PatternKit © 2017–21 Constantino Tsarouhas 2 | 3 | import Foundation 4 | 5 | /// Returns a character set containing a range of Unicode scalars. 6 | /// 7 | /// - Parameter firstScalar: The first scalar in the range. 8 | /// - Parameter lastScalar: The last scalar in the range, inclusive. 9 | /// 10 | /// - Returns: `CharacterSet(charactersIn: firstScalar...lastScalar)` 11 | public func ... (firstScalar: UnicodeScalar, lastScalar: UnicodeScalar) -> CharacterSet { 12 | .init(charactersIn: firstScalar...lastScalar) 13 | } 14 | 15 | /// Returns the union of two character sets. 16 | /// 17 | /// - Parameter firstSet: The first set. 18 | /// - Parameter secondSet: The second set. 19 | /// 20 | /// - Returns: `firstSet.union(secondSet)` 21 | public func | (firstSet: CharacterSet, secondSet: CharacterSet) -> CharacterSet { 22 | firstSet.union(secondSet) 23 | } 24 | 25 | /// Returns the union of a character set with a Unicode scalar. 26 | /// 27 | /// - Parameter set: The set. 28 | /// - Parameter scalar: The scalar. 29 | /// 30 | /// - Returns: A superset of `set` such that `set.contains(scalar)`. 31 | public func | (set: CharacterSet, scalar: UnicodeScalar) -> CharacterSet { 32 | var set = set 33 | set.insert(scalar) 34 | return set 35 | } 36 | 37 | /// Returns the union of a character set with a Unicode scalar. 38 | /// 39 | /// - Parameter scalar: The scalar. 40 | /// - Parameter set: The set. 41 | /// 42 | /// - Returns: A superset of `set` such that `set.contains(scalar)`. 43 | public func | (scalar: UnicodeScalar, set: CharacterSet) -> CharacterSet { 44 | var set = set 45 | set.insert(scalar) 46 | return set 47 | } 48 | 49 | /// Returns a character set containing two Unicode scalars. 50 | /// 51 | /// - Parameter firstScalar: The first scalar. 52 | /// - Parameter secondScalar: The second scalar. 53 | /// 54 | /// - Returns: `CharacterSet([firstScalar, secondScalar])` 55 | public func | (firstScalar: UnicodeScalar, secondScalar: UnicodeScalar) -> CharacterSet { 56 | .init([firstScalar, secondScalar]) 57 | } 58 | -------------------------------------------------------------------------------- /Sources/PatternKit/Character Set/Character Set.swift: -------------------------------------------------------------------------------- 1 | // PatternKit © 2017–21 Constantino Tsarouhas 2 | 3 | import Foundation 4 | 5 | extension CharacterSet : Pattern { 6 | 7 | public func forwardMatches(enteringFrom base: Match) -> SingularMatchCollection { 8 | 9 | guard let character = base.remainingElements(direction: .forward).first else { return nil } 10 | 11 | let scalars = character.unicodeScalars 12 | guard let scalar = scalars.first, scalars.count == 1, contains(scalar) else { return nil } 13 | 14 | return .init(resultMatch: base.movingInputPosition(distance: 1, direction: .forward)) 15 | 16 | } 17 | 18 | public func backwardMatches(recedingFrom base: Match) -> SingularMatchCollection { 19 | 20 | guard let character = base.remainingElements(direction: .backward).last else { return nil } 21 | 22 | let scalars = character.unicodeScalars 23 | guard let scalar = scalars.first, scalars.count == 1, contains(scalar) else { return nil } 24 | 25 | return .init(resultMatch: base.movingInputPosition(distance: 1, direction: .backward)) 26 | 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /Sources/PatternKit/Concatenation/Concatenation Operators.swift: -------------------------------------------------------------------------------- 1 | // PatternKit © 2017–21 Constantino Tsarouhas 2 | 3 | /// The pattern concatenation operator. 4 | /// 5 | /// Concatentation doesn't use `+` to minimise type inference performance problems as well as to avoid making code harder to read when combined with the `+` postfix operator for nonoptional eagerly repeating patterns. 6 | infix operator • : AdditionPrecedence 7 | 8 | /// Returns a concatenation of two arbitrary patterns. 9 | /// 10 | /// - Parameter leadingPattern: The pattern that matches the first part of the concatenation. 11 | /// - Parameter trailingPattern: The pattern that matches the part after the part matched by the leading pattern. 12 | /// 13 | /// - Returns: `Concatenation(leadingPattern, trailingPattern)` 14 | public func • (leadingPattern: L, trailingPattern: R) -> Concatenation { 15 | .init(leadingPattern, trailingPattern) 16 | } 17 | 18 | /// Returns a concatenation of an arbitrary pattern with a literal pattern. 19 | /// 20 | /// This overload is defined to guide the type checker into allowing for (Swift) literals to be used in concatenation patterns, e.g., `somePattern • "this is a literal"`. `Literal` conforms to the `ExpressibleByStringLiteral` protocol but the compiler won't even consider that conformance if it cannot find an overload that specifies `Literal` (instead of just `Pattern`). 21 | /// 22 | /// - Parameter leadingPattern: The pattern that matches the first part of the concatenation. 23 | /// - Parameter trailingPattern: The pattern that matches the part after the part matched by the leading pattern. 24 | /// 25 | /// - Returns: `Concatenation(leadingPattern, trailingPattern)` 26 | public func • (leadingPattern: L, trailingPattern: Literal) -> Concatenation> { 27 | .init(leadingPattern, trailingPattern) 28 | } 29 | 30 | /// Returns a concatenation of a literal pattern with an arbitrary pattern. 31 | /// 32 | /// This overload is defined to guide the type checker into allowing for (Swift) literals to be used in concatenation patterns, e.g., `"this is a literal" • somePattern`. `Literal` conforms to the `ExpressibleByStringLiteral` protocol but the compiler won't even consider that conformance if it cannot find an overload that specifies `Literal` (instead of just `Pattern`). 33 | /// 34 | /// - Parameter leadingPattern: The pattern that matches the first part of the concatenation. 35 | /// - Parameter trailingPattern: The pattern that matches the part after the part matched by the leading pattern. 36 | /// 37 | /// - Returns: `Concatenation(leadingPattern, trailingPattern)` 38 | public func • (leadingPattern: Literal, trailingPattern: R) -> Concatenation, R> { 39 | .init(leadingPattern, trailingPattern) 40 | } 41 | 42 | /// Returns a concatenation of two literal patterns. 43 | /// 44 | /// This overload optimises the concatenation by merging the adjacent literals instead of creating a new concatenation construct over both literals. 45 | /// 46 | /// - Parameter leadingLiteral: The literal pattern that matches the first part of the concatenation. 47 | /// - Parameter trailingPattern: The literal pattern that matches the part after the part matched by the leading literal. 48 | /// 49 | /// - Returns: A literal formed by concatenating `leadingLiteral` and `trailingLiteral`. 50 | public func • (leadingLiteral: Literal, trailingLiteral: Literal) -> Literal { 51 | .init(leadingLiteral.literal.appending(contentsOf: trailingLiteral.literal)) 52 | } 53 | 54 | /// Returns a concatenation of a literal pattern with a concatenation with a leading literal pattern. 55 | /// 56 | /// This overload optimises the resulting concatenation by merging the adjacent literals. 57 | /// 58 | /// - Parameter leadingLiteral: The literal pattern that matches the first part of the encompassing concatenation. 59 | /// - Parameter trailingConcatenation: The concatenation pattern that matches the part after the part matched by the leading literal. 60 | /// 61 | /// - Returns: A concatenation formed by prepending `leadingLiteral` to `trailingConcatenation`. 62 | public func • (leadingLiteral: Literal, trailingConcatenation: Concatenation, P>) -> Concatenation, P> { 63 | let newLiteral = leadingLiteral.literal.appending(contentsOf: trailingConcatenation.leadingPattern.literal) 64 | return .init(Literal(newLiteral), trailingConcatenation.trailingPattern) 65 | } 66 | 67 | /// Returns a concatenation of a concatenation (with a trailing literal pattern) with another literal pattern. 68 | /// 69 | /// This overload optimises the resulting concatenation by merging the adjacent literals. 70 | /// 71 | /// - Parameter leadingConcatenation: The concatenation pattern that matches the first part of the encompassing concatenation. 72 | /// - Parameter trailingPattern: The literal pattern that matches the part after the part matched by the leading concatenation pattern. 73 | /// 74 | /// - Returns: A concatenation formed by appending `trailingLiteral` to `leadingConcatenation`. 75 | public func • (leadingConcatenation: Concatenation>, trailingLiteral: Literal) -> Concatenation> { 76 | let newLiteral = leadingConcatenation.trailingPattern.literal.appending(contentsOf: trailingLiteral.literal) 77 | return .init(leadingConcatenation.leadingPattern, Literal(newLiteral)) 78 | } 79 | -------------------------------------------------------------------------------- /Sources/PatternKit/Concatenation/Concatenation.swift: -------------------------------------------------------------------------------- 1 | // PatternKit © 2017–21 Constantino Tsarouhas 2 | 3 | import DepthKit 4 | 5 | /// A pattern that matches two patterns sequentially. 6 | public struct Concatenation where 7 | 8 | LeadingPattern.Subject == TrailingPattern.Subject, 9 | 10 | LeadingPattern.ForwardMatchCollection.Indices : OrderedCollection, 11 | TrailingPattern.ForwardMatchCollection.Indices : OrderedCollection, 12 | 13 | LeadingPattern.BackwardMatchCollection.Indices : OrderedCollection, 14 | TrailingPattern.BackwardMatchCollection.Indices : OrderedCollection { 15 | 16 | public typealias Subject = LeadingPattern.Subject 17 | 18 | /// Creates a concatenation. 19 | /// 20 | /// - Parameter leadingPattern: The pattern that matches the first part of the concatenation. 21 | /// - Parameter trailingPattern: The pattern that matches the part after the part matched by the leading pattern. 22 | public init(_ leadingPattern: LeadingPattern, _ trailingPattern: TrailingPattern) { 23 | self.leadingPattern = leadingPattern 24 | self.trailingPattern = trailingPattern 25 | } 26 | 27 | /// The pattern that matches the first part of the concatenation. 28 | public var leadingPattern: LeadingPattern 29 | 30 | /// The pattern that matches the part after the part matched by the leading pattern. 31 | public var trailingPattern: TrailingPattern 32 | 33 | } 34 | 35 | extension Concatenation : Pattern { 36 | 37 | public func forwardMatches(enteringFrom base: Match) -> ForwardConcatenationMatchCollection { 38 | .init(leadingPattern: leadingPattern, trailingPattern: trailingPattern, baseMatch: base) 39 | } 40 | 41 | public func backwardMatches(recedingFrom base: Match) -> BackwardConcatenationMatchCollection { 42 | .init(leadingPattern: leadingPattern, trailingPattern: trailingPattern, baseMatch: base) 43 | } 44 | 45 | public func underestimatedSmallestInputPositionForForwardMatching(on subject: Subject, fromIndex inputPosition: Subject.Index) -> Subject.Index { 46 | leadingPattern.underestimatedSmallestInputPositionForForwardMatching(on: subject, fromIndex: inputPosition) 47 | } 48 | 49 | public func overestimatedLargestInputPositionForBackwardMatching(on subject: Subject, fromIndex inputPosition: Subject.Index) -> Subject.Index { 50 | trailingPattern.overestimatedLargestInputPositionForBackwardMatching(on: subject, fromIndex: inputPosition) 51 | } 52 | 53 | } 54 | 55 | extension Concatenation : BidirectionalCollection { 56 | 57 | public enum Index : Int, Hashable { 58 | 59 | /// The position of the leading pattern. 60 | case leadingPattern = 0 61 | 62 | /// The position of the trailing pattern. 63 | case trailingPattern 64 | 65 | /// The past-the-end position. 66 | case end 67 | 68 | } 69 | 70 | public enum Element { 71 | 72 | /// The leading pattern. 73 | case leadingPattern(LeadingPattern) 74 | 75 | /// The trailing pattern. 76 | case trailingPattern(TrailingPattern) 77 | 78 | } 79 | 80 | public var startIndex: Index { .leadingPattern } 81 | 82 | public var endIndex: Index { .end } 83 | 84 | public subscript (index: Index) -> Element { 85 | switch index { 86 | case .leadingPattern: return .leadingPattern(leadingPattern) 87 | case .trailingPattern: return .trailingPattern(trailingPattern) 88 | case .end: preconditionFailure("Index out of bounds") 89 | } 90 | } 91 | 92 | public func index(before index: Index) -> Index { 93 | switch index { 94 | case .leadingPattern: preconditionFailure("Index out of bounds") 95 | case .trailingPattern: return .leadingPattern 96 | case .end: return .trailingPattern 97 | } 98 | } 99 | 100 | public func index(after index: Index) -> Index { 101 | switch index { 102 | case .leadingPattern: return .trailingPattern 103 | case .trailingPattern: return .end 104 | case .end: preconditionFailure("Index out of bounds") 105 | } 106 | } 107 | 108 | } 109 | 110 | extension Concatenation.Index : Comparable { 111 | public static func <(leftIndex: Self, rightIndex: Self) -> Bool { 112 | leftIndex.rawValue < rightIndex.rawValue 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /Sources/PatternKit/Concatenation/Homogeneous/Backward Homogeneous Concatenation Match Collection.swift: -------------------------------------------------------------------------------- 1 | // PatternKit © 2017–21 Constantino Tsarouhas 2 | 3 | /// A collection of backward matches of a homogeneous concatenation pattern. 4 | public struct BackwardHomogeneousConcatenationMatchCollection { 5 | 6 | public typealias Subject = Subpattern.Subject 7 | 8 | /// Creates a homogeneous concatenation match collection. 9 | /// 10 | /// - Parameter subpatterns: The subpatterns. 11 | /// - Parameter baseMatch: The base match. 12 | internal init(subpatterns: [Subpattern], baseMatch: Match) { 13 | assert(subpatterns.count >= 2) 14 | self.subpatterns = subpatterns 15 | self.baseMatch = baseMatch 16 | } 17 | 18 | /// The subpatterns of the concatenation. 19 | /// 20 | /// - Invariant: `subpatterns` contains at least two subpatterns. 21 | public let subpatterns: [Subpattern] 22 | 23 | /// The base match. 24 | public let baseMatch: Match 25 | 26 | } 27 | 28 | extension BackwardHomogeneousConcatenationMatchCollection : BidirectionalCollection { 29 | 30 | public enum Index : Equatable { 31 | 32 | /// A position to a valid match. 33 | /// 34 | /// - Invariant: No index in `indicesBySubpattern` is not equal to `endIndex` of their respective subpattern's match collection. 35 | /// - Invariant: The number of indices is equal to the number of subpatterns. 36 | /// 37 | /// - Parameter indicesBySubpattern: The indices within the respective subpatterns. 38 | case some(indicesBySubpattern: [Subpattern.BackwardMatchCollection.Index]) 39 | 40 | /// The position after the last element of the match collection. 41 | case end 42 | 43 | } 44 | 45 | public var startIndex: Index { 46 | TODO.unimplemented 47 | } 48 | 49 | public var endIndex: Index { 50 | TODO.unimplemented 51 | } 52 | 53 | public subscript (index: Index) -> Match { 54 | TODO.unimplemented 55 | } 56 | 57 | public func index(before index: Index) -> Index { 58 | TODO.unimplemented 59 | } 60 | 61 | public func index(after index: Index) -> Index { 62 | TODO.unimplemented 63 | } 64 | 65 | } 66 | 67 | extension BackwardHomogeneousConcatenationMatchCollection.Index : Comparable { 68 | public static func <(lhs: Self, rhs: Self) -> Bool { 69 | TODO.unimplemented 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /Sources/PatternKit/Concatenation/Homogeneous/Forward Homogeneous Concatenation Match Collection.swift: -------------------------------------------------------------------------------- 1 | // PatternKit © 2017–21 Constantino Tsarouhas 2 | 3 | /// A collection of forward matches of a homogeneous concatenation pattern. 4 | public struct ForwardHomogeneousConcatenationMatchCollection { 5 | 6 | public typealias Subject = Subpattern.Subject 7 | 8 | /// Creates a homogeneous concatenation match collection. 9 | /// 10 | /// - Parameter subpatterns: The subpatterns. 11 | /// - Parameter baseMatch: The base match. 12 | internal init(subpatterns: [Subpattern], baseMatch: Match) { 13 | assert(subpatterns.count >= 2) 14 | self.subpatterns = subpatterns 15 | self.baseMatch = baseMatch 16 | } 17 | 18 | /// The subpatterns of the concatenation. 19 | /// 20 | /// - Invariant: `subpatterns` contains at least two subpatterns. 21 | public let subpatterns: [Subpattern] 22 | 23 | /// The base match. 24 | public let baseMatch: Match 25 | 26 | } 27 | 28 | extension ForwardHomogeneousConcatenationMatchCollection : BidirectionalCollection { 29 | 30 | public enum Index : Equatable { 31 | 32 | /// A position to a valid match. 33 | /// 34 | /// - Invariant: No index in `indicesBySubpattern` is not equal to `endIndex` of their respective subpattern's match collection. 35 | /// - Invariant: The number of indices is equal to the number of subpatterns. 36 | /// 37 | /// - Parameter indicesBySubpattern: The indices within the respective subpatterns. 38 | case some(indicesBySubpattern: [Subpattern.ForwardMatchCollection.Index]) 39 | 40 | /// The position after the last element of the match collection. 41 | case end 42 | 43 | } 44 | 45 | public var startIndex: Index { 46 | TODO.unimplemented 47 | } 48 | 49 | public var endIndex: Index { 50 | TODO.unimplemented 51 | } 52 | 53 | public subscript (index: Index) -> Match { 54 | TODO.unimplemented 55 | } 56 | 57 | public func index(before index: Index) -> Index { 58 | TODO.unimplemented 59 | } 60 | 61 | public func index(after index: Index) -> Index { 62 | TODO.unimplemented 63 | } 64 | 65 | } 66 | 67 | extension ForwardHomogeneousConcatenationMatchCollection.Index : Comparable { 68 | public static func <(lhs: Self, rhs: Self) -> Bool { 69 | TODO.unimplemented 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /Sources/PatternKit/Concatenation/Homogeneous/Homogeneous Concatenation.swift: -------------------------------------------------------------------------------- 1 | // PatternKit © 2017–21 Constantino Tsarouhas 2 | 3 | /// A pattern that matches a series of same-typed patterns sequentially. 4 | public struct HomogeneousConcatenation { 5 | 6 | public typealias Subject = Subpattern.Subject 7 | 8 | /// Creates a homogeneous concatenation pattern with given subpatterns. 9 | /// 10 | /// - Parameter firstPattern: The first subpattern. 11 | /// - Parameter otherPatterns: The other subpatterns. 12 | public init(_ firstPattern: Subpattern, _ otherPatterns: Subpattern...) { 13 | self.subpatterns = [firstPattern] + otherPatterns 14 | } 15 | 16 | /// Creates a homogeneous concatenation pattern with given subpatterns. 17 | /// 18 | /// - Requires: `subpatterns` contains at least two subpatterns. 19 | /// 20 | /// - Parameter subpatterns: The subpatterns. 21 | public init(_ subpatterns: [Subpattern]) { 22 | precondition(subpatterns.count >= 2, "Fewer than 2 subpatterns in concatenation") 23 | self.subpatterns = subpatterns 24 | } 25 | 26 | /// The subpatterns, in order of matching. 27 | /// 28 | /// - Invariant: `subpatterns` contains at least two subpatterns. 29 | public var subpatterns: [Subpattern] { 30 | willSet { precondition(newValue.count >= 2, "Fewer than 2 subpatterns in concatenation") } 31 | } 32 | 33 | } 34 | 35 | extension HomogeneousConcatenation : Pattern { 36 | 37 | public func forwardMatches(enteringFrom base: Match) -> ForwardHomogeneousConcatenationMatchCollection { 38 | .init(subpatterns: subpatterns, baseMatch: base) 39 | } 40 | 41 | public func backwardMatches(recedingFrom base: Match) -> BackwardHomogeneousConcatenationMatchCollection { 42 | .init(subpatterns: subpatterns, baseMatch: base) 43 | } 44 | 45 | public func underestimatedSmallestInputPositionForForwardMatching(on subject: Subpattern.Subject, fromIndex inputPosition: Subpattern.Subject.Index) -> Subpattern.Subject.Index { 46 | subpatterns.first!.underestimatedSmallestInputPositionForForwardMatching(on: subject, fromIndex: inputPosition) 47 | } 48 | 49 | public func overestimatedLargestInputPositionForBackwardMatching(on subject: Subpattern.Subject, fromIndex inputPosition: Subpattern.Subject.Index) -> Subpattern.Subject.Index { 50 | subpatterns.last!.overestimatedLargestInputPositionForBackwardMatching(on: subject, fromIndex: inputPosition) 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /Sources/PatternKit/Exports.swift: -------------------------------------------------------------------------------- 1 | // PatternKit © 2017–21 Constantino Tsarouhas 2 | 3 | @_exported import PatternKitCore 4 | -------------------------------------------------------------------------------- /Sources/PatternKit/Literal.swift: -------------------------------------------------------------------------------- 1 | // PatternKit © 2017–21 Constantino Tsarouhas 2 | 3 | /// A pattern that matches an exact subcollection. 4 | public struct Literal where Subject.Element : Equatable { 5 | 6 | /// Creates a pattern that matches an exact collection. 7 | /// 8 | /// - Parameter literal: The collection that the pattern matches exactly. 9 | public init(_ literal: Subject) { 10 | self.literal = literal 11 | } 12 | 13 | /// The collection that the pattern matches exactly. 14 | public var literal: Subject 15 | 16 | } 17 | 18 | extension Literal : Pattern { 19 | 20 | public func forwardMatches(enteringFrom base: Match) -> SingularMatchCollection { 21 | guard base.remainingElements(direction: .forward).starts(with: literal) else { return nil } 22 | return SingularMatchCollection(resultMatch: base.movingInputPosition(distance: literal.count, direction: .forward)) 23 | } 24 | 25 | public func backwardMatches(recedingFrom base: Match) -> SingularMatchCollection { 26 | guard base.remainingElements(direction: .backward).ends(with: literal) else { return nil } 27 | return SingularMatchCollection(resultMatch: base.movingInputPosition(distance: literal.count, direction: .backward)) 28 | } 29 | 30 | // TODO: Potentially optimise by implementing the heuristic functions, using an efficient substring finding algorithm 31 | 32 | } 33 | 34 | extension Literal where Subject : RangeReplaceableCollection { 35 | 36 | /// Creates a literal pattern over some elements. 37 | /// 38 | /// - Parameter elements: The elements to match. 39 | public init(_ elements: Subject.Element...) { 40 | self.init(Subject(elements)) 41 | } 42 | 43 | } 44 | 45 | extension Literal : ExpressibleByUnicodeScalarLiteral where Subject : ExpressibleByUnicodeScalarLiteral { 46 | public typealias UnicodeScalarLiteralType = Subject.UnicodeScalarLiteralType 47 | public init(unicodeScalarLiteral: Subject.UnicodeScalarLiteralType) { 48 | self.init(Subject(unicodeScalarLiteral: unicodeScalarLiteral)) 49 | } 50 | } 51 | 52 | extension Literal : ExpressibleByExtendedGraphemeClusterLiteral where Subject : ExpressibleByExtendedGraphemeClusterLiteral { 53 | public typealias ExtendedGraphemeClusterLiteralType = Subject.ExtendedGraphemeClusterLiteralType 54 | public init(extendedGraphemeClusterLiteral: Subject.ExtendedGraphemeClusterLiteralType) { 55 | self.init(Subject(extendedGraphemeClusterLiteral: extendedGraphemeClusterLiteral)) 56 | } 57 | } 58 | 59 | extension Literal : ExpressibleByStringLiteral where Subject : ExpressibleByStringLiteral { 60 | public typealias StringLiteralType = Subject.StringLiteralType 61 | public init(stringLiteral: Subject.StringLiteralType) { 62 | self.init(Subject(stringLiteral: stringLiteral)) 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /Sources/PatternKit/Range Pattern/Range Pattern Operators.swift: -------------------------------------------------------------------------------- 1 | // PatternKit © 2017–21 Constantino Tsarouhas 2 | 3 | /// Creates a range pattern. 4 | /// 5 | /// - Parameter lowerBound: The smallest element that is matched. 6 | /// - Parameter upperBound: The element greater than the largest element that is matched. 7 | /// 8 | /// - Returns: A range pattern matching elements between `lowerBound` and `upperBound`, exclusive. 9 | public func ..< (lowerBound: Subject.Element, upperBound: Subject.Element) -> RangePattern { 10 | .init(lowerBound..(lowerBound: Subject.Element, upperBound: Subject.Element) -> RangePattern { 20 | .init(lowerBound...upperBound) 21 | } 22 | -------------------------------------------------------------------------------- /Sources/PatternKit/Range Pattern/Range Pattern.swift: -------------------------------------------------------------------------------- 1 | // PatternKit © 2017–21 Constantino Tsarouhas 2 | 3 | /// A pattern that matches any one element that is contained in a range. 4 | public struct RangePattern where Subject.Element : Comparable { 5 | 6 | /// Creates a range pattern. 7 | /// 8 | /// - Parameter range: The range of elements that are matched by the range pattern. 9 | public init(_ range: Swift.Range) { 10 | self.range = .halfOpen(range) 11 | } 12 | 13 | /// Creates a range pattern. 14 | /// 15 | /// - Parameter range: The range of elements that are matched by the range pattern. 16 | public init(_ range: ClosedRange) { 17 | self.range = .closed(range) 18 | } 19 | 20 | /// The range of elements that are matched by the range pattern. 21 | public var range: Range 22 | public indirect enum Range { 23 | case halfOpen(Swift.Range) 24 | case closed(ClosedRange) 25 | } 26 | 27 | } 28 | 29 | extension RangePattern : Pattern { 30 | 31 | public func forwardMatches(enteringFrom base: Match) -> SingularMatchCollection { 32 | 33 | guard let element = base.remainingElements(direction: .forward).first else { return nil } 34 | 35 | switch range { 36 | case .halfOpen(let range): guard range.contains(element) else { return nil } 37 | case .closed(let range): guard range.contains(element) else { return nil } 38 | } 39 | 40 | return .init(resultMatch: base.movingInputPosition(distance: 1, direction: .forward)) 41 | 42 | } 43 | 44 | public func backwardMatches(recedingFrom base: Match) -> SingularMatchCollection { 45 | 46 | guard let element = base.remainingElements(direction: .backward).last else { return nil } 47 | 48 | switch range { 49 | case .halfOpen(let range): guard range.contains(element) else { return nil } 50 | case .closed(let range): guard range.contains(element) else { return nil } 51 | } 52 | 53 | return .init(resultMatch: base.movingInputPosition(distance: 1, direction: .backward)) 54 | 55 | } 56 | 57 | } 58 | -------------------------------------------------------------------------------- /Sources/PatternKit/Repeating/Backward Ring.swift: -------------------------------------------------------------------------------- 1 | // PatternKit © 2017–21 Constantino Tsarouhas 2 | 3 | /// An iteration of a repeatedly backward-matching pattern. 4 | /// 5 | /// A ring takes a base match and produces a successor ring for every match produced by the repeated pattern given the base match. The successor rings themselves can be used to generate further descendant rings. Given a *root* ring, one can determine a tree of ring and thus of matches. 6 | public struct BackwardRing { 7 | 8 | public typealias Subject = RepeatedPattern.Subject 9 | public typealias MatchCollection = RepeatedPattern.BackwardMatchCollection 10 | 11 | /// Creates a ring. 12 | /// 13 | /// - Requires: `depth >= 0` 14 | /// 15 | /// - Parameter repeatedPattern: The pattern that is being repeatedly matched. 16 | /// - Parameter baseMatch: The match from which successor matches are generated. 17 | /// - Parameter depth: The number of rings that precede this ring. The ring built from the origin match has depth 0. The default is zero. 18 | public init(repeatedPattern: RepeatedPattern, baseMatch: Match, depth: Int = 0) { 19 | self.repeatedPattern = repeatedPattern 20 | self.baseMatch = baseMatch 21 | self.successorMatches = repeatedPattern.backwardMatches(recedingFrom: baseMatch) 22 | self.depth = depth 23 | } 24 | 25 | /// The pattern that is being repeatedly matched. 26 | public let repeatedPattern: RepeatedPattern 27 | 28 | /// The match from which successor matches are generated. 29 | public let baseMatch: Match 30 | 31 | /// The matches that are the result of applying the repeated pattern using the base match. 32 | public let successorMatches: MatchCollection 33 | 34 | /// The rings that represent the next iteration, one for every successor match. 35 | public var successorRings: LazyMapCollection { 36 | let repeatedPattern = self.repeatedPattern 37 | let newDepth = depth + 1 38 | return successorMatches.lazy.map { successorMatch in 39 | .init(repeatedPattern: repeatedPattern, baseMatch: successorMatch, depth: newDepth) 40 | } 41 | } 42 | 43 | /// The number of rings that precede this ring. 44 | /// 45 | /// The ring built from the origin match has depth 0. 46 | /// 47 | /// - Invariant: `depth >= 0` 48 | public let depth: Int 49 | 50 | } 51 | 52 | extension BackwardRing : BidirectionalCollection { 53 | public typealias Index = MatchCollection.Index 54 | public var startIndex: Index { successorMatches.startIndex } 55 | public var endIndex: Index { successorMatches.endIndex } 56 | public subscript (index: Index) -> BackwardRing { successorRings[index] } 57 | public func index(before index: Index) -> Index { successorMatches.index(before: index) } 58 | public func index(after index: Index) -> Index { successorMatches.index(after: index) } 59 | } 60 | -------------------------------------------------------------------------------- /Sources/PatternKit/Repeating/Eagerly Repeating Operators.swift: -------------------------------------------------------------------------------- 1 | // PatternKit © 2017–21 Constantino Tsarouhas 2 | 3 | /// The unbounded Kleene operator with eager matching semantics. 4 | postfix operator * 5 | 6 | /// Returns an arbitrarily, eagerly repeating pattern over a given repeated pattern. 7 | /// 8 | /// - Parameter repeatedPattern: The pattern that is repeated. 9 | /// 10 | /// - Returns: An arbitrarily, eagerly repeating pattern over `repeatedPattern`. 11 | public postfix func *

(repeatedPattern: P) -> EagerlyRepeating

{ 12 | .init(repeatedPattern) 13 | } 14 | 15 | /// Returns an arbitrarily, eagerly repeating pattern over a given literal collection. 16 | /// 17 | /// - Parameter repeatedlyMatchedCollection: The collection that is repeatedly matched exactly. 18 | /// 19 | /// - Returns: An arbitrarily, eagerly repeating pattern over a literal pattern matching `repeatedlyMatchedCollection`. 20 | public postfix func * (repeatedlyMatchedCollection: C) -> EagerlyRepeating> { 21 | .init(Literal(repeatedlyMatchedCollection)) 22 | } 23 | 24 | 25 | 26 | /// The lower-bounded Kleene operator with eager matching semantics. 27 | postfix operator + 28 | 29 | /// Returns an arbitrarily, eagerly repeating pattern over a given repeated pattern that must match at least once. 30 | /// 31 | /// - Parameter repeatedPattern: The pattern that is repeated. 32 | /// 33 | /// - Returns: An arbitrarily, nonoptional, eagerly repeating pattern over `repeatedPattern`. 34 | public postfix func +

(repeatedPattern: P) -> EagerlyRepeating

{ 35 | .init(repeatedPattern, min: 1) 36 | } 37 | 38 | /// Returns an arbitrarily, eagerly repeating pattern over a given literal collection that must match at least once. 39 | /// 40 | /// - Parameter repeatedlyMatchedCollection: The collection that is repeatedly matched exactly. 41 | /// 42 | /// - Returns: An arbitrarily, nonoptional, eagerly repeating pattern over a literal pattern matching `repeatedlyMatchedCollection`. 43 | public postfix func + (repeatedlyMatchedCollection: C) -> EagerlyRepeating> { 44 | .init(.init(repeatedlyMatchedCollection), min: 1) 45 | } 46 | 47 | 48 | 49 | /// The optionality operator with eager matching semantics. 50 | postfix operator /? 51 | 52 | /// Returns a pattern that eagerly matches a given optional pattern. 53 | /// 54 | /// - Parameter optionalPattern: The pattern that is eagerly but optionally matched. 55 | /// 56 | /// - Returns: A pattern that optionally and eagerly matches `optionalPattern`. 57 | public postfix func /?

(optionalPattern: P) -> EagerlyRepeating

{ 58 | .init(optionalPattern, max: 1) 59 | } 60 | 61 | /// Returns a pattern that eagerly matches a given optional collection. 62 | /// 63 | /// - Parameter optionalCollection: The collection that is eagerly but optionally matched. 64 | /// 65 | /// - Returns: A pattern that optionally and eagerly matches the literal `optionalCollection`. 66 | public postfix func /? (optionalCollection: C) -> EagerlyRepeating> { 67 | .init(.init(optionalCollection), max: 1) 68 | } 69 | -------------------------------------------------------------------------------- /Sources/PatternKit/Repeating/Eagerly Repeating.swift: -------------------------------------------------------------------------------- 1 | // PatternKit © 2017–21 Constantino Tsarouhas 2 | 3 | import DepthKit 4 | 5 | /// A pattern that performs matching of a subpattern repeatedly on consecutive subsequences of the target collection, preferring matching as many times as possible. 6 | public struct EagerlyRepeating { 7 | 8 | public typealias Subject = RepeatedPattern.Subject 9 | 10 | /// Creates a repeating pattern. 11 | /// 12 | /// - Requires: `lowerBound >= 0` and `lowerBound <= upperBound` 13 | /// 14 | /// - Parameter repeatedPattern: The pattern that is repeated. 15 | /// - Parameter lowerBound: The minimal number of times the repeated pattern must be matched. The default is zero. 16 | /// - Parameter upperBound: The maximal number of times the repeated pattern may be matched. The default is `Int.max`. 17 | public init(_ repeatedPattern: RepeatedPattern, min lowerBound: Int = 0, max upperBound: Int = .max) { 18 | precondition(lowerBound >= 0, "Negative lower bound") 19 | self.repeatedPattern = repeatedPattern 20 | multiplicityRange = lowerBound...upperBound 21 | } 22 | 23 | /// The pattern that is repeated. 24 | public var repeatedPattern: RepeatedPattern 25 | 26 | /// The range of the number of times the pattern can be repeated. 27 | /// 28 | /// - Invariant: `multiplicityRange.lowerBound >= 0` 29 | public var multiplicityRange: ClosedRange { 30 | willSet { precondition(newValue.lowerBound >= 0, "Negative lower bound") } 31 | } 32 | 33 | } 34 | 35 | extension EagerlyRepeating { 36 | 37 | /// Creates a repeating pattern. 38 | /// 39 | /// - Requires: `multiplicity >= 0` 40 | /// 41 | /// - Parameter repeatedPattern: The pattern that is repeated. 42 | /// - Parameter multiplicity: The number of times to match the repeated pattern consecutively. 43 | public init(_ repeatedPattern: RepeatedPattern, exactly multiplicity: Int) { 44 | self.init(repeatedPattern, min: multiplicity, max: multiplicity) 45 | } 46 | 47 | } 48 | 49 | extension EagerlyRepeating : Pattern { 50 | 51 | public func forwardMatches(enteringFrom base: Match) -> LazyMapCollection>>, Match> { 52 | let minimumDepth = multiplicityRange.lowerBound 53 | let maximumDepth = multiplicityRange.upperBound == .max ? .max : multiplicityRange.upperBound + 1 54 | return ForwardRing(repeatedPattern: repeatedPattern, baseMatch: base) 55 | .flattenedInPostOrder(maximumDepth: maximumDepth) 56 | .lazy 57 | .filter { $0.depth >= minimumDepth } 58 | .map { $0.baseMatch } 59 | } 60 | 61 | public func backwardMatches(recedingFrom base: Match) -> LazyMapCollection>>, Match> { 62 | let minimumDepth = multiplicityRange.lowerBound 63 | let maximumDepth = multiplicityRange.upperBound == .max ? .max : multiplicityRange.upperBound + 1 64 | return BackwardRing(repeatedPattern: repeatedPattern, baseMatch: base) 65 | .flattenedInPostOrder(maximumDepth: maximumDepth) 66 | .lazy 67 | .filter { $0.depth >= minimumDepth } 68 | .map { $0.baseMatch } 69 | } 70 | 71 | public func underestimatedSmallestInputPositionForForwardMatching(on subject: Subject, fromIndex inputPosition: Subject.Index) -> Subject.Index { 72 | guard multiplicityRange.lowerBound > 0 else { return inputPosition } 73 | return repeatedPattern.underestimatedSmallestInputPositionForForwardMatching(on: subject, fromIndex: inputPosition) 74 | } 75 | 76 | public func overestimatedLargestInputPositionForBackwardMatching(on subject: Subject, fromIndex inputPosition: Subject.Index) -> Subject.Index { 77 | guard multiplicityRange.lowerBound > 0 else { return inputPosition } 78 | return repeatedPattern.overestimatedLargestInputPositionForBackwardMatching(on: subject, fromIndex: inputPosition) 79 | } 80 | 81 | } 82 | 83 | extension EagerlyRepeating : BidirectionalCollection { 84 | 85 | public enum Index : Int, Hashable { 86 | 87 | /// The position of the repeated pattern. 88 | case repeatedPattern = 0 89 | 90 | /// The past-the-end position. 91 | case end 92 | 93 | } 94 | 95 | public var startIndex: Index { .repeatedPattern } 96 | 97 | public var endIndex: Index { .end } 98 | 99 | public subscript (index: Index) -> RepeatedPattern { 100 | precondition(index == .repeatedPattern, "Index out of bounds") 101 | return repeatedPattern 102 | } 103 | 104 | public func index(before index: Index) -> Index { 105 | precondition(index == .end, "Index out of bounds") 106 | return .repeatedPattern 107 | } 108 | 109 | public func index(after index: Index) -> Index { 110 | precondition(index == .repeatedPattern, "Index out of bounds") 111 | return .end 112 | } 113 | 114 | } 115 | 116 | extension EagerlyRepeating.Index : Comparable { 117 | public static func <(leftIndex: Self, rightIndex: Self) -> Bool { 118 | leftIndex.rawValue < rightIndex.rawValue 119 | } 120 | } 121 | 122 | // TODO: Add literal & element initialisers (for autowrapping in Literal) when bugfix lands, in Swift 4 (or later) 123 | -------------------------------------------------------------------------------- /Sources/PatternKit/Repeating/Forward Ring.swift: -------------------------------------------------------------------------------- 1 | // PatternKit © 2017–21 Constantino Tsarouhas 2 | 3 | /// An iteration of a repeatedly forward-matching pattern. 4 | /// 5 | /// A ring takes a base match and produces a successor ring for every match produced by the repeated pattern given the base match. The successor rings themselves can be used to generate further descendant rings. Given a *root* ring, one can determine a tree of ring and thus of matches. 6 | public struct ForwardRing { 7 | 8 | public typealias Subject = RepeatedPattern.Subject 9 | public typealias MatchCollection = RepeatedPattern.ForwardMatchCollection 10 | 11 | /// Creates a ring. 12 | /// 13 | /// - Requires: `depth >= 0` 14 | /// 15 | /// - Parameter repeatedPattern: The pattern that is being repeatedly matched. 16 | /// - Parameter baseMatch: The match from which successor matches are generated. 17 | /// - Parameter depth: The number of rings that precede this ring. The ring built from the origin match has depth 0. The default is zero. 18 | public init(repeatedPattern: RepeatedPattern, baseMatch: Match, depth: Int = 0) { 19 | self.repeatedPattern = repeatedPattern 20 | self.baseMatch = baseMatch 21 | self.successorMatches = repeatedPattern.forwardMatches(enteringFrom: baseMatch) 22 | self.depth = depth 23 | } 24 | 25 | /// The pattern that is being repeatedly matched. 26 | public let repeatedPattern: RepeatedPattern 27 | 28 | /// The match from which successor matches are generated. 29 | public let baseMatch: Match 30 | 31 | /// The matches that are the result of applying the repeated pattern using the base match. 32 | public let successorMatches: MatchCollection 33 | 34 | /// The rings that represent the next iteration, one for every successor match. 35 | public var successorRings: LazyMapCollection { 36 | let repeatedPattern = self.repeatedPattern 37 | let newDepth = depth + 1 38 | return successorMatches.lazy.map { successorMatch in 39 | .init(repeatedPattern: repeatedPattern, baseMatch: successorMatch, depth: newDepth) 40 | } 41 | } 42 | 43 | /// The number of rings that precede this ring. 44 | /// 45 | /// The ring built from the origin match has depth 0. 46 | /// 47 | /// - Invariant: `depth >= 0` 48 | public let depth: Int 49 | 50 | } 51 | 52 | extension ForwardRing : BidirectionalCollection { 53 | public typealias Index = MatchCollection.Index 54 | public var startIndex: Index { successorMatches.startIndex } 55 | public var endIndex: Index { successorMatches.endIndex } 56 | public subscript (index: Index) -> ForwardRing { successorRings[index] } 57 | public func index(before index: Index) -> Index { successorMatches.index(before: index) } 58 | public func index(after index: Index) -> Index { successorMatches.index(after: index) } 59 | } 60 | -------------------------------------------------------------------------------- /Sources/PatternKit/Repeating/Lazily Repeating Operators.swift: -------------------------------------------------------------------------------- 1 | // PatternKit © 2017–21 Constantino Tsarouhas 2 | 3 | /// The unbounded Kleene operator with lazy matching semantics. 4 | postfix operator *? 5 | 6 | /// Returns an arbitrarily, lazily repeating pattern over a given repeated pattern. 7 | /// 8 | /// - Parameter repeatedPattern: The pattern that is repeated. 9 | /// 10 | /// - Returns: An arbitrarily, lazily repeating pattern over `repeatedPattern`. 11 | public postfix func *?

(repeatedPattern: P) -> LazilyRepeating

{ 12 | .init(repeatedPattern) 13 | } 14 | 15 | /// Returns an arbitrarily, lazily repeating pattern over a given literal collection. 16 | /// 17 | /// - Parameter repeatedlyMatchedCollection: The collection that is repeatedly matched exactly. 18 | /// 19 | /// - Returns: An arbitrarily, lazily repeating pattern over a literal pattern matching `repeatedlyMatchedCollection`. 20 | public postfix func *? (repeatedlyMatchedCollection: C) -> LazilyRepeating> { 21 | .init(.init(repeatedlyMatchedCollection)) 22 | } 23 | 24 | 25 | 26 | /// The lower-bounded Kleene operator with lazy matching semantics. 27 | postfix operator +? 28 | 29 | /// Returns an arbitrarily, lazily repeating pattern over a given repeated pattern that must match at least once. 30 | /// 31 | /// - Parameter repeatedPattern: The pattern that is repeated. 32 | /// 33 | /// - Returns: An arbitrarily, nonoptional, lazily repeating pattern over `repeatedPattern`. 34 | public postfix func +?

(repeatedPattern: P) -> LazilyRepeating

{ 35 | .init(repeatedPattern, min: 1) 36 | } 37 | 38 | /// Returns an arbitrarily, lazily repeating pattern over a given literal collection that must match at least once. 39 | /// 40 | /// - Parameter repeatedlyMatchedCollection: The collection that is repeatedly matched exactly. 41 | /// 42 | /// - Returns: An arbitrarily, nonoptional, lazily repeating pattern over a literal pattern matching `repeatedlyMatchedCollection`. 43 | public postfix func +? (repeatedlyMatchedCollection: C) -> LazilyRepeating> { 44 | .init(.init(repeatedlyMatchedCollection), min: 1) 45 | } 46 | 47 | 48 | 49 | /// The optionality operator with lazy matching semantics. 50 | postfix operator /?? 51 | 52 | /// Returns a pattern that lazily matches a given optional pattern. 53 | /// 54 | /// - Parameter optionalPattern: The pattern that is lazily matched. 55 | /// 56 | /// - Returns: A pattern that optionally and lazily matches `optionalPattern`. 57 | public postfix func /??

(optionalPattern: P) -> LazilyRepeating

{ 58 | .init(optionalPattern, max: 1) 59 | } 60 | 61 | /// Returns a pattern that lazily matches a given optional collection. 62 | /// 63 | /// - Parameter optionalCollection: The collection that is lazily matched. 64 | /// 65 | /// - Returns: A pattern that optionally and lazily matches the literal `optionalCollection`. 66 | public postfix func /?? (optionalCollection: C) -> LazilyRepeating> { 67 | .init(.init(optionalCollection), max: 1) 68 | } 69 | -------------------------------------------------------------------------------- /Sources/PatternKit/Repeating/Lazily Repeating.swift: -------------------------------------------------------------------------------- 1 | // PatternKit © 2017–21 Constantino Tsarouhas 2 | 3 | import DepthKit 4 | 5 | /// A pattern that performs matching of a subpattern repeatedly on consecutive subsequences of the target collection, preferring matching as few times as possible. 6 | public struct LazilyRepeating { 7 | 8 | public typealias Subject = RepeatedPattern.Subject 9 | 10 | /// Creates a repeating pattern. 11 | /// 12 | /// - Requires: `lowerBound >= 0` and `lowerBound <= upperBound` 13 | /// 14 | /// - Parameter repeatedPattern: The pattern that is repeated. 15 | /// - Parameter lowerBound: The minimal number of times the repeated pattern must be matched. The default is zero. 16 | /// - Parameter upperBound: The maximal number of times the repeated pattern may be matched. The default is `Int.max`. 17 | public init(_ repeatedPattern: RepeatedPattern, min lowerBound: Int = 0, max upperBound: Int = .max) { 18 | precondition(lowerBound >= 0, "Negative lower bound") 19 | self.repeatedPattern = repeatedPattern 20 | multiplicityRange = lowerBound...upperBound 21 | } 22 | 23 | /// The pattern that is repeated. 24 | public var repeatedPattern: RepeatedPattern 25 | 26 | /// The range of the number of times the pattern can be repeated. 27 | /// 28 | /// - Invariant: `multiplicityRange.lowerBound >= 0` 29 | public var multiplicityRange: ClosedRange { 30 | willSet { precondition(newValue.lowerBound >= 0, "Negative lower bound") } 31 | } 32 | 33 | } 34 | 35 | extension LazilyRepeating { 36 | 37 | /// Creates a repeating pattern. 38 | /// 39 | /// - Requires: `multiplicity >= 0` 40 | /// 41 | /// - Parameter repeatedPattern: The pattern that is repeated. 42 | /// - Parameter multiplicity: The number of times to match the repeated pattern consecutively. 43 | public init(_ repeatedPattern: RepeatedPattern, exactly multiplicity: Int) { 44 | self.init(repeatedPattern, min: multiplicity, max: multiplicity) 45 | } 46 | 47 | } 48 | 49 | public func repeating(_ repeatedPattern: RepeatedPattern, exactly multiplicity: Int) -> LazilyRepeating { 50 | .init(repeatedPattern, exactly: multiplicity) 51 | } 52 | 53 | extension LazilyRepeating : Pattern { 54 | 55 | public func forwardMatches(enteringFrom base: Match) -> LazyMapCollection>>, Match> { 56 | let minimumDepth = multiplicityRange.lowerBound 57 | let maximumDepth = multiplicityRange.upperBound == .max ? .max : multiplicityRange.upperBound + 1 58 | return ForwardRing(repeatedPattern: repeatedPattern, baseMatch: base) 59 | .flattenedInPreOrder(maximumDepth: maximumDepth) 60 | .lazy 61 | .filter { $0.depth >= minimumDepth } 62 | .map { $0.baseMatch } 63 | } 64 | 65 | public func backwardMatches(recedingFrom base: Match) -> LazyMapCollection>>, Match> { 66 | let minimumDepth = multiplicityRange.lowerBound 67 | let maximumDepth = multiplicityRange.upperBound == .max ? .max : multiplicityRange.upperBound + 1 68 | return BackwardRing(repeatedPattern: repeatedPattern, baseMatch: base) 69 | .flattenedInPreOrder(maximumDepth: maximumDepth) 70 | .lazy 71 | .filter { $0.depth >= minimumDepth } 72 | .map { $0.baseMatch } 73 | } 74 | 75 | public func underestimatedSmallestInputPositionForForwardMatching(on subject: Subject, fromIndex inputPosition: Subject.Index) -> Subject.Index { 76 | guard multiplicityRange.lowerBound > 0 else { return inputPosition } 77 | return repeatedPattern.underestimatedSmallestInputPositionForForwardMatching(on: subject, fromIndex: inputPosition) 78 | } 79 | 80 | public func overestimatedLargestInputPositionForBackwardMatching(on subject: Subject, fromIndex inputPosition: Subject.Index) -> Subject.Index { 81 | guard multiplicityRange.lowerBound > 0 else { return inputPosition } 82 | return repeatedPattern.overestimatedLargestInputPositionForBackwardMatching(on: subject, fromIndex: inputPosition) 83 | } 84 | 85 | } 86 | 87 | extension LazilyRepeating : BidirectionalCollection { 88 | 89 | public enum Index : Int, Hashable { 90 | 91 | /// The position of the repeated pattern. 92 | case repeatedPattern = 0 93 | 94 | /// The past-the-end position. 95 | case end 96 | 97 | } 98 | 99 | public var startIndex: Index { .repeatedPattern } 100 | 101 | public var endIndex: Index { .end } 102 | 103 | public subscript (index: Index) -> RepeatedPattern { 104 | precondition(index == .repeatedPattern, "Index out of bounds") 105 | return repeatedPattern 106 | } 107 | 108 | public func index(before index: Index) -> Index { 109 | precondition(index == .end, "Index out of bounds") 110 | return .repeatedPattern 111 | } 112 | 113 | public func index(after index: Index) -> Index { 114 | precondition(index == .repeatedPattern, "Index out of bounds") 115 | return .end 116 | } 117 | 118 | } 119 | 120 | extension LazilyRepeating.Index : Comparable { 121 | public static func <(leftIndex: Self, rightIndex: Self) -> Bool { 122 | leftIndex.rawValue < rightIndex.rawValue 123 | } 124 | } 125 | 126 | // TODO: Add literal & element initialisers (for autowrapping in Literal) when bugfix lands, in Swift 4 (or later) 127 | -------------------------------------------------------------------------------- /Sources/PatternKit/Singular Match Collection.swift: -------------------------------------------------------------------------------- 1 | // PatternKit © 2017–21 Constantino Tsarouhas 2 | 3 | /// A collection that contains at most one match. 4 | public struct SingularMatchCollection where Subject.Element : Equatable { 5 | 6 | /// The match resulting from matching the pattern, or `nil` if the pattern failed to match. 7 | public let resultMatch: Match? 8 | 9 | } 10 | 11 | extension SingularMatchCollection : ExpressibleByNilLiteral { 12 | public init(nilLiteral: ()) { 13 | self.init(resultMatch: nil) 14 | } 15 | } 16 | 17 | extension SingularMatchCollection : BidirectionalCollection { 18 | 19 | public enum Index : Equatable { 20 | 21 | /// The position of the result match if it exists, otherwise the end index of the collection. 22 | case resultMatch 23 | 24 | /// The position after the result match of the collection if the result match exists, otherwise an invalid index. 25 | case afterResultMatch 26 | 27 | } 28 | 29 | public var startIndex: Index { .resultMatch } 30 | 31 | public var endIndex: Index { resultMatch == nil ? .resultMatch : .afterResultMatch } 32 | 33 | public subscript (index: Index) -> Match { 34 | switch index { 35 | 36 | case .resultMatch: 37 | guard let resultMatch = resultMatch else { preconditionFailure("Index out of bounds") } 38 | return resultMatch 39 | 40 | case .afterResultMatch: 41 | preconditionFailure("Index out of bounds") 42 | 43 | } 44 | } 45 | 46 | public func index(before index: Index) -> Index { 47 | switch index { 48 | case .resultMatch: preconditionFailure("Index out of bounds") 49 | case .afterResultMatch: return .resultMatch 50 | } 51 | } 52 | 53 | public func index(after index: Index) -> Index { 54 | switch index { 55 | case .resultMatch: return .afterResultMatch 56 | case .afterResultMatch: preconditionFailure("Index out of bounds") 57 | } 58 | } 59 | 60 | } 61 | 62 | extension SingularMatchCollection.Index : Comparable { 63 | public static func <(left: Self, right: Self) -> Bool { 64 | (left, right) == (.resultMatch, .afterResultMatch) 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /Sources/PatternKit/TODO.swift: -------------------------------------------------------------------------------- 1 | // PatternKit © 2017–21 Constantino Tsarouhas 2 | 3 | import DepthKit 4 | 5 | typealias TODO = DepthKit.TODO 6 | -------------------------------------------------------------------------------- /Sources/PatternKit/Wildcard.swift: -------------------------------------------------------------------------------- 1 | // PatternKit © 2017–21 Constantino Tsarouhas 2 | 3 | /// A pattern that matches any one element. 4 | public struct Wildcard where Subject.Element : Equatable {} 5 | 6 | extension Wildcard : Pattern { 7 | 8 | public func forwardMatches(enteringFrom base: Match) -> SingularMatchCollection { 9 | guard !base.remainingElements(direction: .forward).isEmpty else { return nil } 10 | return .init(resultMatch: base.movingInputPosition(distance: 1, direction: .forward)) 11 | } 12 | 13 | public func backwardMatches(recedingFrom base: Match) -> SingularMatchCollection { 14 | guard !base.remainingElements(direction: .backward).isEmpty else { return nil } 15 | return .init(resultMatch: base.movingInputPosition(distance: 1, direction: .backward)) 16 | } 17 | 18 | } 19 | 20 | /// Returns a pattern matching exactly one element. 21 | /// 22 | /// - Returns: `Wildcard()` 23 | public func one() -> Wildcard { 24 | .init() 25 | } 26 | 27 | /// Returns a pattern eagerly matching some number of arbitrary elements. 28 | /// 29 | /// - Parameter lowerBound: The lower bound. The default is zero. 30 | /// - Parameter upperBound: The upper bound, inclusive. The default is `Int.max`. 31 | /// 32 | /// - Returns: A pattern eagerly matching some number of arbitrary elements. 33 | public func any(min lowerBound: Int = 0, max upperBound: Int = .max) -> EagerlyRepeating> { 34 | .init(Wildcard(), min: lowerBound, max: upperBound) 35 | } 36 | -------------------------------------------------------------------------------- /Sources/PatternKitCore/Extensions on Range.swift: -------------------------------------------------------------------------------- 1 | // PatternKit © 2017–21 Constantino Tsarouhas 2 | 3 | extension Range { 4 | 5 | /// Returns a Boolean value indicating whether a range is fully contained within `self`. 6 | /// 7 | /// - Parameter other: The other range. 8 | /// 9 | /// - Returns: `true` if `other` is fully contained within `self`, `false` otherwise. 10 | public func contains(_ other: Range) -> Bool { 11 | self.lowerBound <= other.lowerBound && self.upperBound >= other.upperBound 12 | } 13 | 14 | /// Returns a Boolean value indicating whether a range is fully contained within `self`. 15 | /// 16 | /// - Parameter other: The other range. 17 | /// 18 | /// - Returns: `true` if `other` is fully contained within `self`, `false` otherwise. 19 | public func contains(_ other: ClosedRange) -> Bool { 20 | self.lowerBound <= other.lowerBound && self.upperBound > other.upperBound 21 | } 22 | 23 | } 24 | 25 | extension ClosedRange { 26 | 27 | /// Returns a Boolean value indicating whether a range is fully contained within `self`. 28 | /// 29 | /// - Parameter other: The other range. 30 | /// 31 | /// - Returns: `true` if `other` is fully contained within `self`, `false` otherwise. 32 | public func contains(_ other: Range) -> Bool { 33 | self.lowerBound <= other.lowerBound && self.upperBound >= other.upperBound 34 | } 35 | 36 | /// Returns a Boolean value indicating whether a range is fully contained within `self`. 37 | /// 38 | /// - Parameter other: The other range. 39 | /// 40 | /// - Returns: `true` if `other` is fully contained within `self`, `false` otherwise. 41 | public func contains(_ other: ClosedRange) -> Bool { 42 | self.lowerBound <= other.lowerBound && self.upperBound > other.upperBound 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /Sources/PatternKitCore/Matching Direction.swift: -------------------------------------------------------------------------------- 1 | // PatternKit © 2017–21 Constantino Tsarouhas 2 | 3 | /// A direction of matching. 4 | public enum MatchingDirection { 5 | 6 | /// Elements in a collection are matched from low to high indices. 7 | case forward 8 | 9 | /// Elements in a collection are matched from high to low indices. 10 | case backward 11 | 12 | /// The inverse direction. 13 | public var inverse: MatchingDirection { 14 | switch self { 15 | case .forward: return .backward 16 | case .backward: return .forward 17 | } 18 | } 19 | 20 | } 21 | 22 | -------------------------------------------------------------------------------- /Sources/PatternKitCore/Token.swift: -------------------------------------------------------------------------------- 1 | // PatternKit © 2017–21 Constantino Tsarouhas 2 | 3 | public final class Token { 4 | 5 | public typealias Subject = CapturedPattern.Subject 6 | 7 | /// Creates a token capturing matches from a subpattern. 8 | /// 9 | /// - Parameter subpattern: The subpattern. 10 | public init(_ subpattern: CapturedPattern) { 11 | self.capturedPattern = subpattern 12 | } 13 | 14 | /// The pattern whose matched subsequences are captured by the token. 15 | public var capturedPattern: CapturedPattern 16 | 17 | } 18 | 19 | extension Token : Pattern { 20 | 21 | public func forwardMatches(enteringFrom base: Match) -> LazyMapCollection> { 22 | capturedPattern 23 | .forwardMatches(enteringFrom: base) 24 | .lazy 25 | .map { match in match.capturing(base.inputPosition..) -> LazyMapCollection> { 29 | capturedPattern 30 | .backwardMatches(recedingFrom: base) 31 | .lazy 32 | .map { $0.capturing($0.inputPosition.. Subject.Index { 36 | capturedPattern.underestimatedSmallestInputPositionForForwardMatching(on: subject, fromIndex: inputPosition) 37 | } 38 | 39 | public func overestimatedLargestInputPositionForBackwardMatching(on subject: Subject, fromIndex inputPosition: Subject.Index) -> Subject.Index { 40 | capturedPattern.overestimatedLargestInputPositionForBackwardMatching(on: subject, fromIndex: inputPosition) 41 | } 42 | 43 | } 44 | 45 | extension Token : BidirectionalCollection { 46 | 47 | public enum Index : Int, Hashable { 48 | 49 | /// The position of the captured pattern. 50 | case capturedPattern = 0 51 | 52 | /// The past-the-end position. 53 | case end 54 | 55 | } 56 | 57 | public var startIndex: Index { .capturedPattern } 58 | 59 | public var endIndex: Index { .end } 60 | 61 | public subscript (index: Index) -> CapturedPattern { 62 | precondition(index == .capturedPattern, "Index out of bounds") 63 | return capturedPattern 64 | } 65 | 66 | public func index(before index: Index) -> Index { 67 | precondition(index == .end, "Index out of bounds") 68 | return .capturedPattern 69 | } 70 | 71 | public func index(after index: Index) -> Index { 72 | precondition(index == .capturedPattern, "Index out of bounds") 73 | return .end 74 | } 75 | 76 | } 77 | 78 | extension Token.Index : Comparable { 79 | public static func <(leftIndex: Self, rightIndex: Self) -> Bool { 80 | leftIndex.rawValue < rightIndex.rawValue 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /Sources/RegexKit/Alternation/Alternation Expression Realisation.swift: -------------------------------------------------------------------------------- 1 | // PatternKit © 2017–21 Constantino Tsarouhas 2 | 3 | import DepthKit 4 | import PatternKit 5 | 6 | /// A realisation of an alternation expression with realisable subexpressions. 7 | public struct AlternationExpressionRealisation : Realisation where 8 | 9 | MainExpressionRealisation.PatternType.ForwardMatchCollection.Indices : OrderedCollection, 10 | AlternativeExpressionRealisation.PatternType.ForwardMatchCollection.Indices : OrderedCollection, 11 | 12 | MainExpressionRealisation.PatternType.BackwardMatchCollection.Indices : OrderedCollection, 13 | AlternativeExpressionRealisation.PatternType.BackwardMatchCollection.Indices : OrderedCollection { 14 | 15 | /// The type of the alternation expression's main expression. 16 | public typealias MainExpression = MainExpressionRealisation.ExpressionType 17 | 18 | /// The type of the alternation expression's alternative expression. 19 | public typealias AlternativeExpression = AlternativeExpressionRealisation.ExpressionType 20 | 21 | // See protocol. 22 | public init(of expression: AlternationExpression) { 23 | realisationOfMainExpression = MainExpressionRealisation(of: expression.mainExpression) 24 | realisationOfAlternativeExpression = AlternativeExpressionRealisation(of: expression.alternativeExpression) 25 | } 26 | 27 | /// The realisation of the main expression. 28 | public let realisationOfMainExpression: MainExpressionRealisation 29 | 30 | /// The realisation of the alternative expression. 31 | public let realisationOfAlternativeExpression: AlternativeExpressionRealisation 32 | 33 | // See protocol. 34 | public var pattern: Alternation { 35 | realisationOfMainExpression.pattern | realisationOfAlternativeExpression.pattern 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /Sources/RegexKit/Alternation/Alternation Expression.swift: -------------------------------------------------------------------------------- 1 | // PatternKit © 2017–21 Constantino Tsarouhas 2 | 3 | import DepthKit 4 | 5 | /// An expression that expresses an alternation pattern. 6 | public struct AlternationExpression { 7 | 8 | /// Creates an alternation expression with given subexpressions. 9 | /// 10 | /// - Parameter mainExpression: The expression that represents the main pattern of the alternation. 11 | /// - Parameter alternativeExpression: The expression that represents the alternative pattern of the alternation. 12 | public init(_ mainExpression: MainExpression, _ alternativeExpression: AlternativeExpression) { 13 | self.mainExpression = mainExpression 14 | self.alternativeExpression = alternativeExpression 15 | } 16 | 17 | /// The expression that represents the main pattern of the alternation. 18 | public var mainExpression: MainExpression 19 | 20 | /// The expression that represents the alternative pattern of the alternation. 21 | public var alternativeExpression: AlternativeExpression 22 | 23 | } 24 | 25 | extension AlternationExpression : Expression { 26 | 27 | public enum Index : Equatable { 28 | 29 | /// A position of a symbol in the main expression. 30 | /// 31 | /// - Invariant: `innerIndex` is an index to a symbol in the main expression. 32 | /// 33 | /// - Parameter innerIndex: The position of the symbol within the main expression. 34 | case inMainExpression(innerIndex: MainExpression.Index) 35 | 36 | /// The position of the delimiter symbol. 37 | case delimiter 38 | 39 | /// A position of a symbol in the alternative expression. 40 | /// 41 | /// - Invariant: `innerIndex` is an index to a symbol in the alternative expression. 42 | /// 43 | /// - Parameter innerIndex: The position of the symbol within the alternative expression. 44 | case inAlternativeExpression(innerIndex: AlternativeExpression.Index) 45 | 46 | /// The position after the last symbol. 47 | case end 48 | 49 | } 50 | 51 | public var startIndex: Index { 52 | if let firstIndex = mainExpression.indices.first { 53 | return .inMainExpression(innerIndex: firstIndex) 54 | } else { 55 | return .delimiter 56 | } 57 | } 58 | 59 | public var endIndex: Index { .end } 60 | 61 | public subscript (index: Index) -> SymbolProtocol { 62 | switch index { 63 | case .inMainExpression(innerIndex: let index): return mainExpression[index] 64 | case .delimiter: return AlternationDelimiterSymbol() 65 | case .inAlternativeExpression(innerIndex: let index): return alternativeExpression[index] 66 | case .end: indexOutOfBounds 67 | } 68 | } 69 | 70 | public func index(before index: Index) -> Index { 71 | switch index { 72 | 73 | case .inMainExpression(innerIndex: let index): 74 | return .inMainExpression(innerIndex: mainExpression.index(before: index)) 75 | 76 | case .delimiter: 77 | guard let lastIndex = mainExpression.indices.last else { indexOutOfBounds } 78 | return .inMainExpression(innerIndex: lastIndex) 79 | 80 | case .inAlternativeExpression(innerIndex: let index): 81 | guard index > alternativeExpression.startIndex else { return .delimiter } 82 | return .inAlternativeExpression(innerIndex: alternativeExpression.index(before: index)) 83 | 84 | case .end: 85 | guard let lastIndex = alternativeExpression.indices.last else { return .delimiter } 86 | return .inAlternativeExpression(innerIndex: lastIndex) 87 | 88 | } 89 | } 90 | 91 | public func index(after index: Index) -> Index { 92 | switch index { 93 | 94 | case .inMainExpression(innerIndex: let index): 95 | let nextIndex = mainExpression.index(after: index) 96 | guard nextIndex < mainExpression.endIndex else { return .delimiter } 97 | return .inMainExpression(innerIndex: nextIndex) 98 | 99 | case .delimiter: 100 | guard let firstIndex = alternativeExpression.indices.first else { return .end } 101 | return .inAlternativeExpression(innerIndex: firstIndex) 102 | 103 | case .inAlternativeExpression(innerIndex: let index): 104 | let nextIndex = alternativeExpression.index(after: index) 105 | guard nextIndex < alternativeExpression.endIndex else { return .end } 106 | return .inAlternativeExpression(innerIndex: nextIndex) 107 | 108 | case .end: 109 | indexOutOfBounds 110 | 111 | } 112 | } 113 | 114 | public var bindingClass: BindingClass { .alternation } 115 | 116 | } 117 | 118 | extension AlternationExpression.Index : Comparable { 119 | public static func <(smallerIndex: Self, greaterIndex: Self) -> Bool { 120 | switch (smallerIndex, greaterIndex) { 121 | case (.inMainExpression(innerIndex: let smallerIndex), .inMainExpression(innerIndex: let greaterIndex)): return smallerIndex < greaterIndex 122 | case (.inMainExpression, _): return true 123 | case (.delimiter, .inMainExpression): return false 124 | case (.delimiter, .delimiter): return false 125 | case (.delimiter, _): return true 126 | case (.inAlternativeExpression, .inMainExpression): return false 127 | case (.inAlternativeExpression, .delimiter): return false 128 | case (.inAlternativeExpression(innerIndex: let smallerIndex), .inAlternativeExpression(innerIndex: let greaterIndex)): return smallerIndex < greaterIndex 129 | case (.inAlternativeExpression, .end): return true 130 | case (.end, _): return false 131 | } 132 | } 133 | } 134 | 135 | /// A symbol that separates the different subexpressions in an alternation. 136 | public struct AlternationDelimiterSymbol : SymbolProtocol { 137 | 138 | // See protocol. 139 | public func serialisation(language: Language) -> String { 140 | "|" 141 | } 142 | 143 | } 144 | -------------------------------------------------------------------------------- /Sources/RegexKit/Alternation/Homogeneous Alternation Expression Realisation.swift: -------------------------------------------------------------------------------- 1 | // PatternKit © 2017–21 Constantino Tsarouhas 2 | 3 | import DepthKit 4 | import PatternKit 5 | 6 | /// A realisation of a homogeneous alternation expression with realisable subexpressions. 7 | public struct HomogeneousAlternationRegularExpressionRealisation : Realisation { 8 | 9 | /// The type of the alternation expression's subexpressions. 10 | public typealias Subexpression = SubexpressionRealisation.ExpressionType 11 | 12 | // See protocol. 13 | public init(of expression: HomogeneousAlternationExpression) { 14 | realisationsOfSubexpressions = expression.subexpressions.map(SubexpressionRealisation.init) 15 | } 16 | 17 | /// The realisations of the subexpressions. 18 | public let realisationsOfSubexpressions: [SubexpressionRealisation] 19 | 20 | // See protocol. 21 | public var pattern: HomogeneousAlternation { 22 | .init(realisationsOfSubexpressions.map { $0.pattern }) 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /Sources/RegexKit/Assertion/Backward Assertion Expression.swift: -------------------------------------------------------------------------------- 1 | // PatternKit © 2017–21 Constantino Tsarouhas 2 | 3 | import DepthKit 4 | 5 | /// An expression that expresses an arbitrary backward assertion. 6 | /// 7 | /// Some languages support backward assertions but not arbitrary backward assertions. Typical constraints involve the length of the backward matched substring. 8 | public struct BackwardAssertionExpression { 9 | 10 | /// Creates a backward assertion expression with given subexpression. 11 | /// 12 | /// - Parameter comment: The expression that expresses the asserted pattern. 13 | public init(_ subexpression: Subexpression) { 14 | self.subexpression = subexpression 15 | } 16 | 17 | /// The expression that expresses the asserted pattern. 18 | public var subexpression: Subexpression 19 | 20 | } 21 | 22 | extension BackwardAssertionExpression : BoundedUnaryExpression { 23 | 24 | public typealias Index = BoundedUnaryExpressionIndex 25 | 26 | public static var leadingBoundarySymbol: SymbolProtocol { BoundarySymbol.leading } 27 | 28 | public static var trailingBoundarySymbol: SymbolProtocol { BoundarySymbol.trailing } 29 | 30 | public enum BoundarySymbol : SymbolProtocol { 31 | 32 | /// A symbol that represents the leading boundary. 33 | case leading 34 | 35 | /// A symbol that represents the trailing boundary. 36 | case trailing 37 | 38 | // See protocol. 39 | public func serialisation(language: Language) throws -> String { 40 | guard language == .perlCompatibleREs else { throw SymbolSerialisationError.unsupportedByLanguage } 41 | switch self { 42 | case .leading: return "(?<=" 43 | case .trailing: return ")" 44 | } 45 | } 46 | 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /Sources/RegexKit/Assertion/Forward Assertion Expression.swift: -------------------------------------------------------------------------------- 1 | // PatternKit © 2017–21 Constantino Tsarouhas 2 | 3 | import DepthKit 4 | 5 | /// An expression that expresses a forward assertion. 6 | public struct ForwardAssertionExpression { 7 | 8 | /// Creates a forward assertion expression with given subexpression. 9 | /// 10 | /// - Parameter comment: The expression that expresses the asserted pattern. 11 | public init(_ subexpression: Subexpression) { 12 | self.subexpression = subexpression 13 | } 14 | 15 | /// The expression that expresses the asserted pattern. 16 | public var subexpression: Subexpression 17 | 18 | } 19 | 20 | extension ForwardAssertionExpression : BoundedUnaryExpression { 21 | 22 | public typealias Index = BoundedUnaryExpressionIndex 23 | 24 | public static var leadingBoundarySymbol: SymbolProtocol { BoundarySymbol.leading } 25 | 26 | public static var trailingBoundarySymbol: SymbolProtocol { BoundarySymbol.trailing } 27 | 28 | public enum BoundarySymbol : SymbolProtocol { 29 | 30 | /// A symbol that represents the leading boundary. 31 | case leading 32 | 33 | /// A symbol that represents the trailing boundary. 34 | case trailing 35 | 36 | // See protocol. 37 | public func serialisation(language: Language) -> String { 38 | switch self { 39 | case .leading: return "(?=" 40 | case .trailing: return ")" 41 | } 42 | } 43 | 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /Sources/RegexKit/Assertion/Negated Backward Assertion Expression.swift: -------------------------------------------------------------------------------- 1 | // PatternKit © 2017–21 Constantino Tsarouhas 2 | 3 | import DepthKit 4 | 5 | /// An expression that expresses an arbitrary negated backward assertion. 6 | public struct NegatedBackwardAssertionExpression { 7 | 8 | /// Creates a negated backward assertion expression with given subexpression. 9 | /// 10 | /// - Parameter comment: The expression that expresses the negatively asserted pattern. 11 | public init(_ subexpression: Subexpression) { 12 | self.subexpression = subexpression 13 | } 14 | 15 | /// The expression that expresses the negatively asserted pattern. 16 | public var subexpression: Subexpression 17 | 18 | } 19 | 20 | extension NegatedBackwardAssertionExpression : BoundedUnaryExpression { 21 | 22 | public typealias Index = BoundedUnaryExpressionIndex 23 | 24 | public static var leadingBoundarySymbol: SymbolProtocol { BoundarySymbol.leading } 25 | 26 | public static var trailingBoundarySymbol: SymbolProtocol { BoundarySymbol.trailing } 27 | 28 | public enum BoundarySymbol : SymbolProtocol { 29 | 30 | /// A symbol that represents the leading boundary. 31 | case leading 32 | 33 | /// A symbol that represents the trailing boundary. 34 | case trailing 35 | 36 | // See protocol. 37 | public func serialisation(language: Language) throws -> String { 38 | guard language == .perlCompatibleREs else { throw SymbolSerialisationError.unsupportedByLanguage } 39 | switch self { 40 | case .leading: return "(? { 7 | 8 | /// Creates a negated forward assertion expression with given subexpression. 9 | /// 10 | /// - Parameter comment: The expression that expresses the negatively asserted pattern. 11 | public init(_ subexpression: Subexpression) { 12 | self.subexpression = subexpression 13 | } 14 | 15 | /// The expression that expresses the negatively asserted pattern. 16 | public var subexpression: Subexpression 17 | 18 | } 19 | 20 | extension NegatedForwardAssertionExpression : BoundedUnaryExpression { 21 | 22 | public typealias Index = BoundedUnaryExpressionIndex 23 | 24 | public static var leadingBoundarySymbol: SymbolProtocol { BoundarySymbol.leading } 25 | 26 | public static var trailingBoundarySymbol: SymbolProtocol { BoundarySymbol.trailing } 27 | 28 | public enum BoundarySymbol : SymbolProtocol { 29 | 30 | /// A symbol that represents the leading boundary. 31 | case leading 32 | 33 | /// A symbol that represents the trailing boundary. 34 | case trailing 35 | 36 | // See protocol. 37 | public func serialisation(language: Language) -> String { 38 | switch self { 39 | case .leading: return "(?!" 40 | case .trailing: return ")" 41 | } 42 | } 43 | 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /Sources/RegexKit/Comment Expression.swift: -------------------------------------------------------------------------------- 1 | // PatternKit © 2017–21 Constantino Tsarouhas 2 | 3 | import DepthKit 4 | 5 | /// An expression with arbitrary literal symbols that expresses no pattern. 6 | public struct CommentRegularExpression { 7 | 8 | /// Creates an expression with given arbitrary characters that expresses no pattern. 9 | /// 10 | /// - Parameter comment: The comment or unevaluated serialisation. 11 | public init(_ comment: String) { 12 | self.comment = comment 13 | } 14 | 15 | /// The comment or unevaluated serialisation. 16 | public var comment: String 17 | 18 | public enum Symbol { 19 | 20 | /// A symbol that represents the leading boundary of a comment. 21 | case leadingBoundary 22 | 23 | /// A symbol that represents an unevaluated character in a comment. 24 | /// 25 | /// - Parameter 1: The unevaluated character. 26 | case unevaluatedCharacter(Character) 27 | 28 | /// A symbol that represents the trailing boundary of a comment. 29 | case trailingBoundary 30 | 31 | } 32 | 33 | } 34 | 35 | extension CommentRegularExpression : Expression { 36 | 37 | public enum Index : Equatable { 38 | 39 | /// The position of the leading boundary symbol. 40 | case leadingBoundary 41 | 42 | /// A position of a symbol that represents a character in the comment. 43 | /// 44 | /// - Invariant: `index` is an index to a character in the comment. 45 | /// 46 | /// - Parameter index: The position of the represented character in the comment. 47 | case unevaluatedCharacter(index: String.Index) 48 | 49 | /// The position of the trailing boundary symbol. 50 | case trailingBoundary 51 | 52 | /// The position after the last symbol. 53 | case end 54 | 55 | } 56 | 57 | public var startIndex: Index { .leadingBoundary } 58 | 59 | public var endIndex: Index { .end } 60 | 61 | public subscript (index: Index) -> SymbolProtocol { 62 | switch index { 63 | case .leadingBoundary: return Symbol.leadingBoundary 64 | case .unevaluatedCharacter(index: let index): return Symbol.unevaluatedCharacter(comment[index]) 65 | case .trailingBoundary: return Symbol.trailingBoundary 66 | case .end: indexOutOfBounds 67 | } 68 | } 69 | 70 | public func index(before index: Index) -> Index { 71 | switch index { 72 | 73 | case .leadingBoundary: 74 | indexOutOfBounds 75 | 76 | case .unevaluatedCharacter(index: let index) where index == comment.startIndex: 77 | return .leadingBoundary 78 | 79 | case .unevaluatedCharacter(index: let index): 80 | return .unevaluatedCharacter(index: comment.index(before: index)) 81 | 82 | case .trailingBoundary: 83 | guard let lastCharacterIndex = comment.indices.last else { return .leadingBoundary } 84 | return .unevaluatedCharacter(index: lastCharacterIndex) 85 | 86 | case .end: 87 | return .trailingBoundary 88 | 89 | } 90 | } 91 | 92 | public func index(after index: Index) -> Index { 93 | switch index { 94 | 95 | case .leadingBoundary: 96 | guard let firstCharacterIndex = comment.indices.first else { return .trailingBoundary } 97 | return .unevaluatedCharacter(index: firstCharacterIndex) 98 | 99 | case .unevaluatedCharacter(index: let index): 100 | let nextCharacterIndex = comment.index(after: index) 101 | guard nextCharacterIndex < comment.endIndex else { return .trailingBoundary } 102 | return .unevaluatedCharacter(index: nextCharacterIndex) 103 | 104 | case .trailingBoundary: 105 | return .end 106 | 107 | case .end: 108 | indexOutOfBounds 109 | 110 | } 111 | } 112 | 113 | public var bindingClass: BindingClass { .atomic } 114 | 115 | } 116 | 117 | extension CommentRegularExpression.Symbol : SymbolProtocol { 118 | 119 | public func serialisation(language: Language) throws -> String { 120 | guard language == .perlCompatibleREs else { throw SymbolSerialisationError.unsupportedByLanguage } 121 | switch self { 122 | case .leadingBoundary: return "(?#" 123 | case .unevaluatedCharacter(")"): throw SymbolSerialisationError.unsupportedByLanguage 124 | case .unevaluatedCharacter(let character): return String(character) 125 | case .trailingBoundary: return ")" 126 | } 127 | } 128 | 129 | } 130 | 131 | extension CommentRegularExpression.Index : Comparable { 132 | public static func <(smallerIndex: Self, greaterIndex: Self) -> Bool { 133 | switch (smallerIndex, greaterIndex) { 134 | case (.leadingBoundary, .leadingBoundary): return false 135 | case (.leadingBoundary, _): return true 136 | case (.unevaluatedCharacter, .leadingBoundary): return false 137 | case (.unevaluatedCharacter(index: let smallerIndex), .unevaluatedCharacter(index: let greaterIndex)): return smallerIndex < greaterIndex 138 | case (.unevaluatedCharacter, _): return true 139 | case (.trailingBoundary, .end): return true 140 | case (.trailingBoundary, _): return false 141 | case (.end, _): return false 142 | } 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /Sources/RegexKit/Concatenated/Concatenated Expression Realisation.swift: -------------------------------------------------------------------------------- 1 | // PatternKit © 2017–21 Constantino Tsarouhas 2 | 3 | import DepthKit 4 | import PatternKit 5 | 6 | /// A realisation of a concatenated expression with realisable subexpressions. 7 | public struct ConcatenatedExpressionRealisation : Realisation where 8 | 9 | LeadingExpressionRealisation.PatternType.ForwardMatchCollection.Indices : OrderedCollection, 10 | TrailingExpressionRealisation.PatternType.ForwardMatchCollection.Indices : OrderedCollection, 11 | 12 | LeadingExpressionRealisation.PatternType.BackwardMatchCollection.Indices : OrderedCollection, 13 | TrailingExpressionRealisation.PatternType.BackwardMatchCollection.Indices : OrderedCollection { 14 | 15 | /// The type of the concatenated expression's leading expression. 16 | public typealias LeadingExpression = LeadingExpressionRealisation.ExpressionType 17 | 18 | /// The type of the concatenated expression's trailing expression. 19 | public typealias TrailingExpression = TrailingExpressionRealisation.ExpressionType 20 | 21 | // See protocol. 22 | public init(of expression: ConcatenatedExpression) { 23 | realisationOfLeadingExpression = LeadingExpressionRealisation(of: expression.leadingExpression) 24 | realisationOfTrailingExpression = TrailingExpressionRealisation(of: expression.trailingExpression) 25 | } 26 | 27 | /// The realisation of the leading expression. 28 | public let realisationOfLeadingExpression: LeadingExpressionRealisation 29 | 30 | /// The realisation of the trailing expression. 31 | public let realisationOfTrailingExpression: TrailingExpressionRealisation 32 | 33 | // See protocol. 34 | public var pattern: Concatenation { 35 | realisationOfLeadingExpression.pattern • realisationOfTrailingExpression.pattern 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /Sources/RegexKit/Concatenated/Concatenated Expression.swift: -------------------------------------------------------------------------------- 1 | // PatternKit © 2017–21 Constantino Tsarouhas 2 | 3 | import DepthKit 4 | 5 | /// An expression that concatenates two subexpressions. 6 | public struct ConcatenatedExpression { 7 | 8 | /// Creates a concatenated expression with given subexpressions. 9 | /// 10 | /// - Parameter leadingExpression: The expression that forms the first part of the concatenated expression. 11 | /// - Parameter trailingExpression: The expression that follows the leading expression. 12 | public init(_ leadingExpression: LeadingExpression, _ trailingExpression: TrailingExpression) { 13 | self.leadingExpression = leadingExpression 14 | self.trailingExpression = trailingExpression 15 | } 16 | 17 | /// The expression that forms the first part of the concatenated expression. 18 | public var leadingExpression: LeadingExpression 19 | 20 | /// The expression that follows the leading expression. 21 | public var trailingExpression: TrailingExpression 22 | 23 | } 24 | 25 | extension ConcatenatedExpression : Expression { 26 | 27 | public enum Index : Equatable { 28 | 29 | /// A position of a symbol in the leading expression. 30 | /// 31 | /// - Invariant: `innerIndex` is an index to a symbol in the leading expression. 32 | /// 33 | /// - Parameter innerIndex: The position of the symbol within the leading expression. 34 | case inLeadingExpression(innerIndex: LeadingExpression.Index) 35 | 36 | /// A position of a symbol in the trailing expression. 37 | /// 38 | /// - Invariant: `innerIndex` is an index to a symbol in the trailing expression. 39 | /// 40 | /// - Parameter innerIndex: The position of the symbol within the trailing expression. 41 | case inTrailingExpression(innerIndex: TrailingExpression.Index) 42 | 43 | /// The position after the last symbol. 44 | case end 45 | 46 | } 47 | 48 | public var startIndex: Index { 49 | if let firstIndex = leadingExpression.indices.first { 50 | return .inLeadingExpression(innerIndex: firstIndex) 51 | } else if let firstIndex = trailingExpression.indices.first { 52 | return .inTrailingExpression(innerIndex: firstIndex) 53 | } else { 54 | return .end 55 | } 56 | } 57 | 58 | public var endIndex: Index { .end } 59 | 60 | public subscript (index: Index) -> SymbolProtocol { 61 | switch index { 62 | case .inLeadingExpression(innerIndex: let index): return leadingExpression[index] 63 | case .inTrailingExpression(innerIndex: let index): return trailingExpression[index] 64 | case .end: indexOutOfBounds 65 | } 66 | } 67 | 68 | public func index(before index: Index) -> Index { 69 | switch index { 70 | 71 | case .inLeadingExpression(innerIndex: let index): 72 | return .inLeadingExpression(innerIndex: leadingExpression.index(before: index)) 73 | 74 | case .inTrailingExpression(innerIndex: let index) where index == trailingExpression.startIndex: 75 | guard let lastIndex = leadingExpression.indices.last else { indexOutOfBounds } 76 | return .inLeadingExpression(innerIndex: lastIndex) 77 | 78 | case .inTrailingExpression(innerIndex: let index): 79 | return .inTrailingExpression(innerIndex: trailingExpression.index(before: index)) 80 | 81 | case .end: 82 | if let lastIndex = trailingExpression.indices.last { 83 | return .inTrailingExpression(innerIndex: lastIndex) 84 | } else if let lastIndex = leadingExpression.indices.last { 85 | return .inLeadingExpression(innerIndex: lastIndex) 86 | } else { 87 | indexOutOfBounds 88 | } 89 | 90 | } 91 | } 92 | 93 | public func index(after index: Index) -> Index { 94 | switch index { 95 | 96 | case .inLeadingExpression(innerIndex: let index): 97 | let nextIndex = leadingExpression.index(after: index) 98 | if nextIndex < leadingExpression.endIndex { 99 | return .inLeadingExpression(innerIndex: nextIndex) 100 | } else if let firstIndex = trailingExpression.indices.first { 101 | return .inTrailingExpression(innerIndex: firstIndex) 102 | } else { 103 | return .end 104 | } 105 | 106 | case .inTrailingExpression(innerIndex: let index): 107 | let nextIndex = trailingExpression.index(after: index) 108 | guard nextIndex < trailingExpression.endIndex else { return .end } 109 | return .inTrailingExpression(innerIndex: nextIndex) 110 | 111 | case .end: 112 | indexOutOfBounds 113 | 114 | } 115 | } 116 | 117 | public var bindingClass: BindingClass { .concatenation } 118 | 119 | } 120 | 121 | extension ConcatenatedExpression.Index : Comparable { 122 | public static func <(smallerIndex: Self, greaterIndex: Self) -> Bool { 123 | switch (smallerIndex, greaterIndex) { 124 | case (.inLeadingExpression(innerIndex: let smallerIndex), .inLeadingExpression(innerIndex: let greaterIndex)): return smallerIndex < greaterIndex 125 | case (.inLeadingExpression, _): return true 126 | case (.inTrailingExpression, .inLeadingExpression): return false 127 | case (.inTrailingExpression(innerIndex: let smallerIndex), .inTrailingExpression(innerIndex: let greaterIndex)): return smallerIndex < greaterIndex 128 | case (.inTrailingExpression, .end): return true 129 | case (.end, _): return false 130 | } 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /Sources/RegexKit/Concatenated/Homogeneous Concatenated Expression Realisation.swift: -------------------------------------------------------------------------------- 1 | // PatternKit © 2017–21 Constantino Tsarouhas 2 | 3 | import DepthKit 4 | import PatternKit 5 | 6 | /// A realisation of a homogeneous concatenated expression with realisable subexpressions. 7 | public struct HomogeneousConcatenatedExpressionRealisation : Realisation { 8 | 9 | /// The type of the concatenated expression's subexpressions. 10 | public typealias Subexpression = SubexpressionRealisation.ExpressionType 11 | 12 | // See protocol. 13 | public init(of expression: HomogeneousConcatenatedExpression) { 14 | realisationsOfSubexpressions = expression.subexpressions.map(SubexpressionRealisation.init) 15 | } 16 | 17 | /// The realisations of the subexpressions. 18 | public let realisationsOfSubexpressions: [SubexpressionRealisation] 19 | 20 | // See protocol. 21 | public var pattern: HomogeneousConcatenation { 22 | .init(realisationsOfSubexpressions.map { $0.pattern }) 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /Sources/RegexKit/Concatenated/Homogenous Concatenated Expression.swift: -------------------------------------------------------------------------------- 1 | // PatternKit © 2017–21 Constantino Tsarouhas 2 | 3 | import DepthKit 4 | 5 | /// An expression that concatenates two or more subexpressions of the same type. 6 | public struct HomogeneousConcatenatedExpression { 7 | 8 | /// Creates a homogeneous concatenated expression with given subexpressions. 9 | /// 10 | /// - Parameter firstExpression: The first subexpression. 11 | /// - Parameter otherExpressions: The other subexpressions. 12 | public init(_ firstExpression: Subexpression, _ otherExpressions: Subexpression...) { 13 | self.subexpressions = [firstExpression] + otherExpressions 14 | } 15 | 16 | /// Creates a homogeneous concatenated expression with given subexpressions. 17 | /// 18 | /// - Requires: `subexpressions` contains at least two subexpressions. 19 | /// 20 | /// - Parameter subexpressions: The subexpressions. 21 | public init(_ subexpressions: [Subexpression]) { 22 | precondition(subexpressions.count >= 2, "Fewer than 2 subexpressions in concatenation") 23 | self.subexpressions = subexpressions 24 | } 25 | 26 | /// The concatenated subexpressions. 27 | /// 28 | /// - Invariant: `subexpressions` contains at least two subexpressions. 29 | public var subexpressions: [Subexpression] { 30 | willSet { precondition(newValue.count >= 2, "Fewer than 2 subexpressions in concatenation") } 31 | } 32 | 33 | } 34 | 35 | extension HomogeneousConcatenatedExpression : Expression { 36 | 37 | public enum Index : Equatable { 38 | 39 | /// A position of a symbol in a subexpression. 40 | /// 41 | /// - Invariant: `subexpressionIndex` is an index to a subexpression. 42 | /// - Invariant: `innerIndex` is an index to a symbol in the subexpression. 43 | /// 44 | /// - Parameter subexpressionIndex: The position of the subexpression. 45 | /// - Parameter innerIndex: The position of the symbol within the subexpression. 46 | case inSubexpression(subexpressionIndex: Int, innerIndex: Subexpression.Index) 47 | 48 | /// The position after the last symbol. 49 | case end 50 | 51 | } 52 | 53 | public var startIndex: Index { 54 | 55 | for (subexpressionIndex, subexpression) in subexpressions.enumerated() { 56 | if let firstIndex = subexpression.indices.first { 57 | return .inSubexpression(subexpressionIndex: subexpressionIndex, innerIndex: firstIndex) 58 | } 59 | } 60 | 61 | return .end 62 | 63 | } 64 | 65 | public var endIndex: Index { .end } 66 | 67 | public subscript (index: Index) -> SymbolProtocol { 68 | guard case .inSubexpression(subexpressionIndex: let subexpressionIndex, innerIndex: let innerIndex) = index else { indexOutOfBounds } 69 | return subexpressions[subexpressionIndex][innerIndex] 70 | } 71 | 72 | public func index(before index: Index) -> Index { 73 | 74 | func lastIndexInSubexpressionPrecedingSubexpression(at subexpressionIndex: Int) -> Index { 75 | 76 | let remainingSubexpressions = subexpressions[.. subexpression.startIndex { 94 | return .inSubexpression(subexpressionIndex: subexpressionIndex, innerIndex: subexpression.index(before: innerIndex)) 95 | } else { 96 | return lastIndexInSubexpressionPrecedingSubexpression(at: subexpressionIndex) 97 | } 98 | 99 | case .end: 100 | return lastIndexInSubexpressionPrecedingSubexpression(at: subexpressions.endIndex) 101 | 102 | } 103 | 104 | } 105 | 106 | public func index(after index: Index) -> Index { 107 | 108 | guard case .inSubexpression(subexpressionIndex: let subexpressionIndex, innerIndex: let innerIndex) = index else { indexOutOfBounds } 109 | 110 | let subexpression = subexpressions[subexpressionIndex] 111 | let nextInnerIndex = subexpression.index(after: innerIndex) 112 | if nextInnerIndex < subexpression.endIndex { 113 | return .inSubexpression(subexpressionIndex: subexpressionIndex, innerIndex: nextInnerIndex) 114 | } 115 | 116 | let nextSubexpressionIndex = subexpressions.index(after: subexpressionIndex) 117 | 118 | for (subexpressionIndex, subexpression) in zip(subexpressions.indices[nextSubexpressionIndex...], subexpressions[nextSubexpressionIndex...]) { 119 | if let firstIndex = subexpression.indices.first { 120 | return .inSubexpression(subexpressionIndex: subexpressionIndex, innerIndex: firstIndex) 121 | } 122 | } 123 | 124 | return .end 125 | 126 | } 127 | 128 | public var bindingClass: BindingClass { .concatenation } 129 | 130 | } 131 | 132 | extension HomogeneousConcatenatedExpression.Index : Comparable { 133 | public static func <(smallerIndex: Self, greaterIndex: Self) -> Bool { 134 | switch (smallerIndex, greaterIndex) { 135 | 136 | case (.inSubexpression(subexpressionIndex: let smallerSubexpressionIndex, innerIndex: let smallerInnerIndex), .inSubexpression(subexpressionIndex: let greaterSubexpressionIndex, innerIndex: let greaterInnerIndex)): 137 | return (smallerSubexpressionIndex, smallerInnerIndex) < (greaterSubexpressionIndex, greaterInnerIndex) 138 | 139 | case (.inSubexpression, .end): 140 | return true 141 | 142 | case (.end, _): 143 | return false 144 | 145 | } 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /Sources/RegexKit/Core/Binding Class.swift: -------------------------------------------------------------------------------- 1 | // PatternKit © 2017–21 Constantino Tsarouhas 2 | 3 | /// A categorisation that indicates how an expression's elements interact with elements in a superexpression. 4 | /// 5 | /// An expression of a higher binding class binds its symbols tigher than an expression of a lower binding class. When a subexpression has a lower (or equal) binding class than the environment in its superexpression, the superexpression must wrap the subexpression's symbols with noncapturing grouping symbols. 6 | public enum BindingClass : Int, Equatable { 7 | 8 | /// The binding class of alternations. 9 | /// 10 | /// This is the lowest binding class. 11 | case alternation 12 | 13 | /// The binding class of concatenations. 14 | case concatenation 15 | 16 | /// The binding class of quantified expressions (the subexpression and associated quantifier). 17 | case quantified 18 | 19 | /// The binding class of atomic expressions, i.e., expressions whose symbols form a lexically indivisible unit. 20 | /// 21 | /// This is the highest binding class. 22 | case atomic 23 | 24 | } 25 | 26 | extension BindingClass : Comparable { 27 | public static func <(lowerClass: Self, higherClass: Self) -> Bool { 28 | lowerClass.rawValue < higherClass.rawValue 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Sources/RegexKit/Core/Expression/Bounded Unary Expression.swift: -------------------------------------------------------------------------------- 1 | // PatternKit © 2017–21 Constantino Tsarouhas 2 | 3 | import DepthKit 4 | 5 | /// A unary expression that is a sequence consisting of a leading boundary symbol, the subexpression's symbols, and a trailing boundary symbol. 6 | public protocol BoundedUnaryExpression : UnaryExpression { 7 | 8 | /// The symbol leading the subexpression. 9 | static var leadingBoundarySymbol: SymbolProtocol { get } 10 | 11 | /// The symbol trailing the subexpression. 12 | static var trailingBoundarySymbol: SymbolProtocol { get } 13 | 14 | } 15 | 16 | extension BoundedUnaryExpression where Index == BoundedUnaryExpressionIndex { 17 | 18 | public var startIndex: Index { .leadingBoundary } 19 | 20 | public var endIndex: Index { .end } 21 | 22 | public subscript (index: Index) -> SymbolProtocol { 23 | switch index { 24 | case .leadingBoundary: return Self.leadingBoundarySymbol 25 | case .inSubexpression(innerIndex: let index): return subexpression[index] 26 | case .trailingBoundary: return Self.trailingBoundarySymbol 27 | case .end: indexOutOfBounds 28 | } 29 | } 30 | 31 | public func index(before index: Index) -> Index { 32 | switch index { 33 | 34 | case .leadingBoundary: 35 | indexOutOfBounds 36 | 37 | case .inSubexpression(innerIndex: let index): 38 | guard index > subexpression.startIndex else { return .leadingBoundary } 39 | return .inSubexpression(innerIndex: subexpression.index(before: index)) 40 | 41 | case .trailingBoundary: 42 | guard let lastIndex = subexpression.indices.last else { return .leadingBoundary } 43 | return .inSubexpression(innerIndex: lastIndex) 44 | 45 | case .end: 46 | return .trailingBoundary 47 | 48 | } 49 | } 50 | 51 | public func index(after index: Index) -> Index { 52 | switch index { 53 | 54 | case .leadingBoundary: 55 | guard let firstIndex = subexpression.indices.first else { return .trailingBoundary } 56 | return .inSubexpression(innerIndex: firstIndex) 57 | 58 | case .inSubexpression(innerIndex: let index): 59 | let nextIndex = subexpression.index(after: index) 60 | guard nextIndex < subexpression.endIndex else { return .trailingBoundary } 61 | return .inSubexpression(innerIndex: nextIndex) 62 | 63 | case .trailingBoundary: 64 | return .end 65 | 66 | case .end: 67 | indexOutOfBounds 68 | 69 | } 70 | } 71 | 72 | public var bindingClass: BindingClass { .atomic } 73 | 74 | } 75 | 76 | public enum BoundedUnaryExpressionIndex : Equatable { 77 | 78 | /// The position of the leading boundary. 79 | case leadingBoundary 80 | 81 | /// A position of a symbol in the subexpression. 82 | /// 83 | /// - Invariant: `innerIndex` is an index to a symbol in the subexpression. 84 | /// 85 | /// - Parameter innerIndex: The position of the symbol within the subexpression. 86 | case inSubexpression(innerIndex: Subexpression.Index) 87 | 88 | /// The position of the trailing boundary. 89 | case trailingBoundary 90 | 91 | /// The position after the last symbol. 92 | case end 93 | 94 | } 95 | 96 | extension BoundedUnaryExpressionIndex : Comparable { 97 | public static func <(smallerIndex: Self, greaterIndex: Self) -> Bool { 98 | switch (smallerIndex, greaterIndex) { 99 | case (.leadingBoundary, .leadingBoundary): return false 100 | case (.leadingBoundary, _): return true 101 | case (.inSubexpression, .leadingBoundary): return false 102 | case (.inSubexpression(innerIndex: let smallerIndex), .inSubexpression(innerIndex: let greaterIndex)): return smallerIndex < greaterIndex 103 | case (.inSubexpression, _): return true 104 | case (.trailingBoundary, .end): return true 105 | case (.trailingBoundary, _): return false 106 | case (.end, _): return false 107 | } 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /Sources/RegexKit/Core/Expression/Expression.swift: -------------------------------------------------------------------------------- 1 | // PatternKit © 2017–21 Constantino Tsarouhas 2 | 3 | /// A regular expression; a sequence of symbols that expresses a pattern that matches over strings. 4 | /// 5 | /// Whereas a pattern type is responsible for defining particular matching behaviour, an expression is responsible for having a specific syntax. Most types of expression can be serialised for most or all regular expression languages, but some may be specific for one or two languages. Since most languages differ mostly in how specific symbols are represented, `Expression` values are language-agnostic, and the language-specific serialisation of each symbol is left to the implementation of the symbol. 6 | public protocol Expression : BidirectionalCollection where Element == SymbolProtocol { 7 | 8 | /// The binding class of the expression. 9 | var bindingClass: BindingClass { get } 10 | 11 | } 12 | 13 | extension Expression { 14 | 15 | /// Returns a serialisation of the expression in a given language. 16 | /// 17 | /// - Throws: An error if the expression can't be serialised. 18 | /// 19 | /// - Parameter language: The language in which to serialise the expression. 20 | public func serialisation(language: Language) throws -> String { 21 | try map { 22 | try $0.serialisation(language: language) 23 | }.joined() 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /Sources/RegexKit/Core/Expression/Postfix Operator Expression.swift: -------------------------------------------------------------------------------- 1 | // PatternKit © 2017–21 Constantino Tsarouhas 2 | 3 | import DepthKit 4 | 5 | /// A unary expression that is a sequence consisting of an optional leading boundary symbol, the subexpression's symbols, an optional trailing boundary symbol, and a postfix operator symbol. 6 | /// 7 | /// The boundary symbols are added if the subexpression is of a lower binding class than the unary expression. 8 | public protocol PostfixOperatorExpression : UnaryExpression { 9 | 10 | /// The symbol leading the subexpression. 11 | static var leadingBoundarySymbol: SymbolProtocol { get } 12 | 13 | /// The symbol trailing the subexpression. 14 | static var trailingBoundarySymbol: SymbolProtocol { get } 15 | 16 | /// The symbol that represents the postfix operator. 17 | var postfixOperatorSymbol: SymbolProtocol { get } 18 | 19 | } 20 | 21 | extension PostfixOperatorExpression where Index == PostfixOperatorExpressionIndex { 22 | 23 | public var startIndex: Index { 24 | 25 | if groupsSubexpression { 26 | return .leadingBoundary 27 | } 28 | 29 | return .inSubexpression(innerIndex: subexpression.startIndex) // Empty subexpressions are grouped 30 | 31 | } 32 | 33 | public var endIndex: Index { .end } 34 | 35 | public subscript (index: Index) -> SymbolProtocol { 36 | switch index { 37 | case .leadingBoundary: return Self.leadingBoundarySymbol 38 | case .inSubexpression(innerIndex: let index): return subexpression[index] 39 | case .trailingBoundary: return Self.trailingBoundarySymbol 40 | case .postfixOperator: return postfixOperatorSymbol 41 | case .end: indexOutOfBounds 42 | } 43 | } 44 | 45 | public func index(before index: Index) -> Index { 46 | 47 | var indexBeforeSubexpression: Index { 48 | guard groupsSubexpression else { indexOutOfBounds } 49 | return .leadingBoundary 50 | } 51 | 52 | var lastIndexOfSubexpression: Index { 53 | guard let lastIndex = subexpression.indices.last else { return indexBeforeSubexpression } 54 | return .inSubexpression(innerIndex: lastIndex) 55 | } 56 | 57 | switch index { 58 | 59 | case .leadingBoundary: 60 | indexOutOfBounds 61 | 62 | case .inSubexpression(innerIndex: let innerIndex): 63 | guard innerIndex > subexpression.startIndex else { return indexBeforeSubexpression } 64 | return .inSubexpression(innerIndex: subexpression.index(before: innerIndex)) 65 | 66 | case .trailingBoundary: 67 | return lastIndexOfSubexpression 68 | 69 | case .postfixOperator: 70 | return groupsSubexpression ? .trailingBoundary : lastIndexOfSubexpression 71 | 72 | case .end: 73 | return .postfixOperator 74 | 75 | } 76 | 77 | } 78 | 79 | public func index(after index: Index) -> Index { 80 | switch index { 81 | 82 | case .leadingBoundary: 83 | guard let firstIndex = subexpression.indices.first else { return .trailingBoundary } 84 | return .inSubexpression(innerIndex: firstIndex) 85 | 86 | case .inSubexpression(innerIndex: let index): 87 | let nextIndex = subexpression.index(after: index) 88 | guard nextIndex < subexpression.endIndex else { return groupsSubexpression ? .trailingBoundary : .postfixOperator } 89 | return .inSubexpression(innerIndex: nextIndex) 90 | 91 | case .trailingBoundary: 92 | return .postfixOperator 93 | 94 | case .postfixOperator: 95 | return .end 96 | 97 | case .end: 98 | indexOutOfBounds 99 | 100 | } 101 | } 102 | 103 | private var groupsSubexpression: Bool { 104 | subexpression.bindingClass < self.bindingClass || subexpression.isEmpty 105 | } 106 | 107 | } 108 | 109 | public enum PostfixOperatorExpressionIndex : Equatable { 110 | 111 | /// The position of the leading boundary. 112 | case leadingBoundary 113 | 114 | /// A position of a symbol in the subexpression. 115 | /// 116 | /// - Invariant: `innerIndex` is an index to a symbol in the subexpression. 117 | /// 118 | /// - Parameter innerIndex: The position of the symbol within the subexpression. 119 | case inSubexpression(innerIndex: Subexpression.Index) 120 | 121 | /// The position of the trailing boundary. 122 | case trailingBoundary 123 | 124 | /// The position of the postfix operator. 125 | case postfixOperator 126 | 127 | /// The position after the last symbol. 128 | case end 129 | 130 | } 131 | 132 | extension PostfixOperatorExpressionIndex : Comparable { 133 | public static func <(smallerIndex: Self, greaterIndex: Self) -> Bool { 134 | switch (smallerIndex, greaterIndex) { 135 | case (.leadingBoundary, .leadingBoundary): return false 136 | case (.leadingBoundary, _): return true 137 | case (.inSubexpression, .leadingBoundary): return false 138 | case (.inSubexpression(innerIndex: let smallerIndex), .inSubexpression(innerIndex: let greaterIndex)): return smallerIndex < greaterIndex 139 | case (.inSubexpression, _): return true 140 | case (.trailingBoundary, .postfixOperator): return true 141 | case (.trailingBoundary, .end): return true 142 | case (.trailingBoundary, _): return false 143 | case (.postfixOperator, .end): return true 144 | case (.postfixOperator, _): return false 145 | case (.end, _): return false 146 | } 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /Sources/RegexKit/Core/Expression/Unary Expression.swift: -------------------------------------------------------------------------------- 1 | // PatternKit © 2017–21 Constantino Tsarouhas 2 | 3 | /// An expression that contains exactly one subexpression. 4 | public protocol UnaryExpression : Expression { 5 | 6 | /// The type of subexpression. 7 | associatedtype Subexpression : Expression 8 | 9 | /// The subexpression. 10 | var subexpression: Subexpression { get } 11 | 12 | } 13 | -------------------------------------------------------------------------------- /Sources/RegexKit/Core/Language.swift: -------------------------------------------------------------------------------- 1 | // PatternKit © 2017–21 Constantino Tsarouhas 2 | 3 | /// A grammar for serialising and deserialising expressions. 4 | public enum Language : Hashable { 5 | 6 | /// The regular expression language in ECMAScript (ECMA-262 and ISO/IEC 16262). 7 | case ecmaScript 8 | 9 | /// The Perl Compatible Regular Expressions (PCRE) language. 10 | case perlCompatibleREs 11 | 12 | } 13 | -------------------------------------------------------------------------------- /Sources/RegexKit/Core/Realisation.swift: -------------------------------------------------------------------------------- 1 | // PatternKit © 2017–21 Constantino Tsarouhas 2 | 3 | import PatternKitCore 4 | 5 | /// A value that converts an expression into a pattern. 6 | /// 7 | /// Although not specified by this protocol, a realisation on a concrete pattern type may provide additional functionality such as mapping symbols to parts of patterns. 8 | public protocol Realisation { 9 | 10 | /// The type of the realised expression. 11 | associatedtype ExpressionType : Expression 12 | 13 | /// The type of the pattern that results after realising the expression. 14 | associatedtype PatternType : Pattern where PatternType.Subject == String 15 | 16 | /// Realises given expression. 17 | /// 18 | /// - Parameter expression: The expression to realise. 19 | init(of expression: ExpressionType) 20 | 21 | /// The resulting pattern. 22 | var pattern: PatternType { get } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /Sources/RegexKit/Core/Symbol/Noncapturing Group Boundary Symbol.swift: -------------------------------------------------------------------------------- 1 | // PatternKit © 2017–21 Constantino Tsarouhas 2 | 3 | public enum NoncapturingGroupBoundarySymbol : SymbolProtocol { 4 | 5 | /// The leading boundary symbol. 6 | case leading 7 | 8 | /// The trailing boundary symbol. 9 | case trailing 10 | 11 | // See protocol. 12 | public func serialisation(language: Language) -> String { 13 | switch self { 14 | case .leading: return "(?:" 15 | case .trailing: return ")" 16 | } 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /Sources/RegexKit/Core/Symbol/Symbol.swift: -------------------------------------------------------------------------------- 1 | // PatternKit © 2017–21 Constantino Tsarouhas 2 | 3 | /// A indivisible sequence of characters that forms a lexical unit in an expression. 4 | /// 5 | /// Note that a symbol value doesn't only carry a specific serialisation for a set of languages, but also a precise lexical meaning. It is, for example, incorrect to represent an alternation delimiter with a literal symbol serialising to `|`. 6 | public protocol SymbolProtocol { 7 | 8 | /// Returns a serialisation of the symbol in a given language. 9 | /// 10 | /// - Throws: An error if the symbol can't be serialised. 11 | /// 12 | /// - Parameter language: The language in which to serialise the symbol. 13 | func serialisation(language: Language) throws -> String 14 | 15 | } 16 | 17 | /// A general error relating to serialising symbols. 18 | public enum SymbolSerialisationError : Error { 19 | 20 | /// The symbol cannot be serialised in the given language. 21 | case unsupportedByLanguage 22 | 23 | } 24 | -------------------------------------------------------------------------------- /Sources/RegexKit/Empty/Empty Expression.swift: -------------------------------------------------------------------------------- 1 | // PatternKit © 2017–21 Constantino Tsarouhas 2 | 3 | import DepthKit 4 | 5 | /// An expression with no symbols. 6 | public struct EmptyExpression { 7 | 8 | /// Creates an empty expression. 9 | public init() {} 10 | 11 | } 12 | 13 | extension EmptyExpression : Expression { 14 | 15 | public enum Index : Equatable { 16 | case end 17 | } 18 | 19 | public var startIndex: Index { .end } 20 | public var endIndex: Index { .end } 21 | public subscript (index: Index) -> SymbolProtocol { indexOutOfBounds } 22 | public func index(before index: Index) -> Index { indexOutOfBounds } 23 | public func index(after index: Index) -> Index { indexOutOfBounds } 24 | public var bindingClass: BindingClass { .atomic } 25 | 26 | } 27 | 28 | extension EmptyExpression.Index : Comparable { 29 | public static func <(smallerIndex: Self, greaterIndex: Self) -> Bool { 30 | false 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Sources/RegexKit/Literal/Literal Expression Realisation.swift: -------------------------------------------------------------------------------- 1 | // PatternKit © 2017–21 Constantino Tsarouhas 2 | 3 | import DepthKit 4 | 5 | public struct LiteralExpressionRealisation { 6 | 7 | // TODO 8 | 9 | } 10 | -------------------------------------------------------------------------------- /Sources/RegexKit/Literal/Literal Expression.swift: -------------------------------------------------------------------------------- 1 | // PatternKit © 2017–21 Constantino Tsarouhas 2 | 3 | import DepthKit 4 | import struct Foundation.CharacterSet 5 | 6 | /// An expression that expresses a literal pattern. 7 | public struct LiteralExpression { 8 | 9 | /// Creates a pattern that matches an exact string. 10 | /// 11 | /// - Requires: `literal` is non-empty. 12 | /// 13 | /// - Parameter literal: The string that the pattern matches exactly. 14 | public init(_ literal: String) { 15 | precondition(!literal.isEmpty, "Empty literal") 16 | self.literal = literal 17 | } 18 | 19 | /// The string that the pattern matches exactly. 20 | /// 21 | /// - Invariant: `literal` is non-empty. 22 | public var literal: String { 23 | willSet { precondition(!newValue.isEmpty, "Empty literal") } 24 | } 25 | 26 | /// A symbol that represents a literal character in a literal expression. 27 | public struct Symbol { 28 | 29 | /// The character represented by the symbol. 30 | var character: Character 31 | 32 | } 33 | 34 | } 35 | 36 | extension LiteralExpression : Expression { 37 | public typealias Index = String.Index 38 | public var startIndex: Index { literal.startIndex } 39 | public var endIndex: Index { literal.endIndex } 40 | public subscript (index: Index) -> SymbolProtocol { Symbol(character: literal[index]) } 41 | public func index(before index: Index) -> Index { literal.index(before: index) } 42 | public func index(after index: Index) -> Index { literal.index(after: index) } 43 | public var bindingClass: BindingClass { .concatenation } 44 | } 45 | 46 | extension LiteralExpression.Symbol : SymbolProtocol { 47 | 48 | public func serialisation(language: Language) -> String { 49 | 50 | let metacharacters: Set = ["[", "\\", "^", "$", ".", "|", "?", "*", "+", "(", ")", "{"] 51 | guard !metacharacters.contains(character) else { return "\\\(character)" } 52 | 53 | let unicodeScalars = character.unicodeScalars 54 | guard let scalar = unicodeScalars.first, unicodeScalars.count == 1 else { return String(character) } 55 | 56 | return serialiseGeneralScalar(scalar, language: language) 57 | 58 | } 59 | 60 | } 61 | 62 | /// Serialises given Unicode scalar in given language, escaping it if required in all contexts. 63 | /// 64 | /// This function does not escape metacharacters (e.g., backslash) as they have lexical meaning dependent on context. 65 | /// 66 | /// - Parameter scalar: The Unicode scalar to serialise. 67 | /// - Parameter language: The language in which to serialise the scalar. 68 | internal func serialiseGeneralScalar(_ scalar: UnicodeScalar, language: Language) -> String { 69 | 70 | guard CharacterSet.controlCharacters.contains(scalar) else { return String(scalar) } 71 | 72 | switch (scalar, language) { 73 | case ("\n", _): return "\\n" 74 | case ("\r", _): return "\\r" 75 | case ("\t", _): return "\\t" 76 | case ("\u{7}", .perlCompatibleREs): return "\\a" 77 | case ("\u{1A}", .perlCompatibleREs): return "\\e" 78 | case ("\u{0C}", _): return "\\f" 79 | case ("\u{0B}", .ecmaScript): return "\\v" 80 | default: break 81 | } 82 | 83 | let unpaddedScalarValue = String(scalar.value, radix: 16, uppercase: true) 84 | let paddingLength = 4 - unpaddedScalarValue.count 85 | let paddedScalarValue = paddingLength > 0 86 | ? String(repeating: "0", count: paddingLength) + unpaddedScalarValue 87 | : unpaddedScalarValue 88 | 89 | switch language { 90 | case .perlCompatibleREs: return "\\o{\(paddedScalarValue)}" 91 | case .ecmaScript: return "\\u\(paddedScalarValue)" 92 | } 93 | 94 | } 95 | -------------------------------------------------------------------------------- /Sources/RegexKit/Repeating/Eagerly Repeating Expression.swift: -------------------------------------------------------------------------------- 1 | // PatternKit © 2017–21 Constantino Tsarouhas 2 | 3 | import DepthKit 4 | 5 | /// An expression that expresses a repeating pattern. 6 | public struct EagerlyRepeatingExpression { 7 | 8 | /// Creates a repeating expression with given subexpression. 9 | /// 10 | /// - Parameter comment: The expression that expresses the repeated pattern. 11 | public init(_ subexpression: Subexpression, multiplicityRange: MultiplicityRange, possessive: Bool = false) { 12 | self.subexpression = subexpression 13 | self.multiplicityRange = multiplicityRange 14 | self.possessive = possessive 15 | } 16 | 17 | /// The expression that expresses the repeated pattern. 18 | public var subexpression: Subexpression 19 | 20 | /// The multiplicity range. 21 | public var multiplicityRange: MultiplicityRange 22 | 23 | /// Whether the repeating pattern is possessive. 24 | var possessive: Bool 25 | 26 | public struct QuantifierSymbol { 27 | 28 | /// The multiplicity range. 29 | var multiplicityRange: MultiplicityRange 30 | 31 | /// Whether the repeating pattern is possessive. 32 | /// 33 | /// - Invariant: If `true`, the multiplicity contains at least two members. 34 | var possessive: Bool 35 | 36 | } 37 | 38 | } 39 | 40 | extension EagerlyRepeatingExpression : PostfixOperatorExpression { 41 | public typealias Index = PostfixOperatorExpressionIndex 42 | public static var leadingBoundarySymbol: SymbolProtocol { NoncapturingGroupBoundarySymbol.leading } 43 | public static var trailingBoundarySymbol: SymbolProtocol { NoncapturingGroupBoundarySymbol.trailing } 44 | public var postfixOperatorSymbol: SymbolProtocol { QuantifierSymbol(multiplicityRange: multiplicityRange, possessive: possessive) } 45 | public var bindingClass: BindingClass { .quantified } 46 | } 47 | 48 | extension EagerlyRepeatingExpression.QuantifierSymbol : SymbolProtocol { 49 | public func serialisation(language: Language) throws -> String { 50 | if possessive { 51 | guard [.perlCompatibleREs].contains(language) else { throw SymbolSerialisationError.unsupportedByLanguage } 52 | return "\(multiplicityRange.serialisation)+" 53 | } else { 54 | return multiplicityRange.serialisation 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /Sources/RegexKit/Repeating/Lazily Repeating Expression.swift: -------------------------------------------------------------------------------- 1 | // PatternKit © 2017–21 Constantino Tsarouhas 2 | 3 | import DepthKit 4 | 5 | /// An expression that expresses a repeating pattern. 6 | public struct LazilyRepeatingExpression { 7 | 8 | /// Creates a repeating expression with given subexpression. 9 | /// 10 | /// - Parameter comment: The expression that expresses the repeated pattern. 11 | public init(_ subexpression: Subexpression, multiplicityRange: MultiplicityRange) { 12 | self.subexpression = subexpression 13 | self.multiplicityRange = multiplicityRange 14 | } 15 | 16 | /// The expression that expresses the repeated pattern. 17 | public var subexpression: Subexpression 18 | 19 | /// The multiplicity range. 20 | public var multiplicityRange: MultiplicityRange 21 | 22 | public struct QuantifierSymbol { 23 | 24 | /// The multiplicity range. 25 | var multiplicityRange: MultiplicityRange 26 | 27 | } 28 | 29 | } 30 | 31 | extension LazilyRepeatingExpression : PostfixOperatorExpression { 32 | public typealias Index = PostfixOperatorExpressionIndex 33 | public static var leadingBoundarySymbol: SymbolProtocol { NoncapturingGroupBoundarySymbol.leading } 34 | public static var trailingBoundarySymbol: SymbolProtocol { NoncapturingGroupBoundarySymbol.trailing } 35 | public var postfixOperatorSymbol: SymbolProtocol { QuantifierSymbol(multiplicityRange: multiplicityRange) } 36 | public var bindingClass: BindingClass { .quantified } 37 | } 38 | 39 | extension LazilyRepeatingExpression.QuantifierSymbol : SymbolProtocol { 40 | public func serialisation(language: Language) throws -> String { 41 | "\(multiplicityRange.serialisation)?" 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /Sources/RegexKit/Repeating/Multiplication Range.swift: -------------------------------------------------------------------------------- 1 | // PatternKit © 2017–21 Constantino Tsarouhas 2 | 3 | import DepthKit 4 | 5 | public enum MultiplicityRange : Equatable { 6 | 7 | /// A closed range. 8 | /// 9 | /// - Invariant: The lower bound is nonnegative. 10 | /// - Invariant: The upper bound is greater than zero if the lower bound is zero. 11 | /// 12 | /// - Parameter 1: The closed range. 13 | case closed(ClosedRange) 14 | 15 | /// A partial, lower-bounded range. 16 | /// 17 | /// - Invariant: The lower bound is nonnegative. 18 | /// 19 | /// - Parameter 1: The partial range. 20 | case partial(PartialRangeFrom) 21 | 22 | internal var serialisation: String { 23 | precondition(lowerBound >= 0, "Negative multiplicity") 24 | switch self { 25 | case 0...1: return "?" 26 | case 0...: return "*" 27 | case 1...: return "+" 28 | case .closed(let range) where range.count == 1: return "{\(range.lowerBound)}" 29 | case .closed(let range): return "{\(range.lowerBound),\(range.upperBound)}" 30 | case .partial(let range): return "{\(range.lowerBound),}" 31 | } 32 | } 33 | 34 | /// The lower bound. 35 | public var lowerBound: Int { 36 | switch self { 37 | case .closed(let range): return range.lowerBound 38 | case .partial(let range): return range.lowerBound 39 | } 40 | } 41 | 42 | /// The upper bound, if any. 43 | public var upperBound: Int? { 44 | switch self { 45 | case .closed(let range): return range.upperBound 46 | case .partial: return nil 47 | } 48 | } 49 | 50 | } 51 | 52 | public func ... (lowerBound: Int, upperBound: Int) -> MultiplicityRange { 53 | .closed(lowerBound...upperBound) 54 | } 55 | 56 | public postfix func ... (lowerBound: Int) -> MultiplicityRange { 57 | .partial(lowerBound...) 58 | } 59 | 60 | extension PartialRangeFrom : Equatable { 61 | public static func == (firstRange: PartialRangeFrom, otherRange: PartialRangeFrom) -> Bool { 62 | firstRange.lowerBound == otherRange.lowerBound 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /Tests/LinuxMain.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import PatternKitTests 3 | 4 | XCTMain([ 5 | testCase(PatternKitTests.allTests), 6 | ]) 7 | -------------------------------------------------------------------------------- /Tests/PatternKitTests/Alternations Test Case.swift: -------------------------------------------------------------------------------- 1 | // PatternKit © 2017–21 Constantino Tsarouhas 2 | 3 | import PatternKit 4 | import XCTest 5 | 6 | class AlternationsTestCase : XCTestCase { 7 | 8 | func testEmptyLiterals() { 9 | 10 | XCTAssert((Literal("") | Literal("")).hasMatches(over: "")) 11 | XCTAssert((Literal("a") | Literal("")).hasMatches(over: "")) 12 | XCTAssert((Literal("") | Literal("a")).hasMatches(over: "")) 13 | XCTAssert(!(Literal("a") | Literal("a")).hasMatches(over: "")) 14 | 15 | XCTAssert((Literal("a") | Literal("")).hasMatches(over: "a")) 16 | XCTAssert((Literal("") | Literal("a")).hasMatches(over: "a")) 17 | XCTAssert(!(Literal("") | Literal("")).hasMatches(over: "a")) 18 | 19 | } 20 | 21 | func testCharacterLiterals() { 22 | XCTAssert(("a" | "b").hasMatches(over: "a")) 23 | XCTAssert(("b" | "a").hasMatches(over: "a")) 24 | XCTAssert(!("b" | "c").hasMatches(over: "a")) 25 | } 26 | 27 | func testStringLiterals() { 28 | XCTAssert((Literal("abba") | Literal("baab")).hasMatches(over: "abba")) 29 | XCTAssert((Literal("baab") | Literal("abba")).hasMatches(over: "abba")) 30 | XCTAssert(!(Literal("baab") | Literal("caac")).hasMatches(over: "abba")) 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /Tests/PatternKitTests/Assertions/Backward Assertions Test Case.swift: -------------------------------------------------------------------------------- 1 | // PatternKit © 2017–21 Constantino Tsarouhas 2 | 3 | import PatternKit 4 | import XCTest 5 | 6 | class BackwardAssertionsTestCase : XCTestCase { 7 | 8 | func testSimpleAssertion() { 9 | XCTAssert((Literal("Hello") • ?<=Literal("Hello")).hasMatches(over: "Hello")) 10 | XCTAssert((Literal("Hello") • ?<=Literal("Hello")).hasMatches(over: "Hello", direction: .backward)) 11 | } 12 | 13 | func testLeadingEmptyAssertion() { 14 | XCTAssert((?<=Literal("") • Literal("Hello")).hasMatches(over: "Hello")) 15 | XCTAssert((?<=Literal("") • Literal("Hello")).hasMatches(over: "Hello", direction: .backward)) 16 | } 17 | 18 | func testMiddleEmptyAssertion() { 19 | XCTAssert((Literal("Hel") • ?<=Literal("") • Literal("lo")).hasMatches(over: "Hello")) 20 | XCTAssert((Literal("Hel") • ?<=Literal("") • Literal("lo")).hasMatches(over: "Hello", direction: .backward)) 21 | } 22 | 23 | func testTrailingEmptyAssertion() { 24 | XCTAssert((Literal("Hello") • ?<=Literal("")).hasMatches(over: "Hello")) 25 | XCTAssert((Literal("Hello") • ?<=Literal("")).hasMatches(over: "Hello", direction: .backward)) 26 | } 27 | 28 | func testMiddleStringAssertion() { 29 | XCTAssert((Literal("Hel") • ?<=Literal("el") • Literal("lo")).hasMatches(over: "Hello")) 30 | XCTAssert((Literal("Hel") • ?<=Literal("el") • Literal("lo")).hasMatches(over: "Hello", direction: .backward)) 31 | } 32 | 33 | func testTrailingStringAssertion() { 34 | XCTAssert((Literal("Hello") • ?<=Literal("llo")).hasMatches(over: "Hello")) 35 | XCTAssert((Literal("Hello") • ?<=Literal("llo")).hasMatches(over: "Hello", direction: .backward)) 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /Tests/PatternKitTests/Assertions/Forward Assertions Test Case.swift: -------------------------------------------------------------------------------- 1 | // PatternKit © 2017–21 Constantino Tsarouhas 2 | 3 | import PatternKit 4 | import XCTest 5 | 6 | class ForwardAssertionsTestCase : XCTestCase { 7 | 8 | func testStandaloneAssertion() { 9 | let matches = ForwardAssertion(1...9).forwardMatches(enteringFrom: Match(over: [1], direction: .forward)) 10 | guard let match = matches.first, matches.count == 1 else { return XCTFail() } 11 | XCTAssert(match.inputPosition == 0) 12 | } 13 | 14 | func testHeadingEmptyAssertion() { 15 | XCTAssert((?=Literal("") • Literal("Hello")).hasMatches(over: "Hello")) 16 | XCTAssert((?=Literal("") • Literal("Hello")).hasMatches(over: "Hello", direction: .backward)) 17 | } 18 | 19 | func testMiddleEmptyAssertion() { 20 | XCTAssert((Literal("Hel") • ?=Literal("") • Literal("lo")).hasMatches(over: "Hello")) 21 | XCTAssert((Literal("Hel") • ?=Literal("") • Literal("lo")).hasMatches(over: "Hello", direction: .backward)) 22 | } 23 | 24 | func testTrailingEmptyAssertion() { 25 | XCTAssert((Literal("Hello") • ?=Literal("")).hasMatches(over: "Hello")) 26 | XCTAssert((Literal("Hello") • ?=Literal("")).hasMatches(over: "Hello", direction: .backward)) 27 | } 28 | 29 | func testLeadingStringAssertion() { 30 | XCTAssert((?=Literal("Hel") • Literal("Hello")).hasMatches(over: "Hello")) 31 | XCTAssert((?=Literal("Hel") • Literal("Hello")).hasMatches(over: "Hello", direction: .backward)) 32 | } 33 | 34 | func testMiddleStringAssertion() { 35 | XCTAssert((Literal("Hel") • ?=Literal("l") • Literal("lo")).hasMatches(over: "Hello")) 36 | XCTAssert((Literal("Hel") • ?=Literal("l") • Literal("lo")).hasMatches(over: "Hello", direction: .backward)) 37 | } 38 | 39 | func testLeadingAssertion() { 40 | 41 | XCTAssert((ForwardAssertion(1...9) • Literal([1, 2, 3, 4])).hasMatches(over: [1, 2, 3, 4])) 42 | XCTAssert((ForwardAssertion(repeating(1...9, exactly: 2)) • Literal([1, 2, 3, 4])).hasMatches(over: [1, 2, 3, 4])) 43 | XCTAssert((ForwardAssertion(repeating(1...9, exactly: 3)) • Literal([1, 2, 3, 4])).hasMatches(over: [1, 2, 3, 4])) 44 | XCTAssert((ForwardAssertion(repeating(1...9, exactly: 4)) • Literal([1, 2, 3, 4])).hasMatches(over: [1, 2, 3, 4])) 45 | XCTAssert(!(ForwardAssertion(repeating(1...9, exactly: 5)) • Literal([1, 2, 3, 4])).hasMatches(over: [1, 2, 3, 4])) 46 | 47 | XCTAssert((ForwardAssertion(1...9) • Literal([1, 2, 3, 4])).hasMatches(over: [1, 2, 3, 4], direction: .backward)) 48 | XCTAssert((ForwardAssertion(repeating(1...9, exactly: 2)) • Literal([1, 2, 3, 4])).hasMatches(over: [1, 2, 3, 4], direction: .backward)) 49 | XCTAssert((ForwardAssertion(repeating(1...9, exactly: 3)) • Literal([1, 2, 3, 4])).hasMatches(over: [1, 2, 3, 4], direction: .backward)) 50 | XCTAssert((ForwardAssertion(repeating(1...9, exactly: 4)) • Literal([1, 2, 3, 4])).hasMatches(over: [1, 2, 3, 4], direction: .backward)) 51 | XCTAssert(!(ForwardAssertion(repeating(1...9, exactly: 5)) • Literal([1, 2, 3, 4])).hasMatches(over: [1, 2, 3, 4], direction: .backward)) 52 | 53 | } 54 | 55 | // TODO 56 | 57 | } 58 | -------------------------------------------------------------------------------- /Tests/PatternKitTests/Assertions/Negated Backward Assertions Test Case.swift: -------------------------------------------------------------------------------- 1 | // PatternKit © 2017–21 Constantino Tsarouhas 2 | 3 | import PatternKit 4 | import XCTest 5 | 6 | class NegatedBackwardAssertionsTestCase : XCTestCase { 7 | 8 | func testSimpleAssertion() { 9 | XCTAssert((Literal("Hello") • ? 51 | 52 | XCTAssert(pattern.hasMatches(over: "a".unicodeScalars)) 53 | XCTAssert(pattern.hasMatches(over: "q".unicodeScalars)) 54 | XCTAssert(pattern.hasMatches(over: "z".unicodeScalars)) 55 | XCTAssert(!pattern.hasMatches(over: "A".unicodeScalars)) 56 | 57 | XCTAssert(pattern.hasMatches(over: "a".unicodeScalars, direction: .backward)) 58 | XCTAssert(pattern.hasMatches(over: "q".unicodeScalars, direction: .backward)) 59 | XCTAssert(pattern.hasMatches(over: "z".unicodeScalars, direction: .backward)) 60 | XCTAssert(!pattern.hasMatches(over: "A".unicodeScalars, direction: .backward)) 61 | 62 | } 63 | 64 | } 65 | -------------------------------------------------------------------------------- /Tests/PatternKitTests/Repeating/Eagerly Repeating Test Case.swift: -------------------------------------------------------------------------------- 1 | // PatternKit © 2017–21 Constantino Tsarouhas 2 | 3 | import PatternKit 4 | import XCTest 5 | 6 | class EagerlyRepeatingTestCase : XCTestCase { 7 | 8 | func testOptionalArrayLiterals() { 9 | XCTAssert(Literal(2)*.hasMatches(over: [2, 2])) 10 | XCTAssert((Literal(1) • Literal(2)* • Literal(3)).hasMatches(over: [1, 2, 2, 3])) 11 | XCTAssert(!(Literal(2)* • Literal(3)).hasMatches(over: [1, 2, 2, 3])) 12 | XCTAssert(!(Literal(1) • Literal(2)*).hasMatches(over: [1, 2, 2, 3])) 13 | } 14 | 15 | func testNonoptionalArrayLiterals() { 16 | XCTAssert(Literal(2)+.hasMatches(over: [2, 2])) 17 | XCTAssert((Literal(1) • Literal(2)+ • Literal(3)).hasMatches(over: [1, 2, 2, 3])) 18 | XCTAssert(!(Literal(2)+ • Literal(3)).hasMatches(over: [1, 2, 2, 3])) 19 | XCTAssert(!(Literal(1) • Literal(2)+).hasMatches(over: [1, 2, 2, 3])) 20 | } 21 | 22 | func testStringLiterals() { 23 | XCTAssert((Literal("a") • Literal("b")+ • Literal("a")).hasMatches(over: "abbbbbba")) 24 | XCTAssert((Literal("a") • Literal("b")* • Literal("a")).hasMatches(over: "abbbbbba")) 25 | XCTAssert((Literal("a")/? • Literal("b")* • Literal("a")).hasMatches(over: "abbbbbba")) 26 | XCTAssert((Literal("a")/? • Literal("b")* • Literal("a")).hasMatches(over: "bbbbbba")) 27 | } 28 | 29 | func testEagerness() { 30 | 31 | let base = "a"+ 32 | let prefix = Token(base) 33 | let suffix = Token(base) 34 | let pattern = prefix • suffix 35 | 36 | let matches = pattern.matches(over: "aaa") 37 | let captures = Array(matches.map { (String($0.captures(for: prefix).first!), String($0.captures(for: suffix).first!)) }) 38 | 39 | XCTAssert(captures[0] == ("aa", "a")) 40 | XCTAssert(captures[1] == ("a", "aa")) 41 | XCTAssert(captures.count == 2) 42 | 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /Tests/PatternKitTests/Repeating/Lazily Repeating Test Case.swift: -------------------------------------------------------------------------------- 1 | // PatternKit © 2017–21 Constantino Tsarouhas 2 | 3 | import PatternKit 4 | import XCTest 5 | 6 | class LazilyRepeatingTestCase : XCTestCase { 7 | 8 | func testOptionalArrayLiterals() { 9 | XCTAssert(Literal(2)*?.hasMatches(over: [2, 2])) 10 | XCTAssert((Literal(1) • Literal(2)*? • Literal(3)).hasMatches(over: [1, 2, 2, 3])) 11 | XCTAssert(!(Literal(2)*? • Literal(3)).hasMatches(over: [1, 2, 2, 3])) 12 | XCTAssert(!(Literal(1) • Literal(2)*?).hasMatches(over: [1, 2, 2, 3])) 13 | } 14 | 15 | func testNonoptionalArrayLiterals() { 16 | XCTAssert(Literal(2)+?.hasMatches(over: [2, 2])) 17 | XCTAssert((Literal(1) • Literal(2)+? • Literal(3)).hasMatches(over: [1, 2, 2, 3])) 18 | XCTAssert(!(Literal(2)+? • Literal(3)).hasMatches(over: [1, 2, 2, 3])) 19 | XCTAssert(!(Literal(1) • Literal(2)+?).hasMatches(over: [1, 2, 2, 3])) 20 | } 21 | 22 | func testStringLiterals() { 23 | XCTAssert((Literal("a") • Literal("b")+? • Literal("a")).hasMatches(over: "abbbbbba")) 24 | XCTAssert((Literal("a") • Literal("b")*? • Literal("a")).hasMatches(over: "abbbbbba")) 25 | XCTAssert((Literal("a")/?? • Literal("b")*? • Literal("a")).hasMatches(over: "abbbbbba")) 26 | XCTAssert((Literal("a")/?? • Literal("b")*? • Literal("a")).hasMatches(over: "bbbbbba")) 27 | } 28 | 29 | func testLaziness() { 30 | 31 | let base = "a"+? 32 | let prefix = Token(base) 33 | let suffix = Token(base) 34 | let pattern = prefix • suffix 35 | 36 | let matches = pattern.matches(over: "aaa") 37 | let captures = Array(matches.map { (String($0.captures(for: prefix).first!), String($0.captures(for: suffix).first!)) }) 38 | 39 | XCTAssert(captures[0] == ("a", "aa")) 40 | XCTAssert(captures[1] == ("aa", "a")) 41 | XCTAssert(captures.count == 2) 42 | 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /Tests/PatternKitTests/Token Test Case.swift: -------------------------------------------------------------------------------- 1 | // PatternKit © 2017–21 Constantino Tsarouhas 2 | 3 | import PatternKit 4 | import XCTest 5 | 6 | class TokenTestCase : XCTestCase { 7 | 8 | func testLiteralToken() { 9 | 10 | let prefix = Literal([1]) 11 | let token = Token(Literal([2, 3])) 12 | let suffix = Literal([4]) 13 | 14 | let pattern = prefix • token • suffix 15 | let matches = pattern.matches(over: [1, 2, 3, 4]) 16 | 17 | guard let match = matches.first, matches.count == 1 else { return XCTFail() } 18 | 19 | let captures = match.captures(for: token) 20 | guard let capture = captures.first, captures.count == 1 else { return XCTFail() } 21 | 22 | XCTAssert(capture == [2, 3]) 23 | 24 | } 25 | 26 | func testBackwardLiteralToken() { 27 | 28 | let prefix = Literal([1]) 29 | let token = Token(Literal([2, 3])) 30 | let suffix = Literal([4]) 31 | 32 | let pattern = prefix • token • suffix 33 | let matches = pattern.backwardMatches(over: [1, 2, 3, 4]) 34 | 35 | guard let match = matches.first, matches.count == 1 else { return XCTFail() } 36 | 37 | let captures = match.captures(for: token) 38 | guard let capture = captures.first, captures.count == 1 else { return XCTFail() } 39 | 40 | XCTAssert(capture == [2, 3]) 41 | 42 | } 43 | 44 | func testCharacterSetToken() { 45 | 46 | let token = Token(("a"..."b") • ("a"..."b")) 47 | let pattern = Literal("a") • token • Literal("a") 48 | let matches = pattern.matches(over: "abba") 49 | 50 | guard let match = matches.first, matches.count == 1 else { return XCTFail() } 51 | 52 | let captures = match.captures(for: token) 53 | guard let capture = captures.first, captures.count == 1 else { return XCTFail() } 54 | 55 | XCTAssert(String(capture) == "bb") 56 | 57 | } 58 | 59 | func testBackwardCharacterSetToken() { 60 | 61 | let token = Token(("a"..."b") • ("a"..."b")) 62 | let pattern = Literal("a") • token • Literal("a") 63 | let matches = pattern.backwardMatches(over: "abba") 64 | 65 | guard let match = matches.first, matches.count == 1 else { return XCTFail() } 66 | 67 | let captures = match.captures(for: token) 68 | guard let capture = captures.first, captures.count == 1 else { return XCTFail() } 69 | 70 | XCTAssert(String(capture) == "bb") 71 | 72 | } 73 | 74 | } 75 | -------------------------------------------------------------------------------- /Tests/PatternKitTests/Wildcards Test Case.swift: -------------------------------------------------------------------------------- 1 | // PatternKit © 2017–21 Constantino Tsarouhas 2 | 3 | import PatternKit 4 | import XCTest 5 | 6 | class WildcardsTestCase : XCTestCase { 7 | 8 | let emptyIntArray: [Int] = [] 9 | 10 | func testSingleWildcard() { 11 | XCTAssert(one().hasMatches(over: [5])) 12 | XCTAssert(!one().hasMatches(over: emptyIntArray)) 13 | XCTAssert(!one().hasMatches(over: [5, 6])) 14 | XCTAssert(one().hasMatches(over: [5], direction: .backward)) 15 | XCTAssert(!one().hasMatches(over: emptyIntArray, direction: .backward)) 16 | XCTAssert(!one().hasMatches(over: [5, 6], direction: .backward)) 17 | } 18 | 19 | func testTwoWildcards() { 20 | XCTAssert((one() • one()).hasMatches(over: [5, 6])) 21 | XCTAssert(!(one() • one()).hasMatches(over: emptyIntArray)) 22 | XCTAssert(!(one() • one()).hasMatches(over: [5])) 23 | XCTAssert(!(one() • one()).hasMatches(over: [5, 6, 7])) 24 | XCTAssert((one() • one()).hasMatches(over: [5, 6], direction: .backward)) 25 | XCTAssert(!(one() • one()).hasMatches(over: emptyIntArray, direction: .backward)) 26 | XCTAssert(!(one() • one()).hasMatches(over: [5], direction: .backward)) 27 | XCTAssert(!(one() • one()).hasMatches(over: [5, 6, 7], direction: .backward)) 28 | } 29 | 30 | func testHelloString() { 31 | XCTAssert((one() • one() • one() • one() • one()).hasMatches(over: "hello")) 32 | XCTAssert(!(one() • one() • one() • one()).hasMatches(over: "hello")) 33 | XCTAssert(!(one() • one() • one() • one() • one() • one()).hasMatches(over: "hello")) 34 | XCTAssert((one() • one() • one() • one() • one()).hasMatches(over: "hello", direction: .backward)) 35 | XCTAssert(!(one() • one() • one() • one()).hasMatches(over: "hello", direction: .backward)) 36 | XCTAssert(!(one() • one() • one() • one() • one() • one()).hasMatches(over: "hello", direction: .backward)) 37 | } 38 | 39 | func testEmptyString() { 40 | XCTAssert(!one().hasMatches(over: "")) 41 | XCTAssert(!(one() • one()).hasMatches(over: "")) 42 | XCTAssert(!one().hasMatches(over: "", direction: .backward)) 43 | XCTAssert(!(one() • one()).hasMatches(over: "", direction: .backward)) 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /Tests/RegexKitTests/Alternation/Alternation Expression Test Case.swift: -------------------------------------------------------------------------------- 1 | // PatternKit © 2017–21 Constantino Tsarouhas 2 | 3 | import RegexKit 4 | import XCTest 5 | 6 | class AlternationExpressionTestCase : XCTestCase { 7 | 8 | func testSimple() { 9 | let expression = AlternationExpression(LiteralExpression("aa"), LiteralExpression("bb")) 10 | XCTAssert(try! expression.serialisation(language: .ecmaScript) == "aa|bb") 11 | XCTAssert(try! expression.serialisation(language: .perlCompatibleREs) == "aa|bb") 12 | } 13 | 14 | func testEmpty() { 15 | 16 | let emptyLeadingExpression = AlternationExpression(EmptyExpression(), LiteralExpression("bb")) 17 | XCTAssert(try! emptyLeadingExpression.serialisation(language: .ecmaScript) == "|bb") 18 | XCTAssert(try! emptyLeadingExpression.serialisation(language: .perlCompatibleREs) == "|bb") 19 | 20 | let emptyTrailingExpression = AlternationExpression(LiteralExpression("aa"), EmptyExpression()) 21 | XCTAssert(try! emptyTrailingExpression.serialisation(language: .ecmaScript) == "aa|") 22 | XCTAssert(try! emptyTrailingExpression.serialisation(language: .perlCompatibleREs) == "aa|") 23 | 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /Tests/RegexKitTests/Alternation/Homogeneous Alternation Expression Test Case.swift: -------------------------------------------------------------------------------- 1 | // PatternKit © 2017–21 Constantino Tsarouhas 2 | 3 | import RegexKit 4 | import XCTest 5 | 6 | class HomogeneousAlternationExpressionTestCase : XCTestCase { 7 | 8 | func testSimple() { 9 | 10 | let binaryExpression = HomogeneousAlternationExpression(LiteralExpression("aa"), LiteralExpression("bb")) 11 | XCTAssert(try! binaryExpression.serialisation(language: .ecmaScript) == "aa|bb") 12 | XCTAssert(try! binaryExpression.serialisation(language: .perlCompatibleREs) == "aa|bb") 13 | 14 | let ternaryExpression = HomogeneousAlternationExpression(LiteralExpression("aa"), LiteralExpression("bb"), LiteralExpression("cc")) 15 | XCTAssert(try! ternaryExpression.serialisation(language: .ecmaScript) == "aa|bb|cc") 16 | XCTAssert(try! ternaryExpression.serialisation(language: .perlCompatibleREs) == "aa|bb|cc") 17 | 18 | } 19 | 20 | func testEmpty() { 21 | 22 | let binaryExpression = HomogeneousAlternationExpression(EmptyExpression(), EmptyExpression()) 23 | XCTAssert(try! binaryExpression.serialisation(language: .ecmaScript) == "|") 24 | XCTAssert(try! binaryExpression.serialisation(language: .perlCompatibleREs) == "|") 25 | 26 | let ternaryExpression = HomogeneousAlternationExpression(EmptyExpression(), EmptyExpression(), EmptyExpression()) 27 | XCTAssert(try! ternaryExpression.serialisation(language: .ecmaScript) == "||") 28 | XCTAssert(try! ternaryExpression.serialisation(language: .perlCompatibleREs) == "||") 29 | 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /Tests/RegexKitTests/Assertion/Backward Assertion Expression Test Case.swift: -------------------------------------------------------------------------------- 1 | // PatternKit © 2017–21 Constantino Tsarouhas 2 | 3 | import RegexKit 4 | import XCTest 5 | 6 | class BackwardAssertionExpressionTestCase : XCTestCase { 7 | 8 | func testSimple() { 9 | let expression = BackwardAssertionExpression(LiteralExpression("hello")) 10 | XCTAssert(try! expression.serialisation(language: .perlCompatibleREs) == "(?<=hello)") 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /Tests/RegexKitTests/Assertion/Forward Assertion Expression Test Case.swift: -------------------------------------------------------------------------------- 1 | // PatternKit © 2017–21 Constantino Tsarouhas 2 | 3 | import RegexKit 4 | import XCTest 5 | 6 | class ForwardAssertionExpressionTestCase : XCTestCase { 7 | 8 | func testSimple() { 9 | let expression = ForwardAssertionExpression(LiteralExpression("hello")) 10 | XCTAssert(try! expression.serialisation(language: .perlCompatibleREs) == "(?=hello)") 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /Tests/RegexKitTests/Assertion/Negated Backward Assertion Expression Test Case.swift: -------------------------------------------------------------------------------- 1 | // PatternKit © 2017–21 Constantino Tsarouhas 2 | 3 | import RegexKit 4 | import XCTest 5 | 6 | class NegatedBackwardAssertionExpressionTestCase : XCTestCase { 7 | 8 | func testSimple() { 9 | let expression = NegatedBackwardAssertionExpression(LiteralExpression("hello")) 10 | XCTAssert(try! expression.serialisation(language: .perlCompatibleREs) == "(?