Refactoring Matcher, MatchTrie. Trying to optimize calls to lookupAuxOccurrences() by reusing the previous searches.
This commit is contained in:
parent
f8970f7825
commit
e72585aa65
|
|
@ -41,6 +41,8 @@ class Controller(
|
|||
|
||||
private val frameStack = FrameStack(storeView)
|
||||
|
||||
private val matcher = Matcher(ruleIndex, profiler)
|
||||
|
||||
// persistent (functional) object. reassigned on update
|
||||
private var propHistory = PropagationHistory()
|
||||
|
||||
|
|
@ -84,7 +86,7 @@ class Controller(
|
|||
trace.reactivate(active)
|
||||
}
|
||||
|
||||
for (match in Matcher(ruleIndex, active, frameStack.current.store, profiler)) {
|
||||
for (match in matcher.matches(active, frameStack.current.store)) {
|
||||
// TODO: paranoid check. should be isAlive() instead
|
||||
if (!active.isStored()) break
|
||||
if (!match.successful) continue
|
||||
|
|
|
|||
|
|
@ -30,7 +30,6 @@ import jetbrains.mps.logic.reactor.util.Profiler
|
|||
import jetbrains.mps.unification.Substitution
|
||||
import jetbrains.mps.unification.Term
|
||||
import jetbrains.mps.unification.TermWrapper
|
||||
import java.util.*
|
||||
import com.github.andrewoma.dexx.collection.List as PersList
|
||||
|
||||
/**
|
||||
|
|
@ -38,21 +37,33 @@ import com.github.andrewoma.dexx.collection.List as PersList
|
|||
*/
|
||||
|
||||
class Matcher(val ruleIndex: RuleIndex,
|
||||
val activeOcc: ConstraintOccurrence,
|
||||
val aux: OccurrenceIndex,
|
||||
val profiler: Profiler? = null) : Iterable<Match>
|
||||
val profiler: Profiler? = null)
|
||||
{
|
||||
|
||||
private val matchTries: LazyIterable<Rule, PartialMatchTrie> by lazy(LazyThreadSafetyMode.NONE) {
|
||||
LazyIterable<Rule, PartialMatchTrie> (ruleIndex.forOccurrence(activeOcc).toList()) { rule ->
|
||||
PartialMatchTrie(rule, activeOcc, aux, profiler)
|
||||
}
|
||||
}
|
||||
private val rule2matchTrieSet = HashMap<Rule, MatchTrieSet>()
|
||||
|
||||
override fun iterator() = matchTries.asSequence().flatMap { matchTrie ->
|
||||
matchTrie.matches().asSequence() }.map { partialMatch ->
|
||||
partialMatch.complete(profiler) }.filter { match ->
|
||||
match.successful }.iterator()
|
||||
fun matches(activeOcc: ConstraintOccurrence, aux: OccurrenceIndex) : Iterable<Match> = object : Iterable<Match> {
|
||||
|
||||
override fun iterator(): Iterator<Match> {
|
||||
|
||||
val relevantRules = ruleIndex.forOccurrence(activeOcc).toList()
|
||||
|
||||
val tries = LazyIterable<Rule, MatchTrieSet>(relevantRules) { rule ->
|
||||
rule2matchTrieSet[rule] ?: MatchTrieSet(rule, profiler).apply {
|
||||
rule2matchTrieSet[rule] = this
|
||||
}
|
||||
}.asSequence()
|
||||
|
||||
return tries.flatMap { matchTrieSet ->
|
||||
matchTrieSet.matches(activeOcc, aux)
|
||||
}.map { partialMatch ->
|
||||
partialMatch.complete(profiler)
|
||||
}.filter { match ->
|
||||
match.successful
|
||||
}.iterator()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -74,4 +74,5 @@ internal class PartialMatch(val rule: Rule) {
|
|||
return match
|
||||
}
|
||||
|
||||
override fun toString(): String = "${keptOccurrences.toList()} \\ ${discardedOccurrences.toList()}"
|
||||
}
|
||||
|
|
@ -32,344 +32,422 @@ import com.github.andrewoma.dexx.collection.Map as PersMap
|
|||
* @author Fedor Isakov
|
||||
*/
|
||||
|
||||
internal class PartialMatchTrie(val rule: Rule,
|
||||
val activeOcc: ConstraintOccurrence,
|
||||
val occIndex: OccurrenceIndex,
|
||||
val profiler: Profiler?)
|
||||
{
|
||||
|
||||
constructor(prototype: PartialMatchTrie,
|
||||
activeOcc: ConstraintOccurrence,
|
||||
occurrenceIndex: OccurrenceIndex,
|
||||
profiler: Profiler?) :
|
||||
this(prototype.rule, activeOcc, occurrenceIndex, profiler)
|
||||
{
|
||||
this._prototype = prototype
|
||||
}
|
||||
|
||||
private var _prototype: PartialMatchTrie? = null
|
||||
|
||||
private val prototype: PartialMatchTrie?
|
||||
get() = _prototype
|
||||
internal class MatchTrieSet(val rule: Rule, val profiler: Profiler?) {
|
||||
|
||||
private val keptConstraints = rule.headKept().toCollection(ArrayList(4))
|
||||
|
||||
private val discardedConstraints = rule.headReplaced().toCollection(ArrayList(4))
|
||||
|
||||
private val root: RootMatchTrieNode by lazy(LazyThreadSafetyMode.NONE) {
|
||||
prototype?.root ?: RootMatchTrieNode()
|
||||
}
|
||||
|
||||
// for each slot keep a set of "related" slots, that is the slots with matching (meta-)logicals
|
||||
// this map is built only once per rule
|
||||
private val relatedSlots: List<BitSet> by lazy(LazyThreadSafetyMode.NONE) {
|
||||
prototype?.relatedSlots ?: HashMap<MetaLogical<*>, BitSet>().let { meta2slots ->
|
||||
private val relatedSlots: List<BitSet> =
|
||||
HashMap<MetaLogical<*>, BitSet>().let { meta2slots ->
|
||||
|
||||
(keptConstraints + discardedConstraints).forEachIndexed { slot, cst ->
|
||||
cst.arguments().forEach { arg ->
|
||||
if (arg is MetaLogical<*>) {
|
||||
val bs = meta2slots[arg] ?: BitSet().apply { meta2slots[arg] = this }
|
||||
bs.set(slot)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ArrayList<BitSet>().apply {
|
||||
(keptConstraints + discardedConstraints).forEachIndexed { slot, cst ->
|
||||
val bs = BitSet()
|
||||
cst.arguments().forEach { arg ->
|
||||
if (arg is MetaLogical<*>) {
|
||||
val bs = meta2slots[arg] ?: BitSet().apply { meta2slots[arg] = this }
|
||||
bs.set(slot)
|
||||
bs.or(meta2slots[arg])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ArrayList<BitSet>().apply {
|
||||
(keptConstraints + discardedConstraints).forEachIndexed { slot, cst ->
|
||||
val bs = BitSet()
|
||||
cst.arguments().forEach { arg ->
|
||||
if (arg is MetaLogical<*>) {
|
||||
bs.or(meta2slots[arg])
|
||||
}
|
||||
}
|
||||
bs.clear(slot)
|
||||
add(bs)
|
||||
}
|
||||
bs.clear(slot)
|
||||
add(bs)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun matches(): Iterable<PartialMatch> = object : Iterable<PartialMatch> {
|
||||
|
||||
override fun iterator() = object : Iterator<PartialMatch> {
|
||||
|
||||
val firstRoot: MatchTrieNode? = root.firstChild
|
||||
|
||||
var next: PartialMatch? = null
|
||||
|
||||
override fun hasNext(): Boolean {
|
||||
if (next == null) {
|
||||
this.next = calcNext()
|
||||
}
|
||||
return (next != null)
|
||||
}
|
||||
|
||||
override fun next(): PartialMatch {
|
||||
if (next == null) {
|
||||
this.next = calcNext()
|
||||
}
|
||||
if (next != null) {
|
||||
val tmp: PartialMatch = next!!
|
||||
this.next = calcNext()
|
||||
return tmp
|
||||
}
|
||||
throw NoSuchElementException()
|
||||
}
|
||||
|
||||
private fun calcNext(): PartialMatch? {
|
||||
// the search always starts at the first root
|
||||
if (firstRoot == null) {
|
||||
return null
|
||||
}
|
||||
var mtn: MatchTrieNode? = firstRoot
|
||||
|
||||
while (mtn != null) {
|
||||
if (mtn.isSeen) {
|
||||
if (mtn.nextSibling == null) {
|
||||
mtn.parent?.setSeen()
|
||||
}
|
||||
mtn = mtn.nextSibling ?: mtn.parent
|
||||
}
|
||||
else if (mtn.isLeaf) {
|
||||
mtn.setSeen()
|
||||
return mtn.match
|
||||
}
|
||||
else {
|
||||
// calculate the children
|
||||
if (mtn.firstChild == null) {
|
||||
mtn.setSeen()
|
||||
}
|
||||
mtn = mtn.firstChild ?: mtn
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
val tries = ArrayList<PartialMatchTrie>()
|
||||
|
||||
private abstract inner class BaseMatchTrieNode() {
|
||||
fun matches(activeOcc: ConstraintOccurrence, occIndex: OccurrenceIndex): Sequence<PartialMatch> {
|
||||
val constraints = keptConstraints + discardedConstraints
|
||||
|
||||
var _firstChild: MatchTrieNode? = null
|
||||
|
||||
var _nextSibling: MatchTrieNode? = null
|
||||
|
||||
abstract fun reset()
|
||||
|
||||
}
|
||||
|
||||
private inner class RootMatchTrieNode() : BaseMatchTrieNode() {
|
||||
|
||||
val firstChild: MatchTrieNode?
|
||||
get() {
|
||||
if (_firstChild == null) {
|
||||
var first: MatchTrieNode? = null
|
||||
var prev: MatchTrieNode? = null
|
||||
for ((idx, cst) in keptConstraints.withIndex()) {
|
||||
if (cst.symbol() == activeOcc.constraint().symbol()) {
|
||||
val next = MatchTrieNode(null, true, cst, activeOcc, idx)
|
||||
if (first == null) {
|
||||
first = next
|
||||
}
|
||||
prev?._nextSibling = next
|
||||
prev = next
|
||||
}
|
||||
val relevantTries = ArrayList<PartialMatchTrie>()
|
||||
val newTries = ArrayList<PartialMatchTrie>()
|
||||
for (t in tries) {
|
||||
if (!t.vacant.isEmpty) {
|
||||
t.vacant.stream()
|
||||
newTries.add(t)
|
||||
for (idx in t.vacant.stream()) {
|
||||
if (activeOcc.constraint().symbol() == constraints[idx].symbol()) {
|
||||
relevantTries.add(PartialMatchTrie(t, activeOcc, occIndex))
|
||||
newTries.removeAt(newTries.size - 1)
|
||||
break
|
||||
}
|
||||
for ((idx, cst) in discardedConstraints.withIndex()) {
|
||||
if (cst.symbol() == activeOcc.constraint().symbol()) {
|
||||
val next = MatchTrieNode(null, false, cst, activeOcc, idx)
|
||||
if (first == null) {
|
||||
first = next
|
||||
}
|
||||
prev?._nextSibling = next
|
||||
prev = next
|
||||
}
|
||||
}
|
||||
|
||||
this._firstChild = first
|
||||
}
|
||||
return _firstChild
|
||||
}
|
||||
|
||||
override fun reset() {
|
||||
while (!(_firstChild?.occurrence?.isAlive() ?: true)) {
|
||||
this._firstChild = _firstChild?._nextSibling
|
||||
}
|
||||
_firstChild?.reset()
|
||||
}
|
||||
if (relevantTries.isEmpty()) {
|
||||
relevantTries.add(PartialMatchTrie(activeOcc, occIndex))
|
||||
}
|
||||
|
||||
this.tries.clear()
|
||||
this.tries.addAll(newTries)
|
||||
this.tries.addAll(relevantTries)
|
||||
|
||||
return relevantTries.asSequence().flatMap { t -> t.matches() }
|
||||
}
|
||||
|
||||
private inner class MatchTrieNode(val parent: MatchTrieNode?,
|
||||
val keepConstraint: Boolean,
|
||||
val constraint: Constraint,
|
||||
val occurrence: ConstraintOccurrence,
|
||||
val index: Int) : BaseMatchTrieNode()
|
||||
inner class PartialMatchTrie(val activeOcc: ConstraintOccurrence,
|
||||
val occIndex: OccurrenceIndex)
|
||||
{
|
||||
|
||||
private var _isSeen: Boolean = false
|
||||
constructor(prototype: PartialMatchTrie,
|
||||
activeOcc: ConstraintOccurrence,
|
||||
occurrenceIndex: OccurrenceIndex) :
|
||||
this(activeOcc, occurrenceIndex)
|
||||
{
|
||||
this._prototype = prototype
|
||||
}
|
||||
|
||||
val isSeen: Boolean
|
||||
get() = _isSeen
|
||||
private var _prototype: PartialMatchTrie? = null
|
||||
|
||||
val vacantSlots: BitSet by lazy(LazyThreadSafetyMode.NONE) {
|
||||
val result = BitSet()
|
||||
result.set(0, keptConstraints.size + discardedConstraints.size)
|
||||
foldPath(result) { bits, mtn ->
|
||||
val slot = if (mtn.keepConstraint) mtn.index else mtn.index + keptConstraints.size
|
||||
bits.clear(slot)
|
||||
bits
|
||||
private val prototype: PartialMatchTrie?
|
||||
get() = _prototype
|
||||
|
||||
private val root: RootMatchTrieNode by lazy(LazyThreadSafetyMode.NONE) {
|
||||
prototype?.root?.let { root -> RootMatchTrieNode(root) } ?: RootMatchTrieNode()
|
||||
}
|
||||
|
||||
val vacant = BitSet()
|
||||
|
||||
fun matches(): Sequence<PartialMatch> = object : Sequence<PartialMatch> {
|
||||
|
||||
override fun iterator() = object : Iterator<PartialMatch> {
|
||||
|
||||
val firstRoot: MatchTrieNode? = root.firstChild
|
||||
|
||||
var next: PartialMatch? = null
|
||||
|
||||
override fun hasNext(): Boolean {
|
||||
if (next == null) {
|
||||
this.next = calcNext()
|
||||
}
|
||||
return (next != null)
|
||||
}
|
||||
|
||||
override fun next(): PartialMatch {
|
||||
if (next == null) {
|
||||
this.next = calcNext()
|
||||
}
|
||||
if (next != null) {
|
||||
val tmp: PartialMatch = next!!
|
||||
this.next = calcNext()
|
||||
return tmp
|
||||
}
|
||||
throw NoSuchElementException()
|
||||
}
|
||||
|
||||
private fun calcNext(): PartialMatch? {
|
||||
// the search always starts at the first root
|
||||
if (firstRoot == null) {
|
||||
return null
|
||||
}
|
||||
var mtn: MatchTrieNode? = firstRoot
|
||||
|
||||
while (mtn != null) {
|
||||
if (mtn.isSeen) {
|
||||
if (mtn.nextSibling == null) {
|
||||
mtn.parent?.setSeen()
|
||||
}
|
||||
mtn = mtn.nextSibling ?: mtn.parent
|
||||
}
|
||||
else if (mtn.isLeaf) {
|
||||
mtn.setSeen()
|
||||
vacant.clear()
|
||||
return mtn.match
|
||||
}
|
||||
else if (mtn.firstChild == null) {
|
||||
mtn.setSeen()
|
||||
vacant.or(mtn.vacantSlots)
|
||||
vacant.andNot(mtn.relatedFirstVacantSlots)
|
||||
}
|
||||
else {
|
||||
mtn = mtn.firstChild
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
}
|
||||
result
|
||||
|
||||
}
|
||||
|
||||
val relatedFirstVacantSlots: BitSet by lazy(LazyThreadSafetyMode.NONE) {
|
||||
val result = vacantSlots.clone() as BitSet
|
||||
result.and(relatedSlots[if (keepConstraint) index else index + keptConstraints.size])
|
||||
result
|
||||
private abstract inner class BaseMatchTrieNode() {
|
||||
|
||||
var _firstChild: MatchTrieNode? = null
|
||||
|
||||
}
|
||||
|
||||
val isLeaf: Boolean
|
||||
get() = vacantSlots.isEmpty
|
||||
private inner class RootMatchTrieNode(copyFrom: RootMatchTrieNode? = null) : BaseMatchTrieNode() {
|
||||
|
||||
val match: PartialMatch by lazy(LazyThreadSafetyMode.NONE) {
|
||||
val parentMatch = parent?.match ?: PartialMatch(rule, keptConstraints.size, discardedConstraints.size)
|
||||
if (keepConstraint) parentMatch.keep(occurrence, index)
|
||||
else parentMatch.discard(occurrence, index)
|
||||
}
|
||||
init {
|
||||
copyFrom?.firstChild?.firstAlive()?.let { mtn ->
|
||||
|
||||
fun setSeen() {
|
||||
this._isSeen = true
|
||||
}
|
||||
var first = MatchTrieNode(null, mtn)
|
||||
var prev = first
|
||||
var next = mtn.nextSibling?.firstAlive()
|
||||
|
||||
val firstChild: MatchTrieNode?
|
||||
get() {
|
||||
if (_firstChild == null) {
|
||||
var first: MatchTrieNode? = null
|
||||
var prev: MatchTrieNode? = null
|
||||
for (next in calcChildren()) {
|
||||
while (next != null) {
|
||||
val n = MatchTrieNode(null, next)
|
||||
prev._nextSibling = n
|
||||
prev = n
|
||||
next = n.nextSibling?.firstAlive()
|
||||
}
|
||||
|
||||
// prev._nextSibling = buildRoots()
|
||||
|
||||
this._firstChild = first
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
val firstChild: MatchTrieNode?
|
||||
get() {
|
||||
if (_firstChild == null) {
|
||||
this._firstChild = buildRoots()
|
||||
}
|
||||
return _firstChild
|
||||
}
|
||||
|
||||
private fun buildRoots(): MatchTrieNode? {
|
||||
var first: MatchTrieNode? = null
|
||||
var prev: MatchTrieNode? = null
|
||||
for ((idx, cst) in keptConstraints.withIndex()) {
|
||||
if (cst.symbol() == activeOcc.constraint().symbol()) {
|
||||
val next = MatchTrieNode(null, true, cst, activeOcc, idx)
|
||||
if (first == null) {
|
||||
first = next
|
||||
}
|
||||
prev?._nextSibling = next
|
||||
prev = next
|
||||
}
|
||||
}
|
||||
for ((idx, cst) in discardedConstraints.withIndex()) {
|
||||
if (cst.symbol() == activeOcc.constraint().symbol()) {
|
||||
val next = MatchTrieNode(null, false, cst, activeOcc, idx)
|
||||
if (first == null) {
|
||||
first = next
|
||||
}
|
||||
prev?._nextSibling = next
|
||||
prev = next
|
||||
}
|
||||
}
|
||||
return first
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private inner class MatchTrieNode(val parent: MatchTrieNode?,
|
||||
val keepConstraint: Boolean,
|
||||
val constraint: Constraint,
|
||||
val occurrence: ConstraintOccurrence,
|
||||
val index: Int) : BaseMatchTrieNode()
|
||||
{
|
||||
|
||||
constructor(parent: MatchTrieNode?, copyFrom: MatchTrieNode) :
|
||||
this(parent, copyFrom.keepConstraint, copyFrom.constraint, copyFrom.occurrence, copyFrom.index)
|
||||
{
|
||||
copyChildren(copyFrom)
|
||||
}
|
||||
|
||||
private var _isSeen: Boolean = false
|
||||
|
||||
val isSeen: Boolean
|
||||
get() = _isSeen
|
||||
|
||||
val vacantSlots: BitSet by lazy(LazyThreadSafetyMode.NONE) {
|
||||
val result = BitSet()
|
||||
result.set(0, keptConstraints.size + discardedConstraints.size)
|
||||
foldPath(result) { bits, mtn ->
|
||||
val slot = if (mtn.keepConstraint) mtn.index else mtn.index + keptConstraints.size
|
||||
bits.clear(slot)
|
||||
bits
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
val relatedFirstVacantSlots: BitSet by lazy(LazyThreadSafetyMode.NONE) {
|
||||
val result = vacantSlots.clone() as BitSet
|
||||
result.and(relatedSlots[if (keepConstraint) index else index + keptConstraints.size])
|
||||
result
|
||||
}
|
||||
|
||||
val isLeaf: Boolean
|
||||
get() = vacantSlots.isEmpty
|
||||
|
||||
val match: PartialMatch by lazy(LazyThreadSafetyMode.NONE) {
|
||||
val parentMatch = parent?.match ?: PartialMatch(rule, keptConstraints.size, discardedConstraints.size)
|
||||
if (keepConstraint) parentMatch.keep(occurrence, index)
|
||||
else parentMatch.discard(occurrence, index)
|
||||
}
|
||||
|
||||
fun setSeen() {
|
||||
this._isSeen = true
|
||||
}
|
||||
|
||||
val firstChild: MatchTrieNode?
|
||||
get() {
|
||||
if (_firstChild == null) {
|
||||
var first: MatchTrieNode? = null
|
||||
var prev: MatchTrieNode? = null
|
||||
for (next in calcChildren()) {
|
||||
if (first == null) {
|
||||
first = next
|
||||
}
|
||||
prev?._nextSibling = next
|
||||
prev = next
|
||||
}
|
||||
|
||||
this._firstChild = first
|
||||
}
|
||||
return _firstChild
|
||||
}
|
||||
|
||||
var _nextSibling: MatchTrieNode? = null
|
||||
|
||||
val nextSibling: MatchTrieNode?
|
||||
get() = _nextSibling
|
||||
|
||||
private fun calcChildren(): Iterable<MatchTrieNode> {
|
||||
val nextSlots = if (!relatedFirstVacantSlots.isEmpty) relatedFirstVacantSlots else vacantSlots
|
||||
val nextSlot = nextSlots.nextSetBit(0)
|
||||
if (nextSlot < 0) {
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
val keep = nextSlot < keptConstraints.size
|
||||
val nextIdx = if (keep) nextSlot else nextSlot - keptConstraints.size
|
||||
val nextCst = if (keep) keptConstraints[nextSlot] else discardedConstraints[nextIdx]
|
||||
|
||||
|
||||
return if (nextCst.symbol() == activeOcc.constraint().symbol() &&
|
||||
!anyInPath { it -> it.constraint.symbol() == activeOcc.constraint().symbol() })
|
||||
{
|
||||
arrayListOf(MatchTrieNode(this, keep, nextCst, activeOcc, nextIdx))
|
||||
}
|
||||
else {
|
||||
val (result, auxOccurrences) = lookupAuxOccurrences(nextCst)
|
||||
when (result) {
|
||||
Result.DEFINITIVE -> parent
|
||||
Result.NONE -> this
|
||||
Result.INCONCLUSIVE -> null
|
||||
}?.forEachInPath { mtn -> mtn._isSeen = true }
|
||||
|
||||
auxOccurrences.map { auxOcc -> MatchTrieNode(this, keep, nextCst, auxOcc, nextIdx) }
|
||||
}
|
||||
}
|
||||
|
||||
// the first component indicates the quality of the result:
|
||||
// DEFINITIVE indicates the only possible matches were returned
|
||||
// INCONCLUSIVE may yield some (ir-)relevant results
|
||||
// NONE means no results could have been found at all, so it's useless to attempt again with the same input
|
||||
private fun lookupAuxOccurrences(cst: Constraint): Pair<Result, Iterable<ConstraintOccurrence>> {
|
||||
return profiler.profile<Pair<Result, Iterable<ConstraintOccurrence>>> ("lookupAux_${cst.symbol()}") {
|
||||
|
||||
val fromArgs = ArrayList<ConstraintOccurrence>(4)
|
||||
for (arg in metaInstances(cst.arguments()) + cst.arguments().filter { arg -> !(arg is MetaLogical<*>) }) {
|
||||
when (arg) {
|
||||
is Logical<*> -> fromArgs.addAll(occIndex.forLogical(arg))
|
||||
|
||||
is Term -> fromArgs.addAll(occIndex.forTermAndConstraint(arg, cst))
|
||||
|
||||
is Any -> fromArgs.addAll(occIndex.forValue(arg))
|
||||
}
|
||||
}
|
||||
if (fromArgs.isNotEmpty()) {
|
||||
Result.DEFINITIVE.to(fromArgs.filter { occ -> occ.constraint().symbol() == cst.symbol() && !hasOccurrence(occ) })
|
||||
|
||||
} else {
|
||||
return profiler.profile<Pair<Result, Iterable<ConstraintOccurrence>>> ("lookupAux_default_${cst.symbol()}") {
|
||||
|
||||
val hasRelated = relatedFirstVacantSlots == vacantSlots
|
||||
val allArgsMetaLogicals = cst.arguments().all { a -> a is MetaLogical<*> }
|
||||
val noArgs = cst.arguments().isEmpty()
|
||||
|
||||
if (noArgs || (allArgsMetaLogicals && !hasRelated)) {
|
||||
Result.INCONCLUSIVE.to(occIndex.forSymbol(cst.symbol()).filter { occ -> !hasOccurrence(occ) })
|
||||
|
||||
}
|
||||
else {
|
||||
// all candidate occurrences should have been found by this time; if not, then there's none
|
||||
Result.NONE.to(emptyList())
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun copyChildren(copyFrom: MatchTrieNode) {
|
||||
copyFrom.firstChild?.firstAlive()?.let { mtn ->
|
||||
|
||||
val first = MatchTrieNode(this, mtn)
|
||||
var prev = first
|
||||
var next = mtn.nextSibling?.firstAlive()
|
||||
|
||||
while (next != null) {
|
||||
val n = MatchTrieNode(this, next)
|
||||
prev._nextSibling = n
|
||||
prev = n
|
||||
next = n.nextSibling?.firstAlive()
|
||||
}
|
||||
|
||||
this._firstChild = first
|
||||
}
|
||||
return _firstChild
|
||||
}
|
||||
|
||||
val nextSibling: MatchTrieNode?
|
||||
get() = _nextSibling
|
||||
|
||||
private fun calcChildren(): Iterable<MatchTrieNode> {
|
||||
val nextSlots = if (!relatedFirstVacantSlots.isEmpty) relatedFirstVacantSlots else vacantSlots
|
||||
val nextSlot = nextSlots.nextSetBit(0)
|
||||
if (nextSlot < 0) {
|
||||
return emptyList()
|
||||
fun firstAlive(): MatchTrieNode? {
|
||||
var fca: MatchTrieNode? = this
|
||||
while (!(fca?.occurrence?.isAlive() ?: true)) {
|
||||
fca = fca?._nextSibling
|
||||
}
|
||||
return fca
|
||||
}
|
||||
|
||||
val keep = nextSlot < keptConstraints.size
|
||||
val nextIdx = if (keep) nextSlot else nextSlot - keptConstraints.size
|
||||
val nextCst = if (keep) keptConstraints[nextSlot] else discardedConstraints[nextIdx]
|
||||
|
||||
val (result, auxOccurrences) = lookupAuxOccurrences(nextCst)
|
||||
when (result) {
|
||||
Result.DEFINITIVE -> parent
|
||||
Result.NONE -> this
|
||||
Result.INCONCLUSIVE -> null
|
||||
}?.forEachInPath { mtn -> mtn._isSeen = true }
|
||||
|
||||
return auxOccurrences.map { auxOcc -> MatchTrieNode(this, keep, nextCst, auxOcc, nextIdx) }
|
||||
}
|
||||
|
||||
// the first component indicates the quality of the result:
|
||||
// DEFINITIVE indicates the only possible matches were returned
|
||||
// INCONCLUSIVE may yield some (ir-)relevant results
|
||||
// NONE means no results could have been found at all, so it's useless to attempt again with the same input
|
||||
private fun lookupAuxOccurrences(cst: Constraint): Pair<Result, Iterable<ConstraintOccurrence>> {
|
||||
return profiler.profile<Pair<Result, Iterable<ConstraintOccurrence>>> ("lookupAux_${cst.symbol()}") {
|
||||
|
||||
val fromArgs = ArrayList<ConstraintOccurrence>(4)
|
||||
for (arg in metaInstances(cst.arguments()) + cst.arguments().filter { arg -> !(arg is MetaLogical<*>) }) {
|
||||
when (arg) {
|
||||
is Logical<*> -> fromArgs.addAll(occIndex.forLogical(arg))
|
||||
|
||||
is Term -> fromArgs.addAll(occIndex.forTermAndConstraint(arg, cst))
|
||||
|
||||
is Any -> fromArgs.addAll(occIndex.forValue(arg))
|
||||
private fun collectMetaInstances(meta: Collection<*>, list: ArrayList<Any>): ArrayList<Any> {
|
||||
constraint.arguments().zip(occurrence.arguments()).forEach { p ->
|
||||
if (p.first is MetaLogical<*> && meta.contains(p.first) && !list.contains(p.second)) {
|
||||
list.add(p.second!!)
|
||||
}
|
||||
}
|
||||
if (fromArgs.isNotEmpty()) {
|
||||
Result.DEFINITIVE.to(fromArgs.filter { occ -> occ.constraint().symbol() == cst.symbol() && !hasOccurrence(occ) })
|
||||
return list
|
||||
}
|
||||
|
||||
} else {
|
||||
return profiler.profile<Pair<Result, Iterable<ConstraintOccurrence>>> ("lookupAux_default_${cst.symbol()}") {
|
||||
private fun metaInstances(meta: Collection<*>): List<Any> =
|
||||
foldPath(ArrayList<Any>(4)) { list, mtn -> mtn.collectMetaInstances(meta, list) }
|
||||
|
||||
val hasRelated = relatedFirstVacantSlots == vacantSlots
|
||||
val allArgsMetaLogicals = cst.arguments().all { a -> a is MetaLogical<*> }
|
||||
val noArgs = cst.arguments().isEmpty()
|
||||
private fun hasOccurrence(occ: ConstraintOccurrence): Boolean =
|
||||
anyInPath { mtn -> mtn.occurrence == occ }
|
||||
|
||||
if (noArgs || (allArgsMetaLogicals && !hasRelated)) {
|
||||
Result.INCONCLUSIVE.to(occIndex.forSymbol(cst.symbol()).filter { occ -> !hasOccurrence(occ) })
|
||||
private inline fun anyInPath(predicate: (MatchTrieNode) -> Boolean): Boolean =
|
||||
foldPath(false) { b, mtn -> b || predicate(mtn) }
|
||||
|
||||
}
|
||||
else {
|
||||
// all candidate occurrences should have been found by this time; if not, then there's none
|
||||
Result.NONE.to(emptyList())
|
||||
}
|
||||
private inline fun forEachInPath(proc: (MatchTrieNode) -> Unit) =
|
||||
foldPath(Unit) { b, mtn -> proc(mtn) }
|
||||
|
||||
}
|
||||
private inline fun <T> foldPath(initVal: T, step: (T, MatchTrieNode) -> T): T {
|
||||
var mtn: MatchTrieNode? = this
|
||||
var currVal = initVal
|
||||
while(mtn != null) {
|
||||
currVal = step(currVal, mtn)
|
||||
mtn = mtn.parent
|
||||
}
|
||||
return currVal
|
||||
}
|
||||
}
|
||||
|
||||
// prepare the node for re-use
|
||||
override fun reset() {
|
||||
this._isSeen = false
|
||||
|
||||
while (!(_firstChild?.occurrence?.isAlive() ?: true)) {
|
||||
this._firstChild = _firstChild?._nextSibling
|
||||
}
|
||||
_firstChild?.reset()
|
||||
|
||||
while(!(_nextSibling?.occurrence?.isAlive() ?: true)) {
|
||||
this._nextSibling = _nextSibling?._nextSibling
|
||||
}
|
||||
_nextSibling?.reset()
|
||||
}
|
||||
|
||||
private fun collectMetaInstances(meta: Collection<*>, list: ArrayList<Any>): ArrayList<Any> {
|
||||
constraint.arguments().zip(occurrence.arguments()).forEach { p ->
|
||||
if (p.first is MetaLogical<*> && meta.contains(p.first) && !list.contains(p.second)) {
|
||||
list.add(p.second!!)
|
||||
}
|
||||
}
|
||||
return list
|
||||
}
|
||||
|
||||
private fun metaInstances(meta: Collection<*>): List<Any> =
|
||||
foldPath(ArrayList<Any>(4)) { list, mtn -> mtn.collectMetaInstances(meta, list) }
|
||||
|
||||
private fun hasOccurrence(occ: ConstraintOccurrence): Boolean =
|
||||
anyInPath { mtn -> mtn.occurrence == occ }
|
||||
|
||||
private inline fun anyInPath(predicate: (MatchTrieNode) -> Boolean): Boolean =
|
||||
foldPath(false) { b, mtn -> b || predicate(mtn) }
|
||||
|
||||
private inline fun forEachInPath(proc: (MatchTrieNode) -> Unit) =
|
||||
foldPath(Unit) { b, mtn -> proc(mtn) }
|
||||
|
||||
private inline fun <T> foldPath(initVal: T, step: (T, MatchTrieNode) -> T): T {
|
||||
var mtn: MatchTrieNode? = this
|
||||
var currVal = initVal
|
||||
while(mtn != null) {
|
||||
currVal = step(currVal, mtn)
|
||||
mtn = mtn.parent
|
||||
}
|
||||
return currVal
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -382,3 +460,8 @@ internal class PartialMatchTrie(val rule: Rule,
|
|||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ class TestMatcher {
|
|||
))
|
||||
).let { builder ->
|
||||
builder.indices().run {
|
||||
Matcher(first, occurrence("main"), second).matching().let { matches ->
|
||||
Matcher(first).matching(occurrence("main"), second).let { matches ->
|
||||
val match = matches.single()
|
||||
assertEquals(match.rule, builder.rules.first())
|
||||
assertTrue(match.keptOccurrences.isEmpty())
|
||||
|
|
@ -57,7 +57,7 @@ class TestMatcher {
|
|||
))
|
||||
).let { builder ->
|
||||
builder.indices().run {
|
||||
Matcher(first, occurrence("main"), second).matching().let { matches ->
|
||||
Matcher(first).matching(occurrence("main"), second).let { matches ->
|
||||
assertTrue(matches.all {m -> m.successful})
|
||||
assertEquals(builder.rules.toSet(), matches.map { m -> m.rule }.toSet())
|
||||
matches.forEach { m -> assertTrue(m.keptOccurrences.size + m.discardedOccurrences.size == 1) }
|
||||
|
|
@ -92,7 +92,7 @@ class TestMatcher {
|
|||
))
|
||||
).let { builder ->
|
||||
builder.indices().run {
|
||||
Matcher(first, occurrence("main"), second).matching().let { matches ->
|
||||
Matcher(first).matching(occurrence("main"), second).let { matches ->
|
||||
assertTrue(matches.all {m -> m.successful})
|
||||
assertEquals(builder.rules.drop(1).toSet(), matches.map { m -> m.rule }.toSet())
|
||||
}
|
||||
|
|
@ -122,7 +122,7 @@ class TestMatcher {
|
|||
))
|
||||
).let { builder ->
|
||||
builder.indices(occurrence("aux")).run {
|
||||
Matcher(first, occurrence("main"), second).matching().let { matches ->
|
||||
Matcher(first).matching(occurrence("main"), second).let { matches ->
|
||||
assertTrue(matches.all {m -> m.successful})
|
||||
assertEquals(builder.rules.toSet(), matches.map { m -> m.rule }.toSet())
|
||||
}
|
||||
|
|
@ -149,7 +149,7 @@ class TestMatcher {
|
|||
))
|
||||
).let { builder ->
|
||||
builder.indices().run{
|
||||
Matcher(first, occurrence("main", "bar"), second).matching().let { matches ->
|
||||
Matcher(first).matching(occurrence("main", "bar"), second).let { matches ->
|
||||
assertTrue(matches.all {m -> m.successful})
|
||||
assertEquals(builder.rules.drop(1), matches.map { m -> m.rule }.toList())
|
||||
}
|
||||
|
|
@ -175,7 +175,7 @@ class TestMatcher {
|
|||
constraint("bar")
|
||||
))
|
||||
).indices().run{
|
||||
Matcher(first, occurrence("main", "qux"), second).matching().let { matches ->
|
||||
Matcher(first).matching(occurrence("main", "qux"), second).let { matches ->
|
||||
assertFalse(matches.any())
|
||||
}
|
||||
}
|
||||
|
|
@ -197,13 +197,13 @@ class TestMatcher {
|
|||
))
|
||||
)
|
||||
).indices().run {
|
||||
Matcher(first, occurrence("qux"), second).matching().let { matches ->
|
||||
Matcher(first).matching(occurrence("qux"), second).let { matches ->
|
||||
assertFalse(matches.any())
|
||||
}
|
||||
Matcher(first, occurrence("foo"), second).matching().let { matches ->
|
||||
Matcher(first).matching(occurrence("foo"), second).let { matches ->
|
||||
assertSame(1, matches.size)
|
||||
}
|
||||
Matcher(first, occurrence("bar"), second).matching().let { matches ->
|
||||
Matcher(first).matching(occurrence("bar"), second).let { matches ->
|
||||
assertSame(1, matches.size)
|
||||
}
|
||||
}
|
||||
|
|
@ -224,7 +224,7 @@ class TestMatcher {
|
|||
)
|
||||
)
|
||||
).indices().run {
|
||||
Matcher(first, occurrence("foo", "blah", b), second).first().run {
|
||||
Matcher(first).matches(occurrence("foo", "blah", b), second).first().run {
|
||||
assert(successful)
|
||||
assertEquals("blah", logicalContext.variable(A).findRoot().value())
|
||||
assertSame(b, logicalContext.variable(B))
|
||||
|
|
@ -261,14 +261,14 @@ class TestMatcher {
|
|||
))
|
||||
).run {
|
||||
indices().run {
|
||||
Matcher(first, occurrence("foo", 1), second).matching().let { matches ->
|
||||
Matcher(first).matching(occurrence("foo", 1), second).let { matches ->
|
||||
assertFalse(matches.any())
|
||||
}
|
||||
}
|
||||
|
||||
// same parameter -- 4 matches (all permutations)
|
||||
indices(occurrence("foo", 42)).run {
|
||||
Matcher(first, occurrence("foo", 42), second).matching().let { matches ->
|
||||
Matcher(first).matching(occurrence("foo", 42), second).let { matches ->
|
||||
assertTrue(matches.all {m -> m.successful})
|
||||
assertEquals(4, matches.count())
|
||||
assertTrue(matches.all { m -> m.allOccurrences().toSet().size == 2 })
|
||||
|
|
@ -276,7 +276,7 @@ class TestMatcher {
|
|||
}
|
||||
|
||||
indices(occurrence("foo", 42)).run {
|
||||
Matcher(first, occurrence("foo", 16), second).matching().let { matches ->
|
||||
Matcher(first).matching(occurrence("foo", 16), second).let { matches ->
|
||||
assertTrue(matches.all {m -> m.successful})
|
||||
assertEquals(2, matches.count())
|
||||
assertEquals(listOf("main1", "main1"), matches.map { m -> m.rule.tag() }.toList())
|
||||
|
|
@ -296,7 +296,7 @@ class TestMatcher {
|
|||
y.set(456)
|
||||
|
||||
indices(occurrence("foo", x)).run {
|
||||
Matcher(first, occurrence("foo", y), second).matching().let { matches ->
|
||||
Matcher(first).matching(occurrence("foo", y), second).let { matches ->
|
||||
assertTrue(matches.all {m -> m.successful})
|
||||
assertEquals(2, matches.count())
|
||||
assertEquals(listOf("main1", "main1"), matches.map { m -> m.rule.tag() }.toList())
|
||||
|
|
@ -308,7 +308,7 @@ class TestMatcher {
|
|||
w.findRoot().union(v)
|
||||
|
||||
indices(occurrence("foo", v)).run {
|
||||
Matcher(first, occurrence("foo", w), second).matching().let { matches ->
|
||||
Matcher(first).matching(occurrence("foo", w), second).let { matches ->
|
||||
assertTrue(matches.all {m -> m.successful})
|
||||
assertEquals(2, matches.count())
|
||||
assertTrue(matches.all{ m -> m.allOccurrences().toSet().size == 2 })
|
||||
|
|
@ -487,12 +487,12 @@ class TestMatcher {
|
|||
)
|
||||
|
||||
program.indices(occurrence("foo", x, parse("a{c}"))).run {
|
||||
Matcher(first, occurrence("foo", y, parse("a{b}")), second).matching().let { matches ->
|
||||
Matcher(first).matching(occurrence("foo", y, parse("a{b}")), second).let { matches ->
|
||||
assertEquals("foo2", matches.single().rule.tag())
|
||||
}
|
||||
}
|
||||
program.indices(occurrence("foo", x, parse("a{b}"))).run {
|
||||
Matcher(first, occurrence("foo", y, parse("a{c}")), second).matching().let { matches ->
|
||||
Matcher(first).matching(occurrence("foo", y, parse("a{c}")), second).let { matches ->
|
||||
assertEquals("foo2", matches.single().rule.tag())
|
||||
}
|
||||
}
|
||||
|
|
@ -530,6 +530,6 @@ class TestMatcher {
|
|||
|
||||
private fun Match.allOccurrences() = (keptOccurrences + discardedOccurrences)
|
||||
|
||||
private fun Matcher.matching() = this.filter { m -> m.successful }
|
||||
private fun Matcher.matching(activeOcc: ConstraintOccurrence, aux: OccurrenceIndex) = this.matches(activeOcc, aux).filter { m -> m.successful }
|
||||
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue