UNPKG

@multila/multila-parser-generator

Version:
397 lines 14 kB
"use strict"; /* MULTILA Compiler and Computer Architecture Infrastructure Copyright (c) 2022 by Andreas Schwenk, contact@multila.org Licensed by GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 */ Object.defineProperty(exports, "__esModule", { value: true }); exports.LR1_Edge = exports.LR1_StateItem = exports.LR1_State = void 0; const lr1_1 = require("./lr1"); const lr1rule_1 = require("./lr1rule"); const lr1table_1 = require("./lr1table"); /** * Paring state (refer to LR(1) parsing textbooks). */ class LR1_State { constructor(lr1) { /** * index number of the state (labeling of the state graph; root-state has * index 0). */ this.index = -1; /** * CLOSURE_1 items of the state. ATTENTION: implemented as array; not as set. */ this.itemSet = []; /** * reference to LR1 object */ this.lr1 = null; /** * outgoing edges of state transitions */ this.outEdges = []; // TODO: private! /** * incoming edges of state transitions */ this.inEdges = []; // TODO: private! this.lr1 = lr1; } setIndex(i) { this.index = i; } getIndex() { return this.index; } /** * Compares a given state to the current state object * @param s state to be compared with * @returns true, if the given state is equal th the current state object. */ equal(s) { const n = this.itemSet.length; // If the number of items in item set differs, states are obviously not // equal. if (n != s.itemSet.length) { return false; } // since the item set is implemented as arrays, we have to compare them // in two loops. for (let i = 0; i < n; i++) { let found = false; for (let j = 0; j < n; j++) { if (this.itemSet[i].equal(s.itemSet[j])) { found = true; break; } } if (!found) return false; } return true; } /** * Adds a new item if it is not yet present in the item set. * NOTE: items are said to be equal, if the rule and the current position are * equal. The lookahead set is updated such that the new lookahead-set is * constructed from the previous lookahead-set unified the lookahead-set of * the given item. * @param newItem The item to be added * @returns true, if the number of items increased; otherwise false */ addItem(newItem) { let found = false; for (const item of this.itemSet) { // only compare the position and the rule if (item.pos == newItem.pos && item.rule == newItem.rule) { found = true; // unify lookahead sets item.lookAheadSet = new Set([ ...item.lookAheadSet, ...newItem.lookAheadSet, ]); break; } } // add the item if it is not yet in the set if (!found) { this.itemSet.push(newItem); return true; } return false; } /** * Calculates CLOSURE_1 from an initial item set as follows: * Let a, b, c in (Sigma uu V)^* and let "." denote the current position. * For all items of the form [x -> a . y b, L], * with rule [x -> a . y b] and lookahead set L, * add all production rules [y -> . c, FIRST(bL)] to the closure. * If only FIRST(bL) is different to existing items, than modify the * existing item where only FIRST differs by L := L uu FIRST(bL). * Run this procedure until no more changes occur. * This method also calculates and returns transitions to other states. * For these states, only the initial CLOSURE_1 is drawn, s.t. a recursive * calls construct the set of all states. * Transitions are calculated as follows: Determine the set of outgoing * terminals (called "t") and the set of outgoing non-terminals (called "nt"). * These sets are constructed by the set of (non-)terminals at the current * position. * For each element of t and nt, an outgoing edge and a (temporary) destination * state is created. * For all items [x -> a . y b, L], add [x -> a y . b, L] to the set of * initial items of the appropriate destination state. * @returns destination states for the transitions outgoing from this state. */ calcItemSet() { const rules = this.lr1.getRules(); const first = this.lr1.getFirst(); // first set // run until convergency let change = false; do { const n = this.itemSet.length; change = false; const newItems = []; for (const item of this.itemSet) { if (item.pos < item.rule.rhs.length && item.rule.rhs[item.pos].type === lr1rule_1.LR1_RuleItemType.NonTerminal) { const y = item.rule.rhs[item.pos].value; for (const rule of rules) { if (rule.lhs === y) { const newItem = new LR1_StateItem(); newItem.pos = 0; newItem.rule = rule; if (item.pos + 1 < item.rule.rhs.length) { const b = item.rule.rhs[item.pos + 1]; const v = b.value; if (b.type === lr1rule_1.LR1_RuleItemType.Terminal) { newItem.lookAheadSet.add(v); } else { newItem.lookAheadSet = new Set(first[v]); } } else { newItem.lookAheadSet = new Set(item.lookAheadSet); } newItems.push(newItem); } } } } for (const item of newItems) { this.addItem(item); } if (this.itemSet.length > n) { change = true; } } while (change); // calculate successor items const t = new Set(); // successor terminals const nt = new Set(); // successor non-terminals for (const item of this.itemSet) { if (item.pos < item.rule.rhs.length) { const x = item.rule.rhs[item.pos]; if (x.type === lr1rule_1.LR1_RuleItemType.Terminal) { t.add(x.value); } else { nt.add(x.value); } } } // create successor states (initial version only) const s = []; for (const ti of t) { const si = new LR1_State(this.lr1); s.push(si); const e = new LR1_Edge(this, si); e.label.type = lr1rule_1.LR1_RuleItemType.Terminal; e.label.value = ti; this.outEdges.push(e); si.inEdges.push(e); for (const item of this.itemSet) { if (item.pos < item.rule.rhs.length) { const x = item.rule.rhs[item.pos]; if (x.type === lr1rule_1.LR1_RuleItemType.Terminal && x.value == ti) { const i = item.clone(); i.pos++; if (si.addItem(i) == false) { // TODO const bp = 1337; process.exit(-1); } } } } } for (const nti of nt) { const si = new LR1_State(this.lr1); s.push(si); const e = new LR1_Edge(this, si); e.label.type = lr1rule_1.LR1_RuleItemType.NonTerminal; e.label.value = nti; this.outEdges.push(e); si.inEdges.push(e); for (const item of this.itemSet) { if (item.pos < item.rule.rhs.length) { const x = item.rule.rhs[item.pos]; if (x.type === lr1rule_1.LR1_RuleItemType.NonTerminal && x.value == nti) { const i = item.clone(); i.pos++; if (si.addItem(i) == false) { // TODO const bp = 1337; process.exit(-1); } } } } } return s; } /** * Calculate REDUCE entries for the parsing table. * For each item of the state, check if the current position is right to * the last item of the right-hand side. In this case, a reduce entry is * constructed for each item of the lookahead set of that item. * @returns dictionary of table entries for each item of the lookahead set */ calcReduceEntries() { const rules = this.lr1.getRules(); const entries = {}; for (const item of this.itemSet) { if (item.pos === item.rule.rhs.length) { for (const terminal of item.lookAheadSet) { const entry = new lr1table_1.LR1_TableEntry(); entry.value = item.rule.index; if (terminal in entries) { const r1 = entries[terminal].value; const r2 = entry.value; throw new lr1_1.LR1Error('reduce/reduce conflict for rules ' + r1 + ' [' + rules[r1] + '] and ' + r2 + ' [' + rules[r2] + ']'); } entries[terminal] = entry; } } } return entries; } /** * Stringifies the state. * @returns stringified version of the state */ toString() { let s = 'state ' + this.index + ' = {\n'; for (const item of this.itemSet) { s += ' ' + item.toString() + '\n'; } s += ' inEdges = '; for (const edge of this.inEdges) { s += edge.label.type === lr1rule_1.LR1_RuleItemType.Terminal ? '"' + edge.label.value + '"' : edge.label.value; s += ':' + edge.src.index + '->' + edge.dest.index; s += ', '; } s += '\n'; s += ' outEdges = '; for (const edge of this.outEdges) { s += edge.label.type === lr1rule_1.LR1_RuleItemType.Terminal ? '"' + edge.label.value + '"' : edge.label.value; s += ':' + edge.src.index + '->' + edge.dest.index; s += ', '; } s += '\n'; s += '}\n'; return s; } } exports.LR1_State = LR1_State; /** * State item := item of CLOSURE_1. */ class LR1_StateItem { constructor() { /** * current position (referred to the items of the right-hand side) */ this.pos = 0; /** * lookahead set := items after the right-most item of the rule. */ this.lookAheadSet = new Set(); /** * reference to the rule */ this.rule = null; } /** * Checks if the given item is equal to the current item object. * @param i item that is compared to the current item object * @returns true, if the given item is equal to the present item object; * otherwise false */ equal(i) { if (this.pos != i.pos) return false; if (this.rule != i.rule) return false; if (this.compareLookAhead(i.lookAheadSet) == false) return false; return true; } /** * Clones the current item object * @returns clone of the current object */ clone() { const c = new LR1_StateItem(); c.pos = this.pos; c.lookAheadSet = new Set(this.lookAheadSet); c.rule = this.rule; return c; } /** * Compares a given lookahead set to the lookahead set of the current object. * @param l lookahead set to compare to * @returns true, if both sets are equal; otherwise false */ compareLookAhead(l) { if (this.lookAheadSet.size != l.size) { return false; } for (const li of l) { if (this.lookAheadSet.has(li) == false) { return false; } } return true; } /** * Stringifies the state. * @returns stringified version of the state */ toString() { let s = '[' + this.rule.toString(this.pos) + ' { '; for (const l of this.lookAheadSet) { s += '"' + l.toString() + '", '; } s = s.substring(0, s.length - 2); s += ' }]'; return s; } } exports.LR1_StateItem = LR1_StateItem; /** * Directed edge (transition) between two states. */ class LR1_Edge { constructor(src, dest) { /** * source state */ this.src = null; /** * destination state */ this.dest = null; /** * label of the transition (type and value of terminal or non-terminal) */ this.label = new lr1rule_1.LR1_RuleItem(); this.src = src; this.dest = dest; } } exports.LR1_Edge = LR1_Edge; //# sourceMappingURL=lr1state.js.map