Skip to content

Commit 64fe004

Browse files
authored
Merge pull request #55 from kareman/documentation
Documentation
2 parents 0b087fe + 4591ba9 commit 64fe004

File tree

7 files changed

+96
-24
lines changed

7 files changed

+96
-24
lines changed

.swiftpm/xcode/xcshareddata/xcschemes/Debug PerformanceTests.xcscheme

+16
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,22 @@
55
<BuildAction
66
parallelizeBuildables = "YES"
77
buildImplicitDependencies = "YES">
8+
<BuildActionEntries>
9+
<BuildActionEntry
10+
buildForTesting = "YES"
11+
buildForRunning = "NO"
12+
buildForProfiling = "YES"
13+
buildForArchiving = "NO"
14+
buildForAnalyzing = "NO">
15+
<BuildableReference
16+
BuildableIdentifier = "primary"
17+
BlueprintIdentifier = "PerformanceTests"
18+
BuildableName = "PerformanceTests"
19+
BlueprintName = "PerformanceTests"
20+
ReferencedContainer = "container:">
21+
</BuildableReference>
22+
</BuildActionEntry>
23+
</BuildActionEntries>
824
</BuildAction>
925
<TestAction
1026
buildConfiguration = "Debug"

Package.swift

+1-2
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,7 @@ let package = Package(
2828
name: "PatternsTests",
2929
dependencies: ["Patterns"],
3030
swiftSettings: [ // Move code that takes too long to build into 'LongTests'.
31-
.unsafeFlags(["-Xfrontend", "-warn-long-expression-type-checking=200",
32-
"-Xfrontend", "-warn-long-function-bodies=200"]),
31+
.unsafeFlags(["-Xfrontend", "-warn-long-expression-type-checking=200"]),
3332
]),
3433
.testTarget(
3534
name: "PerformanceTests",

Sources/Patterns/Operations on Patterns/And.swift

+4
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ prefix operator &&
3838

3939
extension Pattern {
4040
/// Matches the following pattern without consuming any input.
41+
///
42+
/// - note: in standard PEG this operator is `&`, but that is not allowed in Swift.
4143
@inlinable
4244
public static prefix func && (me: Self) -> AndPattern<Self> {
4345
AndPattern(me)
@@ -46,6 +48,8 @@ extension Pattern {
4648

4749
extension Literal {
4850
/// Matches the following pattern without consuming any input.
51+
///
52+
/// - note: in standard PEG this operator is `&`, but that is not allowed in Swift.
4953
@inlinable
5054
public static prefix func && (me: Literal) -> AndPattern<Literal> {
5155
AndPattern(me)

Sources/Patterns/Operations on Patterns/Repetition.swift

+4
Original file line numberDiff line numberDiff line change
@@ -94,12 +94,16 @@ public postfix func + <Input>(me: Literal<Input>) -> RepeatPattern<Literal<Input
9494
postfix operator ¿
9595

9696
/// Tries the preceding pattern, and continues even if it fails.
97+
///
98+
/// - note: in standard PEG this operator is `?`, but that is not allowed in Swift.
9799
@inlinable
98100
public postfix func ¿ <P: Pattern>(me: P) -> RepeatPattern<P> {
99101
me.repeat(0 ... 1)
100102
}
101103

102104
/// Tries the preceding pattern, and continues even if it fails.
105+
///
106+
/// - note: in standard PEG this operator is `?`, but that is not allowed in Swift.
103107
@inlinable
104108
public postfix func ¿ <Input>(me: Literal<Input>) -> RepeatPattern<Literal<Input>> {
105109
me.repeat(0 ... 1)

Sources/Patterns/Operations on Patterns/Skip.swift

+51-16
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,39 @@
55
// Created by Kåre Morstøl on 25/05/2020.
66
//
77

8-
/// Skips 0 or more elements until a match for the next patterns are found.
8+
/// Skips 0 or more elements until a match for the next patterns is found.
99
///
10-
/// If this is at the end of a pattern, it skips to the end of input.
10+
/// ```swift
11+
/// let s = Skip() • a
12+
/// ```
13+
/// is the same as `|S <- A / . <S>|` in standard PEG.
14+
///
15+
/// - note:
16+
/// If `Skip` is at the end of a pattern, it just succeeds without consuming input. So it will be pointless.
17+
///
18+
/// But this works:
19+
/// ```swift
20+
/// let s = Skip()
21+
/// let p = s • " "
22+
/// ```
23+
/// because here the `s` pattern is "inlined".
24+
///
25+
/// This, however, does not work:
26+
/// ```swift
27+
/// let g = Grammar { g in
28+
/// g.nextSpace <- g.skip • " "
29+
/// g.skip <- Skip()
30+
/// }
31+
/// ```
32+
/// because in grammars the subexpressions are called, like functions, not "inlined", like Swift variables.
33+
/// So the `Skip()` in `g.skip` can't tell what will come after it.
1134
public struct Skip<Input: BidirectionalCollection>: Pattern where Input.Element: Hashable {
1235
public var description: String { "Skip()" }
1336

37+
@inlinable
1438
public init() {}
1539

40+
@inlinable
1641
public init() where Input == String {}
1742

1843
@inlinable
@@ -24,6 +49,7 @@ public struct Skip<Input: BidirectionalCollection>: Pattern where Input.Element:
2449
import SE0270_RangeSet
2550

2651
extension MutableCollection where Self: RandomAccessCollection, Self: RangeReplaceableCollection, Index == Int {
52+
/// Replaces all placeholder `.skip` instructions.
2753
@usableFromInline
2854
mutating func replaceSkips<Input>() where Element == Instruction<Input> {
2955
// `setupSkip(at: i)` adds 1 new instruction somewhere after `ì`, so we cant loop over self.indices directly
@@ -38,26 +64,30 @@ extension MutableCollection where Self: RandomAccessCollection, Self: RangeRepla
3864
} while i < self.endIndex
3965
}
4066

67+
/// Replaces the dummy `.skip` instruction at `skipIndex` with one that will search using the instructions
68+
/// right after `skipIndex`.
4169
@usableFromInline
4270
mutating func setupSkip<Input>(at skipIndex: Index) where Element == Instruction<Input> {
43-
let searchablesStartAt = skipIndex + 1
44-
switch self[searchablesStartAt] {
71+
let afterSkip = skipIndex + 1
72+
switch self[afterSkip] {
4573
case let .checkIndex(function, atIndexOffset: 0):
4674
self[skipIndex] = .search { input, index in
4775
input[index...].indices.first(where: { function(input, $0) })
4876
?? (function(input, input.endIndex) ? input.endIndex : nil)
4977
}
50-
self[searchablesStartAt] = .choice(offset: -1, atIndexOffset: 1)
78+
self[afterSkip] = .choice(offset: -1, atIndexOffset: 1)
5179
case .checkIndex(_, atIndexOffset: _):
52-
fatalError("Cannot see a valid reason for a `.checkIndex` with a non-zero offset to be located right after a `.skip` instruction.") // Correct me if I'm wrong.
80+
// A `.checkIndex` will only have a non-zero offset if it has been moved by `moveMovablesForward`,
81+
// and that will never move anything beyond a `.skip`.
82+
fatalError("A `.checkIndex` with a non-zero offset can't be located right after a `.skip` instruction.")
5383
case let .checkElement(test):
5484
self[skipIndex] = .search { input, index in
5585
input[index...].firstIndex(where: test)
5686
.map(input.index(after:))
5787
}
58-
self[searchablesStartAt] = .choice(offset: -1, atIndexOffset: 0)
88+
self[afterSkip] = .choice(offset: -1, atIndexOffset: 0)
5989
case .elementEquals:
60-
let elements: [Input.Element] = self[searchablesStartAt...]
90+
let elements: [Input.Element] = self[afterSkip...]
6191
.mapPrefix {
6292
switch $0 {
6393
case let .elementEquals(element):
@@ -71,14 +101,14 @@ extension MutableCollection where Self: RandomAccessCollection, Self: RangeRepla
71101
input[index...].firstIndex(of: elements[0])
72102
.map(input.index(after:))
73103
}
74-
self[searchablesStartAt] = .choice(offset: -1, atIndexOffset: 0)
104+
self[afterSkip] = .choice(offset: -1, atIndexOffset: 0)
75105
} else {
76106
let cache = SearchCache(elements)
77107
self[skipIndex] = .search { input, index in
78108
input.range(of: cache, from: index)?.upperBound
79109
}
80-
self[searchablesStartAt] = .choice(offset: -1, atIndexOffset: (-elements.count) + 1)
81-
self[searchablesStartAt + 1] = .jump(offset: elements.count - 1)
110+
self[afterSkip] = .choice(offset: -1, atIndexOffset: (-elements.count) + 1)
111+
self[afterSkip + 1] = .jump(offset: elements.count - 1)
82112
}
83113
default:
84114
self[skipIndex] = .choice(offset: 0, atIndexOffset: +1)
@@ -89,16 +119,15 @@ extension MutableCollection where Self: RandomAccessCollection, Self: RangeRepla
89119
}
90120

91121
@usableFromInline
92-
mutating func placeSkipCommit<Input>(startSearchFrom: Index)
93-
where Element == Instruction<Input> {
122+
mutating func placeSkipCommit<Input>(startSearchFrom: Index) where Element == Instruction<Input> {
94123
var i = startSearchFrom
95124
loop: while true {
96125
switch self[i] {
97126
case let .choice(_, indexOffset) where indexOffset < 0:
98127
fatalError("Not implemented.")
99128
case let .choice(offset, _):
100129
// Follow every choice offset.
101-
// If one step back there is a jump forwards, then it's a '/' operation. So follow it too.
130+
// If one step back there is a jump forwards, then it's a '/' pattern. So follow that jump too.
102131
if case let .jump(jumpOffset) = self[i + offset - 1], jumpOffset > 0 {
103132
i += offset - 1 + jumpOffset
104133
} else {
@@ -112,17 +141,22 @@ extension MutableCollection where Self: RandomAccessCollection, Self: RangeRepla
112141
insertInstructions(.commit, at: i)
113142
return
114143
case .openCall:
115-
fatalError()
144+
fatalError("`.openCall` instruction should have been replaced.")
116145
}
117146
}
118147
}
119148

120-
/// Inserts new instructions at `location`. Adjusts the offsets of the other instructions accordingly.
149+
/// Inserts `newInstructions` at `location`. Adjusts the offsets of the other instructions accordingly.
150+
///
151+
/// Since all offsets are relative to the positions of their instructions,
152+
/// if `location` lies between an instruction with an offset and where that offset leads to,
153+
/// the offset needs to be increased by the length of `newInstructions`.
121154
@usableFromInline
122155
mutating func insertInstructions<Input>(_ newInstructions: Element..., at location: Index)
123156
where Element == Instruction<Input> {
124157
insert(contentsOf: newInstructions, at: location)
125158
let insertedRange = location ..< (location + newInstructions.count + 1)
159+
/// instruction ... location ... offsetTarget
126160
for i in startIndex ..< insertedRange.lowerBound {
127161
switch self[i] {
128162
case let .call(offset) where offset > (location - i):
@@ -135,6 +169,7 @@ extension MutableCollection where Self: RandomAccessCollection, Self: RangeRepla
135169
break
136170
}
137171
}
172+
/// offsetTarget ... location ... instruction
138173
for i in insertedRange.upperBound ..< endIndex {
139174
switch self[i] {
140175
case let .call(offset) where offset < (location - i):

Sources/Patterns/Optimise Instructions.swift

+4-2
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
//
66

77
private extension Instruction {
8+
/// Can this instruction be moved by `moveMovablesForward`?
89
var isMovable: Bool {
910
switch self {
1011
case .checkIndex, .captureStart, .captureEnd:
@@ -14,6 +15,7 @@ private extension Instruction {
1415
}
1516
}
1617

18+
/// Does this instruction prohibit `moveMovablesForward` from moving anything past it?
1719
var stopsMovables: Bool {
1820
switch self {
1921
case .elementEquals, .checkElement:
@@ -27,7 +29,7 @@ private extension Instruction {
2729
import SE0270_RangeSet
2830

2931
extension MutableCollection where Self: RandomAccessCollection, Index == Int {
30-
/// Moves any `.checkIndex, .captureStart, .captureEnd` past any `.elementEquals, .checkElement`.
32+
/// Moves any `.checkIndex`, `.captureStart`, `.captureEnd` past any `.elementEquals`, `.checkElement`.
3133
///
3234
/// Improves performance noticeably.
3335
@usableFromInline
@@ -50,7 +52,7 @@ extension MutableCollection where Self: RandomAccessCollection, Index == Int {
5052
self[movedIndex] = .checkIndex(test, atIndexOffset: offset - distanceMoved)
5153
checkIndexIndices.insert(movedIndex, within: self)
5254
default:
53-
fatalError()
55+
fatalError("'\(self[movedIndex])' is not a 'movable'.")
5456
}
5557
}
5658
movables.removeAll()

Tests/PatternsTests/SkipTests.swift

+16-4
Original file line numberDiff line numberDiff line change
@@ -46,10 +46,6 @@ class SkipTests: XCTestCase {
4646
input: lines, result: ["1", "2", "", "3"])
4747
assertParseAll(Capture(Line.start Skip() Line.end),
4848
input: lines, result: ["1", "2", "", "3"])
49-
50-
// undefined (Skip at end)
51-
_ = try Parser(search: " " Capture(Skip()))
52-
.matches(in: text)
5349
}
5450

5551
func testInsideOptional() throws {
@@ -73,6 +69,22 @@ class SkipTests: XCTestCase {
7369
assertParseMarkers(try Parser(Skip() Skip() " "), input: "This |is")
7470
}
7571

72+
func testAtTheEnd() throws {
73+
assertParseMarkers(" " Skip(), input: "a |bee")
74+
assertParseAll(" " Capture(Skip()), input: "a bee", result: [""])
75+
76+
// used in documentation for Skip.
77+
78+
let s = Skip()
79+
assertParseMarkers(try Parser(s " "), input: "jfd | |jlj |")
80+
81+
let g = Grammar { g in
82+
g.nextSpace <- g.skip " "
83+
g.skip <- Skip() // Does not work.
84+
}
85+
assertParseMarkers(try Parser(g), input: "sdf ksj")
86+
}
87+
7688
func testBeforeGrammarTailCall() throws {
7789
let recursive = Grammar { g in
7890
g.a <- " " Skip() g.a

0 commit comments

Comments
 (0)