UNPKG

stsys

Version:

String rewriting system (semi-Thue system)

261 lines (238 loc) 5.93 kB
class Cmp { constructor(preorderElementRegex) { this.preorder = null; this.preorderElementRegex = preorderElementRegex || /^[a-zA-Z]$/; } checkPreorder(preorder) { if (!(preorder instanceof Array) || preorder.length === 0) throw new Error('invalid preorder: non-empty array of letters expected'); const faultyElement = preorder.find(sym => typeof sym !== 'string' || !this.preorderElementRegex.test(sym)); if (faultyElement) throw new Error('invalid element in preorder: ' + faultyElement); for (let i = 0; i < preorder.length - 1; ++i) { const sym = preorder[i].length > 1 ? preorder[i].charAt(0) : preorder[i]; for (let j = i + 1; j < preorder.length; ++j) { if (sym === preorder[j].charAt(0)) throw new Error('duplicates in preorder: ' + sym); } } } setPreorder(preorder) { this.checkPreorder(preorder); this.preorder = preorder.map(sym => sym); } getPreorder() { return this.preorder.map(sym => sym); } } class LpoCmp extends Cmp { constructor(preorder) { super(); this.setPreorder(preorder); } compare(s1, s2) { return lpoCmp.call(this, s1, 0, s2, 0, []); } getOrdering() { return 'lpo'; } toString() { return 'LPO using preorder ' + this.preorder.join(' > '); } } class LexCmp extends Cmp { constructor(preorder) { super(); this.setPreorder(preorder); } compare(s1, s2) { let d = s1.length - s2.length; if (d !== 0) return d; for (let i = 0; i < s1.length; i++) { d = preorderCmp(this.preorder, s1.charAt(i), s2.charAt(i)); if (d !== 0) return d; } return 0; } getOrdering() { return 'lex'; } toString() { return 'Length/lexicographic ordering using preorder ' + this.preorder.join(' > '); } } class SylCmp extends Cmp { constructor(preorder, defaultDirection) { super(/^[a-zA-Z](?::[lr])?$/); this.directionMap = null; this.defaultDirection = defaultDirection || 'l'; this.setPreorder(preorder); } setPreorder(preorder) { this.checkPreorder(preorder); this.directionMap = {}; this.preorder = preorder.map(sym => { if (sym.charAt(1) === ':') { const firstChar = sym.charAt(0); this.directionMap[firstChar] = sym.charAt(2); return firstChar; } this.directionMap[sym] = this.defaultDirection; return sym; }); } compare(s1, s2) { return sylCmp.call(this, 0, s1, 0, s1.length, s2, 0, s2.length); } getOrdering() { return 'syl-' + this.defaultDirection; } toString() { return 'Syllable ordering with default direction ' + this.defaultDirection + ' nested as per preorder ' + this.preorder.map(sym => this.directionMap[sym] === this.defaultDirection ? sym : sym + ':' + this.directionMap[sym]).join(' > '); } } function preorderCmp(preorder, c1, c2) { const i1 = preorder.indexOf(c1); const i2 = preorder.indexOf(c2); if (i1 < 0 || i2 < 0) throw new Error('Preorder comparison failed for letters ' + c1 + ' and ' + c2 + ' (preorder incomplete)'); return i2 - i1; } function lpoCmp(s1, i1, s2, i2, cmpCash) { const l1 = s1.length - i1; const l2 = s2.length - i2; if (l1 === 0 && l2 === 0) return 0; if (l1 === 0) return -1; if (l2 === 0) return 1; const cmpCashIdx = i1 * s2.length + i2; if (l1 === l2 && substringEqual(s1, i1, s2, i2)) return cmpCash[cmpCashIdx] = 0; if (typeof cmpCash[cmpCashIdx] === 'number') return cmpCash[cmpCashIdx]; if (lpoCmp.call(this, s1, i1 + 1, s2, i2, cmpCash) >= 0) return cmpCash[cmpCashIdx] = 1; if (lpoCmp.call(this, s1, i1, s2, i2 + 1, cmpCash) <= 0) return cmpCash[cmpCashIdx] = -1; let c1 = s1.charAt(i1); let c2 = s2.charAt(i2); if (c1 === c2) return cmpCash[cmpCashIdx] = lpoCmp.call(this, s1, i1 + 1, s2, i2 + 1, cmpCash); else if (preorderCmp(this.preorder, c1, c2) > 0) return cmpCash[cmpCashIdx] = 1; return cmpCash[cmpCashIdx] = -1; } function sylCmp(poi, s1, from1, to1, s2, from2, to2) { if (poi >= this.preorder.length) return 0; const poc = this.preorder[poi]; let o1 = countLetterOccurrences(poc, s1, from1, to1); let o2 = countLetterOccurrences(poc, s2, from2, to2); if (o1 > o2) return 1; if (o1 < o2) return -1; if (this.directionMap[poc] === 'l') while (from1 < to1 || from2 < to2) { o1 = s1.indexOf(poc, from1); o2 = s2.indexOf(poc, from2); if (o1 < 0 || o1 > to1) o1 = to1; if (o2 < 0 || o2 > to2) o2 = to2; let r = sylCmp.call(this, poi + 1, s1, from1, o1, s2, from2, o2); if (r !== 0) return r; from1 = o1 + 1; from2 = o2 + 1; } else while (from1 < to1 || from2 < to2) { o1 = s1.lastIndexOf(poc, to1 - 1); o2 = s2.lastIndexOf(poc, to2 - 1); if (o1 < from1) o1 = from1; else o1++; if (o2 < from2) o2 = from2; else o2++; let r = sylCmp.call(this, poi + 1, s1, o1, to1, s2, o2, to2); if (r !== 0) return r; to1 = o1 - 1; to2 = o2 - 1; } return 0; } function substringEqual(s1, i1, s2, i2) { while (i1 < s1.length) { if (s1.charAt(i1++) !== s2.charAt(i2++)) return false; } return true; } function countLetterOccurrences(c, str, from, to) { let n = 0; while (from < to) { if (str.charAt(from++) === c) n++; } return n; } module.exports.createComparer = function(name, preorder) { switch (name) { case 'lex': return new LexCmp(preorder); case 'lpo': return new LpoCmp(preorder); case 'syl-l': return new SylCmp(preorder, 'l'); case 'syl-r': return new SylCmp(preorder, 'r'); } throw new Error(name ? 'unknown ordering: ' + name : 'no ordering specified'); };