where Input.Element: Has
extension Sequence {
/// The offset by which these instructions will move the input index.
- @inlinable
+ @usableFromInline
func movesIndexBy() -> Int? where Element == Instruction
{
lazy .map { $0.movesIndexBy }.reduceIfNoNils(into: 0) { result, offset in result += offset }
}
diff --git a/Sources/Patterns/VMBacktrack.swift b/Sources/Patterns/VMBacktrack.swift
index 3fd0ef9..c10cac8 100644
--- a/Sources/Patterns/VMBacktrack.swift
+++ b/Sources/Patterns/VMBacktrack.swift
@@ -9,6 +9,7 @@
struct VMEngine where Input.Element: Hashable {
@usableFromInline
typealias Instructions = ContiguousArray>
+ @usableFromInline
let instructions: Instructions
@usableFromInline
@@ -23,6 +24,8 @@ struct VMEngine where Input.Element: Hashable {
self.instructions = instructions
}
+ @_specialize(where Input == String) // doesn't happen automatically (swiftlang-1200.0.28.1).
+ @_specialize(where Input == String.UTF8View)
@usableFromInline
func match(in input: Input, at startIndex: Input.Index) -> Parser.Match? {
launch(input: input, startIndex: startIndex)
@@ -56,9 +59,13 @@ extension Parser.Match {
extension VMEngine {
@usableFromInline
struct Thread {
+ @usableFromInline
var instructionIndex: Instructions.Index
+ @usableFromInline
var inputIndex: Input.Index
+ @usableFromInline
var captures: ContiguousArray<(index: Input.Index, instruction: Instructions.Index)>
+ @usableFromInline
var isReturnAddress: Bool = false
@usableFromInline
diff --git a/Tests/PerformanceTests/PerformanceTests.swift b/Tests/PerformanceTests/StringTests.swift
similarity index 70%
rename from Tests/PerformanceTests/PerformanceTests.swift
rename to Tests/PerformanceTests/StringTests.swift
index 264159e..df70f9d 100644
--- a/Tests/PerformanceTests/PerformanceTests.swift
+++ b/Tests/PerformanceTests/StringTests.swift
@@ -12,29 +12,7 @@ import XCTest
// Note: the hits parameter to speedTest doesn't necessarily mean the _correct_ number of hits.
// It's just there to notify us when the number of hits changes.
-class PerformanceTests: XCTestCase {
- func speedTest(_ pattern: Parser, testFile: String = "Long.txt", textFraction: Int = 1, hits: Int,
- file: StaticString = #filePath, line: UInt = #line) throws {
- let fulltext = try String(contentsOf: getLocalURL(for: testFile))
- let text = String(fulltext.prefix(fulltext.count / textFraction)).utf8
- var result = 0
- let block = {
- result = pattern.matches(in: text).reduce(into: 0) { c, _ in c += 1 }
- }
- #if DEBUG
- block()
- #else
- if #available(OSX 10.15, *) {
- let options = XCTMeasureOptions()
- options.iterationCount = 10
- self.measure(metrics: [XCTCPUMetric(limitingToCurrentThread: true)], options: options, block: block)
- } else {
- self.measure(block)
- }
- #endif
- XCTAssertEqual(result, hits, file: file, line: line)
- }
-
+class StringTests: XCTestCase {
func speedTest(_ pattern: Parser, testFile: String = "Long.txt", textFraction: Int = 1, hits: Int,
file: StaticString = #filePath, line: UInt = #line) throws {
let fulltext = try String(contentsOf: getLocalURL(for: testFile))
@@ -73,36 +51,36 @@ class PerformanceTests: XCTestCase {
}
func testLine() throws {
- let pattern = try Parser(search: Line.Start() • Capture(Skip()) • Line.End())
+ let pattern = try Parser(search: Line.start • Capture(Skip()) • Line.end)
try speedTest(pattern, textFraction: 2, hits: 7260)
}
func testNotNewLine() throws {
- let pattern = try Parser(search: "," • Capture(Skip()) • Line.End())
+ let pattern = try Parser(search: "," • Capture(Skip()) • Line.End())
try speedTest(pattern, textFraction: 2, hits: 4933)
}
func testLiteralSearch() throws {
- let pattern = try Parser(search: Literal("Prince"))
+ let pattern = try Parser(search: Literal("Prince"))
try speedTest(pattern, textFraction: 1, hits: 2168)
}
func testGrammarLiteralSearch() throws {
func any() -> OneOf { OneOf(description: "any", contains: { _ in true }) }
- let g = Grammar()
+ let g = Grammar()
g.a <- Capture("Prince") / any() • g.a
let pattern = try Parser(g)
try speedTest(pattern, textFraction: 13, hits: 260)
}
func testNonExistentLiteralSearch() throws {
- let pattern = try Parser(search: "\n" • Skip() • "DOESN'T EXIST")
+ let pattern = try Parser(search: "\n" • Skip() • "DOESN'T EXIST")
try speedTest(pattern, textFraction: 1, hits: 0)
}
func testOptionalStringFollowedByNonOptionalString() throws {
- let pattern = try Parser(search: Literal("\"")¿ • "I")
+ let pattern = try Parser(search: "\""¿ • "I")
try speedTest(pattern, textFraction: 12, hits: 814)
}
@@ -113,7 +91,7 @@ class PerformanceTests: XCTestCase {
func testSkipping1() throws {
// [ word.boundary ] * " " * ":" * " " * " " * " " * "{" * Line.end
- let pattern = try Parser(search: "." • Skip() • " " • Skip() • " ")
+ let pattern = try Parser(search: "." • Skip() • " " • Skip() • " ")
try speedTest(pattern, textFraction: 2, hits: 13939)
}
diff --git a/Tests/PerformanceTests/UTF8Tests.swift b/Tests/PerformanceTests/UTF8Tests.swift
new file mode 100644
index 0000000..1a32480
--- /dev/null
+++ b/Tests/PerformanceTests/UTF8Tests.swift
@@ -0,0 +1,77 @@
+//
+// File.swift
+//
+//
+// Created by Kåre Morstøl on 19/08/2020.
+//
+
+import Foundation
+import Patterns
+import XCTest
+
+// Note: the hits parameter to speedTest doesn't necessarily mean the _correct_ number of hits.
+// It's just there to notify us when the number of hits changes.
+
+class UTF8Tests: XCTestCase {
+ func speedTest(_ pattern: Parser, testFile: String = "Long.txt", textFraction: Int = 1, hits: Int,
+ file: StaticString = #filePath, line: UInt = #line) throws {
+ let fulltext = try String(contentsOf: getLocalURL(for: testFile))
+ let text = String(fulltext.prefix(fulltext.count / textFraction)).utf8
+ var result = 0
+ let block = {
+ result = pattern.matches(in: text).reduce(into: 0) { c, _ in c += 1 }
+ }
+ #if DEBUG
+ block()
+ #else
+ if #available(OSX 10.15, *) {
+ let options = XCTMeasureOptions()
+ options.iterationCount = 10
+ self.measure(metrics: [XCTCPUMetric(limitingToCurrentThread: true)], options: options, block: block)
+ } else {
+ self.measure(block)
+ }
+ #endif
+ XCTAssertEqual(result, hits, file: file, line: line)
+ }
+
+ func testLine() throws {
+ let pattern = try Parser(search: Line.Start() • Capture(Skip()) • Line.End())
+ try speedTest(pattern, textFraction: 2, hits: 7260)
+ }
+
+ func testNotNewLine() throws {
+ let pattern = try Parser(search: "," • Capture(Skip()) • Line.End())
+ try speedTest(pattern, textFraction: 2, hits: 4933)
+ }
+
+ func testLiteralSearch() throws {
+ let pattern = try Parser(search: Literal("Prince"))
+ try speedTest(pattern, textFraction: 1, hits: 2168)
+ }
+
+ func testGrammarLiteralSearch() throws {
+ func any() -> OneOf { OneOf(description: "any", contains: { _ in true }) }
+
+ let g = Grammar()
+ g.a <- Capture("Prince") / any() • g.a
+ let pattern = try Parser(g)
+ try speedTest(pattern, textFraction: 13, hits: 260)
+ }
+
+ func testNonExistentLiteralSearch() throws {
+ let pattern = try Parser(search: "\n" • Skip() • "DOESN'T EXIST")
+ try speedTest(pattern, textFraction: 1, hits: 0)
+ }
+
+ func testOptionalStringFollowedByNonOptionalString() throws {
+ let pattern = try Parser(search: Literal("\"")¿ • "I")
+ try speedTest(pattern, textFraction: 12, hits: 814)
+ }
+
+ func testSkipping1() throws {
+ // [ word.boundary ] * " " * ":" * " " * " " * " " * "{" * Line.end
+ let pattern = try Parser(search: "." • Skip() • " " • Skip() • " ")
+ try speedTest(pattern, textFraction: 2, hits: 13939)
+ }
+}