import GRule, { GElement } from "./GRule"; import GrammarCompilerHelper from "../helper/GrammarCompilerHelper"; import Assert from "@cafetextual/util/dist/src/assert/Assert"; export default class GRuleContextHelper { static calculateRepeatedRuleTokens(rule:GRule, stopOnWS:boolean, getRule:Function = null):Array { var out:Array = new Array(); this.calculateReferenceTokens(rule, out, stopOnWS, true // include reference. , getRule ); return out; } // calcualte RepeatedRuleTokens /** * Encapsuates algorithm for determining reference tokens of a rules as seen from the right. * * where recursion rules ensure that tokens are placed in rules such that that * a) this algorithm will be finite * b) the return values are context independant * * @param includeReference whether to calculate referenced rules. This is used in the context of repeated rules only * * @param getRule - Necessary only when calling this before all child rules are cloned, resolves the element.childRuleName * * Note: this assumes that restrictions on recursion have already been enforced and that this will be called only * * */ static calculateReferenceTokens(rule:GRule, out:Array, stopOnWS:boolean, includeReference:boolean, getRule:Function, depth:number = 0):boolean { depth++; if (depth > 20) { Assert.fail("recursion too complex, or (more likely) compiler has failed to flag an error in recursive structure") } // iterate forward over element for (var i:number = 0; i < rule.elements.length; i++) { var e:GElement = rule.elements[i]; var last:boolean = (i == rule.elements.length -1); var childFailedToComplete:boolean = false; if (e.prefixTokenValue) { out.push(e.prefixTokenValue); } else if (e.contentTokens) { GrammarCompilerHelper.mergeTokens(out, e.contentTokens); } else if (e.childRuleName || e.childRule) { if (e.ruleIsReference || e.childRule == null) { // a recursive rule should always be preceeded buy a non-optional element, // Which should have been enforced earlier (ie this rule not eligible for recursion or injectable by reference) // so we should never get to one in this algorithm // May need to extend this in the case of if (includeReference) { var childRule:GRule = e.childRule; if (e.childRule == null) { if (getRule == null) { if (rule.isValid()) { Assert.assert(rule.isValid() == false); } return false; } childRule = getRule(e.childRuleName) } if (childRule == null) { Assert.assert(childRule != null); } // recurse on reference rule - but with include reference false. // recall that if includeReference is true only when calculating repeaded rules :rule*, // so at the top level context independance is guaranteed b/c this is equivalend to :rule, :rule ... // however when we encounter a reference, context independance (ie independance of what comes after :rule*) // is not guaranteed by our usual recursion conditions. Hence, include reference is false when we recurse.s this.calculateReferenceTokens(childRule, out, stopOnWS, true, getRule, depth); } else if ( !e.hasError(GElement.ERROR_INVALID_RULE_RECURSION) ) { console.log("doesn't have invalid recursion error, but expecting one:" + e.stackContext); //Assert.assert( e.hasError(GElement.ERROR_INVALID_RULE_RECURSION) ); } // Assert.assert( e.hasError(GElement.ERROR_INVALID_RULE_RECURSION) ); } else { childFailedToComplete = !this.calculateReferenceTokens(e.childRule, out, stopOnWS, includeReference, getRule, depth); } } if (!rule.isOR && ( !e.allowNone || (!last && stopOnWS && e.requireWS ) ) ) { // only continue if element is optional return !last && !childFailedToComplete; } } return true } // calculateReferencedTokens_low }