in Sources/_StringProcessing/Compiler.swift [307:483]
func compileQuantification(
low: Int,
high: Int?,
kind: AST.Quantification.Kind,
child: AST.Node
) throws {
// Compiler and/or parser should enforce these invariants
// before we are called
assert(high != 0)
assert((0...(high ?? Int.max)).contains(low))
let extraTrips: Int?
if let h = high {
extraTrips = h - low
} else {
extraTrips = nil
}
let minTrips = low
assert((extraTrips ?? 1) >= 0)
// The below is a general algorithm for bounded and unbounded
// quantification. It can be specialized when the min
// is 0 or 1, or when extra trips is 1 or unbounded.
//
// Stuff inside `<` and `>` are decided at compile time,
// while run-time values stored in registers start with a `%`
_ = """
min-trip-count control block:
if %minTrips is zero:
goto exit-policy control block
else:
decrement %minTrips and fallthrough
loop-body:
evaluate the subexpression
goto min-trip-count control block
exit-policy control block:
if %extraTrips is zero:
goto exit
else:
decrement %extraTrips and fallthrough
<if eager>:
save exit and goto loop-body
<if possessive>:
ratchet and goto loop
<if reluctant>:
save loop-body and fallthrough (i.e. goto exit)
exit
... the rest of the program ...
"""
// Specialization based on `minTrips` for 0 or 1:
_ = """
min-trip-count control block:
<if minTrips == 0>:
goto exit-policy
<if minTrips == 1>:
/* fallthrough */
loop-body:
evaluate the subexpression
<if minTrips <= 1>
/* fallthrough */
"""
// Specialization based on `extraTrips` for 0 or unbounded
_ = """
exit-policy control block:
<if extraTrips == 0>:
goto exit
<if extraTrips == .unbounded>:
/* fallthrough */
"""
/*
NOTE: These specializations don't emit the optimal
code layout (e.g. fallthrough vs goto), but that's better
done later (not prematurely) and certainly better
done by an optimizing compiler.
NOTE: We're intentionally emitting essentially the same
algorithm for all quantifications for now, for better
testing and surfacing difficult bugs. We can specialize
for other things, like `.*`, later.
When it comes time for optimizing, we can also look into
quantification instructions (e.g. reduce save-point traffic)
*/
let minTripsControl = builder.makeAddress()
let loopBody = builder.makeAddress()
let exitPolicy = builder.makeAddress()
let exit = builder.makeAddress()
// We'll need registers if we're (non-trivially) bounded
let minTripsReg: IntRegister?
if minTrips > 1 {
minTripsReg = builder.makeIntRegister(
initialValue: minTrips)
} else {
minTripsReg = nil
}
let extraTripsReg: IntRegister?
if (extraTrips ?? 0) > 0 {
extraTripsReg = builder.makeIntRegister(
initialValue: extraTrips!)
} else {
extraTripsReg = nil
}
// Set up a dummy save point for possessive to update
if kind == .possessive {
builder.pushEmptySavePoint()
}
// min-trip-count:
// condBranch(to: exitPolicy, ifZeroElseDecrement: %min)
builder.label(minTripsControl)
switch minTrips {
case 0: builder.buildBranch(to: exitPolicy)
case 1: break
default:
assert(minTripsReg != nil, "logic inconsistency")
builder.buildCondBranch(
to: exitPolicy, ifZeroElseDecrement: minTripsReg!)
}
// FIXME: Possessive needs a "dummy" save point to ratchet
// loop:
// <subexpression>
// branch min-trip-count
builder.label(loopBody)
try emit(child)
if minTrips <= 1 {
// fallthrough
} else {
builder.buildBranch(to: minTripsControl)
}
// exit-policy:
// condBranch(to: exit, ifZeroElseDecrement: %extraTrips)
// <eager: split(to: loop, saving: exit)>
// <possesive:
// clearSavePoint
// split(to: loop, saving: exit)>
// <reluctant: save(restoringAt: loop)
builder.label(exitPolicy)
switch extraTrips {
case nil: break
case 0: builder.buildBranch(to: exit)
default:
assert(extraTripsReg != nil, "logic inconsistency")
builder.buildCondBranch(
to: exit, ifZeroElseDecrement: extraTripsReg!)
}
switch kind {
case .eager:
builder.buildSplit(to: loopBody, saving: exit)
case .possessive:
builder.buildClear()
builder.buildSplit(to: loopBody, saving: exit)
case .reluctant:
builder.buildSave(loopBody)
// FIXME: Is this re-entrant? That is would nested
// quantification break if trying to restore to a prior
// iteration because the register got overwritten?
//
}
builder.label(exit)
}