Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
123 changes: 53 additions & 70 deletions Sources/_StringProcessing/Engine/Backtracking.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,75 +9,53 @@
//
//===----------------------------------------------------------------------===//

internal import _RegexParser

extension Processor {
struct SavePoint {
/// The current position in the instruction list.
var pc: InstructionAddress
var pos: Position?

/// The match position to resume when a save point is restored.
enum SavedPosition {
/// A single position to resume at.
case position(Position)

/// A range of positions still to explore, from the end of the
/// range to the start, along with the semantic mode to use
/// when consuming the range.
case range(Range<Position>, isScalarSemantics: Bool)

/// No position to restore — only the instruction address matters.
case addressOnly
}
// Quantifiers may store a range of positions to restore to
var quantifiedRange: Range<Position>?

var savedPosition: SavedPosition
// FIXME: refactor, for now this field is only used for quantifier save
// points. We should try to separate out the concerns better.
var isScalarSemantics: Bool

// These properties store indices into the mutable `Processor.Registers`
// undo logs. On backtrack, each log is unwound down to the point saved
// here.
//
// NOTE: These are `UInt32` to keep `SavePoint` small. A log can only fill
// up if a single match attempt has 2^32 register mutations without
// clearing the registers, beyond what would already fill up memory.
// FIXME: Save minimal info (e.g. stack position and
// perhaps current start)
var captureEnds: [_StoredCapture]

/// The length of the log of the `captures` register when this save point was created,
/// for backtracking on failure.
var captureLogEnd: UInt32
/// The length of the log of the `ints` register when this save point was created,
/// for backtracking on failure.
var intLogEnd: UInt32
/// The length of the log of the `positions` register when this save point was created,
/// for backtracking on failure.
var positionLogEnd: UInt32
// The int registers store values that can be relevant to
// backtracking, such as the number of trips in a quantification.
var intRegisters: [Int]
// Same with position registers
var posRegisters: [Input.Index]

/// The single position to resume at, for a save point that isn't
/// quantified or address-only.
var pos: Position? {
if case .position(let p) = savedPosition { return p }
return nil
var destructure: (
pc: InstructionAddress,
pos: Position?,
captureEnds: [_StoredCapture],
intRegisters: [Int],
PositionRegister: [Input.Index]
) {
return (pc, pos, captureEnds, intRegisters, posRegisters)
}

/// Whether this save point is quantified, meaning it has a range of
/// possible positions still to explore.
// Whether this save point is quantified, meaning it has a range of
// possible positions to explore.
var isQuantified: Bool {
if case .range = savedPosition { return true }
return false
quantifiedRange != nil
}

/// Pops the next position to try from a quantified save point's
/// range, returning it and updating the stored state so a future pop
/// (if any) tries the next-most-recent position.
///
/// If the range is (or becomes) empty, this save point becomes a
/// normal, single-position save point for a final future pop.
mutating func popQuantifiedPosition(_ input: Input) -> Position {
guard case .range(let range, let isScalarSemantics) = savedPosition else {
fatalError("Not a quantified save point")
}
let resumeAt = range.upperBound
guard !range.isEmpty else {
savedPosition = .position(resumeAt)
return resumeAt
/// Move the next range position into pos, and removing it from the range
mutating func takePositionFromQuantifiedRange(_ input: Input) {
assert(isQuantified)
let range = quantifiedRange!
pos = range.upperBound
if range.isEmpty {
// Becomes a normal save point
quantifiedRange = nil
return
}

// Shrink the range
Expand All @@ -87,8 +65,7 @@ extension Processor {
} else {
newUpper = input.index(before: range.upperBound)
}
savedPosition = .range(range.lowerBound..<newUpper, isScalarSemantics: isScalarSemantics)
return resumeAt
quantifiedRange = range.lowerBound..<newUpper
}
}

Expand All @@ -97,21 +74,25 @@ extension Processor {
) -> SavePoint {
SavePoint(
pc: pc,
savedPosition: .position(currentPosition),
captureLogEnd: .init(asserting: registers.storedCaptures.logCount),
intLogEnd: .init(asserting: registers.ints.logCount),
positionLogEnd: .init(asserting: registers.positions.logCount))
pos: currentPosition,
quantifiedRange: nil,
isScalarSemantics: false,
captureEnds: storedCaptures,
intRegisters: registers.ints,
posRegisters: registers.positions)
}

func makeAddressOnlySavePoint(
resumingAt pc: InstructionAddress
) -> SavePoint {
SavePoint(
pc: pc,
savedPosition: .addressOnly,
captureLogEnd: .init(asserting: registers.storedCaptures.logCount),
intLogEnd: .init(asserting: registers.ints.logCount),
positionLogEnd: .init(asserting: registers.positions.logCount))
pos: nil,
quantifiedRange: nil,
isScalarSemantics: false,
captureEnds: storedCaptures,
intRegisters: registers.ints,
posRegisters: registers.positions)
}

func makeQuantifiedSavePoint(
Expand All @@ -120,10 +101,12 @@ extension Processor {
) -> SavePoint {
SavePoint(
pc: controller.pc + 1,
savedPosition: .range(range, isScalarSemantics: isScalarSemantics),
captureLogEnd: .init(asserting: registers.storedCaptures.logCount),
intLogEnd: .init(asserting: registers.ints.logCount),
positionLogEnd: .init(asserting: registers.positions.logCount))
pos: nil,
quantifiedRange: range,
isScalarSemantics: isScalarSemantics,
captureEnds: storedCaptures,
intRegisters: registers.ints,
posRegisters: registers.positions)
}
}

Expand Down
9 changes: 6 additions & 3 deletions Sources/_StringProcessing/Engine/MEBuilder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -428,10 +428,12 @@ extension MEProgram.Builder {
matcherFunctions: matcherFunctions,
numInts: nextIntRegister.rawValue,
numValues: nextValueRegister.rawValue,
numPositions: nextPositionRegister.rawValue,
numCaptures: nextCaptureRegister.rawValue
numPositions: nextPositionRegister.rawValue
)

let storedCaps = Array(
repeating: Processor._StoredCapture(), count: nextCaptureRegister.rawValue)

let meProgram = MEProgram(
instructions: InstructionList(instructions),
wholeMatchValueRegister: wholeMatchValue,
Expand All @@ -441,7 +443,8 @@ extension MEProgram.Builder {
referencedCaptureOffsets: referencedCaptureOffsets,
initialOptions: initialOptions,
canOnlyMatchAtStart: canOnlyMatchAtStart,
registers: regs)
registers: regs,
storedCaptures: storedCaps)
return meProgram
}

Expand Down
19 changes: 19 additions & 0 deletions Sources/_StringProcessing/Engine/MECapture.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,25 @@

internal import _RegexParser

/*

TODO: Specialized data structure for all captures:

- We want to be able to refer to COW prefixes for which
simple appends do not invalidate
- We want a compact save-point representation

TODO: Conjectures:

- We should be able to remove the entire capture history,
lazily recomputing it on-request from the initial stored
save point
- We should be able to keep these flat and simple, lazily
constructing structured types on-request

*/


extension Processor {
struct _StoredCapture {
var range: Range<Position>? = nil
Expand Down
14 changes: 2 additions & 12 deletions Sources/_StringProcessing/Engine/MEProgram.swift
Original file line number Diff line number Diff line change
Expand Up @@ -36,25 +36,15 @@ struct MEProgram {
// processors can be spun up quicker (useful for running same regex
// over many, many smaller inputs).
var registers: Processor.Registers
var storedCaptures: [Processor._StoredCapture]

}

extension MEProgram: CustomStringConvertible {
var description: String {
// TODO: Re-instate better pretty-printing functionality

var result = """
Capture list: \(captureList)
Instructions: \(instructions.count)
Register counts:
ints: \(registers.ints.count)
positions: \(registers.positions.count)
values: \(registers.values.count)
elements: \(registers.elements.count)
bitsets: \(registers.bitsets.count)
consumeFunctions: \(registers.consumeFunctions.count)
transformFunctions: \(registers.transformFunctions.count)
matcherFunctions: \(registers.matcherFunctions.count)
canOnlyMatchAtStart: \(canOnlyMatchAtStart)

"""
// TODO: Extract into formatting code
Expand Down
Loading
Loading