stsys
Version:
String rewriting system (semi-Thue system)
406 lines (393 loc) • 11.5 kB
JavaScript
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');
}
};
};