UNPKG

stsys

Version:

String rewriting system (semi-Thue system)

406 lines (393 loc) 11.5 kB
const strpair = require('./strpair'); const createRuleMgr = require('./rulemgr').createRuleManager; const createCpMgr = require('./cpmgr').createCpManager; const createGoalMgr = require('./goalmgr').createGoalManager; const createComparer = require('./cmp').createComparer; const statistics = { rulesGenerated: 'Rules generated:', rulesDemoted: 'Rules demoted :', rulesDiscarded: 'Rules discarded:', cpsAdded: 'CPs added :', cpsGenerated: 'CPs generated :', cpsDiscarded: 'CPs discarded :', cpsObsolete: ' - parent(s) discarded :', cpsTrivialImmediate: ' - trivial (immediately):', cpsTrivialPrereduced: ' - trivial (pre-reduced):', cpsTrivialSelected: ' - trivial (selected) :' }; class Prover { constructor(axioms, options) { if (!axioms) throw new Error('missing axioms'); if (typeof axioms === 'string') this.axioms = [strpair.parseStrPair(axioms)]; else if (axioms instanceof Array) { if (axioms.length === 0) throw new Error('no axioms (empty Array)'); this.axioms = axioms.map(axiom => strpair.parseStrPair(axiom)); } else throw new Error('invalid specification of axioms (neither Array nor string)'); this.initialized = false; this.cpMgr = null; this.ruleMgr = null; this.goalMgr = null; this.state = null; this.hrtimeElapsed = null; this.statistics = null; this.comparer = options && options.preorder && options.ordering && createComparer(options && options.ordering, options && options.preorder) || null; this.logger = options && typeof options.logger === 'function' ? options.logger : null, this.logLevel = options && typeof options.logLevel === 'number' ? options.logLevel : 0 } initialize(options) { if (options && options.statistics) { this.statistics = {}; for (let stat in statistics) { this.statistics[stat] = 0; } } else this.statistics = null; if (this.comparer) { if (options && options.ordering && this.comparer.getOrdering() !== options.ordering) this.comparer = createComparer(options.ordering, options.preorder || this.comparer.getPreorder()); else if (options && options.preorder) this.comparer.setPreorder(options.preorder); } else this.comparer = createComparer(options && options.ordering, options && options.preorder); this.ruleMgr = createRuleMgr(); this.cpMgr = createCpMgr(); this.goalMgr = createGoalMgr(this); this.axioms.forEach(axiom => { this.cpMgr.addCp(axiom.copy()); if (this.statistics) this.statistics.cpsAdded++; }); this.hrtimeElapsed = null; this.initialized = true; } isLoggable(logLevel) { return this.logger && this.logLevel >= logLevel; } log(s) { if (this.logger) this.logger(s); } checkEquality(s1, s2) { if (s2) { if (s1 === s2) return true; if (this.ruleMgr) return this.ruleMgr.reduce(s1) === this.ruleMgr.reduce(s2); } else { const sp = strpair.parseStrPair(s1); if (this.ruleMgr) sp.reduce(this.ruleMgr); return sp.isTrivial(); } } run(options) { const tStart = process.hrtime(); if (options) { if (typeof options.logger === 'function') this.logger = options.logger; if (typeof options.logLevel === 'number') this.logLevel = options.logLevel; } if (!this.initialized) this.initialize(options); let maxRuleGen = options && options.maxRuleGen; let timeout = options && options.timeout; const preReduceCps = options && options.preRedCps; if (typeof timeout !== 'number') timeout = -1; if (typeof maxRuleGen !== 'number') maxRuleGen = -1; if (this.isLoggable(2)) { this.log('Ordering: ' + this.comparer.toString()); if (timeout >= 0) this.log('Timeout after ' + timeout + 'ms'); if (maxRuleGen >= 0 && this.isLoggable(2)) this.log('Maximal number of generated rules limited to ' + maxRuleGen); } if (options && options.goals) { this.goalMgr.addGoals(options.goals); this.goalMgr.reduceAll(this.ruleMgr); } if (this.isLoggable(1)) this.log('Running...'); let cp = null; while (!this.goalMgr.allGoalsProved()) { if (maxRuleGen === 0) { if (this.isLoggable(1)) this.log('--- Maximal number of generated rules has been reached ---'); break; } if (timeout >= 0 && hrtimeToMilliseconds(process.hrtime(tStart)) > timeout) { if (this.isLoggable(1)) this.log('--- Timeout ---'); break; } cp = this.cpMgr.getNext(); if (!cp) break; if (this.isLoggable(5)) this.log('Next CP: ' + cp.toString(true)); if (cp.isObsolete()) { if (this.isLoggable(5)) this.log(' discarded because it is obsolete'); if (this.statistics) { this.statistics.cpsDiscarded++; this.statistics.cpsObsolete++; } continue; } cp.reduce(this.ruleMgr); if (this.isLoggable(5)) this.log(' ==> ' + cp.toString()); if (cp.isTrivial()) { if (this.statistics) { this.statistics.cpsDiscarded++; this.statistics.cpsTrivialSelected++; } continue; } cp.order(this.comparer); this.ruleMgr.addRule(cp); if (this.statistics) this.statistics.rulesGenerated++; if (this.isLoggable(3)) this.log('New rule #' + cp.id + ': ' + cp.toString()); if (maxRuleGen > 0) maxRuleGen--; this.goalMgr.reduceAll(this.ruleMgr, cp); this.ruleMgr.reduceAll(cp, this); overlapsIntoNewRule.call(this, cp, preReduceCps); overlapsIntoOldRules.call(this, cp, preReduceCps); } this.hrtimeElapsed = process.hrtime(tStart); if (!this.cpMgr.hasNext()) this.state = 'completed'; else if (this.goalMgr.allGoalsProved()) this.state = 'proved'; else this.state = 'stopped'; } } function overlapsIntoNewRule(newRule, preReduceCps) { for (let i = 1; i < newRule.left.length; i++) { let ovlp = this.ruleMgr.findOverlap(newRule.left, i); if (ovlp) { for (let j = 0; j < ovlp.length; j++) { const r = ovlp[j]; const cp = newRule.overlapIntoAt(r, i); if (this.statistics) this.statistics.cpsGenerated++; if (preReduceCps) cp.reduce(this.ruleMgr); if (!cp.isTrivial()) { this.cpMgr.addCp(cp); if (this.isLoggable(6)) this.log('Adding critical pair from #' + newRule.id + ' and #' + r.id +': ' + cp.toString()); if (this.statistics) this.statistics.cpsAdded++; } else if (this.statistics) { if (preReduceCps) this.statistics.cpsTrivialPrereduced++; else this.statistics.cpsTrivialImmediate++; this.statistics.cpsDiscarded++; } } } } } function overlapsIntoOldRules(newRule, preReduceCps) { let i = 0; let left = newRule.left; let l = left.length - 1; let indexNode = this.ruleMgr.suffixIndexRoot; while (i < l) { if (!(indexNode = indexNode[left.charAt(i++)])) return; let ovlps = indexNode.ovlps; if (!ovlps) continue; for (let j = 0; j < ovlps.length; j++) { const rule = ovlps[j]; const cp = rule.overlapIntoAt(newRule, rule.left.length - i); if (this.statistics) ++this.statistics.cpsGenerated; if (preReduceCps) cp.reduce(this.ruleMgr); if (!cp.isTrivial()) { this.cpMgr.addCp(cp); if (this.isLoggable(6)) this.log('Adding critical pair from #' + rule.id + ' and #' + newRule.id +': ' + cp.toString()); if (this.statistics) this.statistics.cpsAdded++; } else if (this.statistics) { if (preReduceCps) ++this.statistics.cpsTrivialPrereduced; else ++this.statistics.cpsTrivialImmediate; ++this.statistics.cpsDiscarded; } } } } function hrtimeToMilliseconds(hrtime) { return hrtime[0] * 1000 + Math.round(hrtime[1] / 1e6); } function hrtimeToString(hrtime) { const millis = hrtime[1] / 1000000; let seconds = hrtime[0]; if (seconds < 1) return Math.round(millis) + 'ms'; if (seconds < 10) { let roundedMillis = Math.round(millis); if (roundedMillis >= 1000) { roundedMillis -= 1000; ++seconds; } let timeStr = '00' + roundedMillis; return seconds + '.' + timeStr.substring(timeStr.length - 3) + 's'; } if (millis >= 500) ++seconds; if (seconds < 60) return seconds + 's'; if (seconds < 3600) { let minutes = Math.floor(seconds / 60); seconds %= 60; return minutes + 'm' + seconds + 's'; } let hours = Math.floor(seconds / 3600); seconds %= 3600; let minutes = Math.floor(seconds / 60); seconds %= 60; return hours + 'h' + minutes + 'm' + seconds + 's'; } module.exports.createProver = function(axioms, options) { const prover = new Prover(axioms, options); return { run: function(options){ prover.run(options); return prover.state; }, rerun: function(options){ prover.initialized = false; prover.run(options); return prover.state; }, normalize: function(s){ return prover.ruleMgr ? prover.ruleMgr.reduce(s) : s; }, testEquality: function(s1, s2){ if (prover.checkEquality(s1, s2)) return true; if (prover.state === 'completed') return false; }, getState: function(){ return prover.state; }, getHrtimeElapsed: function(){ return prover.hrtimeElapsed; }, getTimeElapsed: function(){ return hrtimeToString(prover.hrtimeElapsed); }, getAxioms: function(){ return prover.axioms.map(function(axiom){ return axiom.toString(); }); }, getOrdering: function(){ return prover.comparer && prover.comparer.getOrdering(); }, getPreorder: function(){ return prover.comparer && prover.comparer.getPreorder(); }, getRulesCount: function(){ return prover.ruleMgr ? prover.ruleMgr.getRulesCount() : 0; }, getRules: function(){ return prover.ruleMgr ? prover.ruleMgr.getRules() : []; }, getTotalCpCount: function(){ return prover.cpMgr ? prover.cpMgr.totalCpCount() : 0; }, getTotalGoalsCount: function(){ return prover.goalMgr ? prover.goalMgr.getGoalsCount() : 0; }, getProvedGoalsCount: function(){ return prover.goalMgr ? prover.goalMgr.getGoalsCount() - prover.goalMgr.unprovedGoalsCount: 0; }, getGoals: function(){ return prover.goalMgr ? prover.goalMgr.getGoals() : []; }, clearGoals: function(){ if (prover.goalMgr) prover.goalMgr.clearGoals(); }, getStatistics: function(){ return prover.statistics ? Object.create(prover.statistics) : null; }, getStatisticsAsString: function(){ if (!prover.statistics) return; const sb = []; for (let stat in statistics) { sb.push([statistics[stat], prover.statistics[stat]].join(' ')); } return sb.join('\n'); } }; };