import IGRuleContext from "./IGRuleContext"; import GRule, { GElement } from "./GRule"; import RuleSuffixContextCache from "./RuleSuffixContextCache"; import GRuleContextHelper from "./GRuleContextHelper"; import SeepParserDefs from "../SeepParserDefs"; import Assert from "@cafetextual/util/dist/src/assert/Assert"; import { SContextError } from "@cafetextual/util"; /** * Manages the context during compilation in two ways: * * 1. manages a cache of preciously cloned rules, /w need to clone determined by the token context in which a rule is encountered * 2. enforces eligibility for recursion of child rule in context. * * * Note that the worst case scenario is exponential/w recursion depth. In practice however the caching mechanism should * suffice to make it practical. * * - TO_OPTIMIZE: a further optimization would be to look at the sensitivity of the child rule itself to the context ie * * ... :child-rule ... * * will not actually be sensitive to context if, for instance we have child-rule: PAREN[ anything ] as it's * own token content insulates it. (although note subtlties in whitspace etc). * * */ export default class GRuleContext implements IGRuleContext { constructor(rules:Array, importedRules:Array) { this._importedRules = importedRules ? importedRules : [] this._ruleCache = new RuleSuffixContextCache(); this._rules = rules ? rules : [] this.setupRuleIndices(); var ok:boolean = this.initRulesByName(); } /** * * @param - typically imported rules will already have been imported */ cacheInitial(r:GRule, isInit:boolean):void { this._ruleCache.cacheRule(r, null, null, false); Assert.assert(this.isBase(r)); this._isInit[r.name] = isInit; } /** * The concept of the base rule is the rule from which other instance are cloned in this gramamr. * * For rules defined locally, this would be the same as clonedFrom == null, but for imported rules we * can't assuem this we need to explicitly track our base registered imports. * */ isBase(r:GRule):boolean { return r == this._ruleCache.getCachedChildRule(r.name, null, null, false); } isInitialized(r:GRule):boolean { Assert.assert(this.isBase(r)); return this._isInit[r.name] === true; } markInitIfBase(r:GRule):void { if (this.isBase(r)) { Assert.assert(this._isInit.hasOwnProperty(r.name)); this._isInit[r.name] = true; } } private _ruleCache:RuleSuffixContextCache private _isInit:{[index:string]:boolean} = {}; private _rules:Array; private _ruleIndices:Array = []; private _importedRules:Array; // used only to determine if rule is imported private _rulesByName:{[index:string]:GRule}; private setupRuleIndices():void { if (this._rules) { for (var i:number = 0; i < this._rules.length; i++) { var rule:GRule = this._rules[i]; this._isInit[rule.name] = false; this._ruleIndices[rule.uid] = i; this.setLineRulesIndices(rule, -1); // line rule precednces are -1 } } } // setupruleIndices errors:Array; private initRulesByName():boolean { this._rulesByName = {}; var r:GRule for (r of this._rules) { if (this._rulesByName.hasOwnProperty(r.name)) { console.log(' rules \n'); var i:number = 0; var rl:GRule for (rl of this._rules ) { console.log(' ' + rl.name + '= ' + (++i).toString() + '= ' + rl.tr() ) } console.log(' imported rules'); i = 0; for (rl of this._importedRules) { console.log( ' ' + rl.name + '= ' + (++i).toString() + '= ' + rl.tr() ); } console.log(' old rule: ' + r.name + "= " + this._rulesByName[r.name].tr() ); console.log(' new rule: ' + r.name + "= " + r.tr() ); if (!this.errors) { this.errors = []; } this.errors.push(" duplicate rule name " + r.name); } this._rulesByName[r.name] = r; } return true } getRuleFn():Function { return ( (name:string) => this.getRule(name) ); } getRule(name:string):GRule { return this._rulesByName[name]; } private setLineRulesIndices(rule:GRule, index:number):void { var lineRule:GRule if (rule.lineRules) { for (lineRule of rule.lineRules) { this._ruleIndices[lineRule.uid] = index; // TODO - set to -1 this.setLineRulesIndices(lineRule, index); // recurse } } } // setLineRulesIndices getRuleIndex(r:GRule):number { if (r.clonedFrom && !this.isImported(r) ) { return this.getRuleIndex(r.clonedFrom); } var index:number = this._ruleIndices[r.uid]; // var index:number = r.clonedFrom ? rules.indexOf(r.clonedFrom) : rules.indexOf(r); return isNaN(index) ? -1 : index; } // getRuleIndex isImported(r:GRule):boolean { if ( this._importedRules.indexOf(r) >= 0 ){ // OR just importDef != null Assert.assert(r.importDef != null); return true; } return false } parentRules:Array = new Array() parentElementIndices:Array = new Array(); parentPrecedences:Array = new Array() names:Array = []; // debug only push(rule:GRule, elementIndex:number):void { this.parentRules.push(rule); var currentRuleIndex:number = this.getRuleIndex(rule); // FIX_THIS line rule adds index of -1 (same as anonmyous rule). this.parentPrecedences.push(currentRuleIndex); this.parentElementIndices.push(elementIndex); this.names.push(rule.name ? rule.name : "unnamed"); // by merit of being pushed on the stack, a rule is init, so that if it's encountered in // it's own recursive descent, it won't attempt to re-initialize. // We addtionally only apply to base rules b/c by merit of cloning a rule we deliver it to the // compiler which initialized it. The distinction here is that init refers to named rules of this grammar // (whether imported or local) only, and recursively cloned rules. if (rule.name && this.isBase(rule)) { if (this._isInit.hasOwnProperty(rule.name)) { this._isInit[rule.name] = true; } } } // push pop():void { this.parentRules.pop(); this.parentPrecedences.pop() this.parentElementIndices.pop(); this.names.pop(); } depth():number { return this.names.length; } static isContextIndependant(r:GRule):boolean { return (r.name && this.ruleIsLeftContextIndependant(r) && this.ruleIsRightContextIndependant(r)); } /** * Calculates the relevant stack context * * @param index of the element within it's parent rule * @param targetPrecedence the precedence of the rule being injected * */ toStackContext( currentElementIndex:number, targetPrecedence:number, targetRule:GRule):string { var i:number; var r:GRule; var precedence:number; var eIndex:number; var stackLength:number = this.parentRules.length -1; var currentRule:GRule = this.parentRules[this.parentRules.length-1]; var currentElement:GElement = currentRule.elements[currentElementIndex]; var targetRuleName:string = currentElement.childRuleName; Assert.assert(targetRuleName != null); // 1. if rule is context Independant, just use it if (targetRule.name && GRuleContext.ruleIsLeftContextIndependant(targetRule) && GRuleContext.ruleIsRightContextIndependant(targetRule)) { var tokens1:Array = new Array(); var tokens2:Array = new Array(); // calculating context, no lookup GRuleContextHelper.calculateReferenceTokens(targetRule, tokens1, true, true, this.getRuleFn); GRuleContextHelper.calculateReferenceTokens(targetRule, tokens2, false, true, this.getRuleFn); return targetRule.name + ":"; } // x. recursive case: if this rule is repeated somehwere in the call stack, need to ensure var r_recurse:number = this.getRecursionStackIndex(targetPrecedence); if (r_recurse >= 0 ) { var eligibleForRecursion:boolean = GRuleContext.elementIsEligibleForRecursiveInjection(currentRule, currentElementIndex); if (!eligibleForRecursion) { for (i = stackLength; i >= Math.max(r_recurse,1); i--) { r = this.parentRules[i-1]; eIndex = this.parentElementIndices[i]; precedence = this.parentPrecedences[i]; var rElement:GElement = r.elements[eIndex]; eligibleForRecursion = GRuleContext.elementIsEligibleForRecursiveInjection(r, eIndex); if (eligibleForRecursion) { break; } } if ((r_recurse == 0) && this.parentPrecedences[0] == -1) { eligibleForRecursion = true; } } if (!eligibleForRecursion) { currentElement.addError(GElement.ERROR_INVALID_RULE_RECURSION, "todo - add information on context here"); return null; } } // x. calculate relevant stack indices var r_prefix:number = 0; // element in stack with a determinate prefix var r_suffix:number = 0; // element in stack with a determinate suffix element var r_lower:number = -1; // nearest rule in stack /w a lower precedence // x.1 - nearset prefixed for (i = stackLength+1; i > 0; i--) { r = this.parentRules[i-1]; // i is the index of the element, i-1 is it's parent rule eIndex = i == stackLength+1 ? currentElementIndex : this.parentElementIndices[i] if (GRuleContext.hasPrefix(r,eIndex)) { r_prefix = i - 1; r_prefix = this.toNearestNamedRuleStackIndex(r_prefix); break; } } // x.2 nearset suffixed for (i = stackLength+1; i > 0; i--) { r = this.parentRules[i-1] eIndex = i == stackLength+1 ? currentElementIndex : this.parentElementIndices[i] if (GRuleContext.hasSuffix(r,eIndex)) { r_suffix = i - 1; r_suffix = this.toNearestNamedRuleStackIndex(r_suffix) break; } } Assert.assert(r_suffix == 0 || this.parentRules[r_suffix].name != null); Assert.assert(r_prefix == 0 || this.parentRules[r_prefix].name != null); // x.3 nearext lowest precedence for (i = stackLength; i >= 0; i--) { precedence = this.parentPrecedences[i]; if (targetPrecedence > precedence) { r_lower = i break; } } // nearest lower precedence isn't //Assert.assert( var r_contextLimit:number = 0; // x. enforce if (r_recurse >= 0 ) { r_contextLimit = r_recurse } if ( (r_prefix > r_contextLimit) && (r_suffix > r_contextLimit) ) { r_contextLimit = Math.min(r_prefix, r_suffix); } // x. determine last name var out:string = targetRuleName + ":"; var ruleIndexLabels:string = this.toIndexLabel(currentElement, currentElementIndex, null); if (currentRule.name) { out += this.toRuleLabel(currentRule, ruleIndexLabels); ruleIndexLabels = ""; } // NOTE - there is a further optimisation here, if the target rule is completely self contained /// ie rule0: X .. any other element .. (varN) // or rule1: PAREN:chidlRule //var targetIsContextIndependant:boolean = ruleIsContextIndependant(targetRule); // var firstPrefix:number = getFirstPrefix( // x. second pass, form context label i = stackLength; while (i > r_contextLimit) { var childRule:GRule = this.parentRules[i]; var rule:GRule = this.parentRules[i-1]; var elementIndex:number = this.parentElementIndices[i]; var element:GElement = rule.elements[elementIndex]; if (childRule != element.childRule) { // Assert.assert(childRule == element.childRule); } while (i >= r_contextLimit && rule.name == null) { ruleIndexLabels = this.toIndexLabel(element, elementIndex, ruleIndexLabels); i--; if (i > r_contextLimit) { rule = this.parentRules[i -1] childRule = this.parentRules[i]; elementIndex = this.parentElementIndices[i]; element = rule.elements[elementIndex]; } else if (i==r_contextLimit) { out += " --> [" + ruleIndexLabels + "]"; //Assert.fail(); // line rule will probably cause this } } /// iteraation up through unnamed rules if (i > r_contextLimit ) { // at this point we've iterated up throught the unnamed rules and can add the named rule Assert.assert(rule.name != null); ruleIndexLabels = this.toIndexLabel(element, elementIndex, ruleIndexLabels) out += this.toRuleLabel(rule, ruleIndexLabels); ruleIndexLabels = ""; i--; // if we've hit the target rule, they this gives us if (rule.name == targetRuleName) { return out; } } } // iteration up through named rules return out; } // to StackContext /** * If an index refers to an unnamed rule (precedence = -1), this bumps it to the stack index of a named rule. * * (Excption is an unnamed line rule, which has -1 index, but stack index always is 0). */ toNearestNamedRuleStackIndex(index:number):number { for (var i:number = index; i >= 0; i--) { if (this.parentPrecedences[i] >= 0) { return i; } } return 0; // an unnamed line rule will might arrive here } // toNearestNAmedRuleStackIndex /** * Returns the index in the rule stack in which the target rule is repeated (and therefore recursive). */ private getRecursionStackIndex(targetPrecedence:number):number { Assert.assert(targetPrecedence >= 0) for (var i:number = this.parentPrecedences.length -1; i>=0; i--) { var precedence:number = this.parentPrecedences[i]; if (targetPrecedence == precedence) { return i; } } // not found return -1; } // getRecursionStackIndex /** * Determines whether a childRule is - in the context of a parent rule - safe to recurse on * * Requires (as this is an LL-style of algorithm): * - a prefix token, or a previous non-optional element * - must also be optional, or have a calling parent as optional * * * Note: * - does not in anyway look at the actual child rule, only the context of the element * - requires *strictly* that a rules never return null results. * * @param rule * @e * @parentIsOptional */ static elementIsEligibleForRecursiveInjection(rule:GRule, elementIndex:number, parentIsOptional:boolean = false):boolean { var e:GElement = rule.elements[elementIndex]; // we could check that we've got a possible grammar // ie rule0: X :rule0 // will never match a (finite) input. But it won't cause the parser to go into an infinite loop, like something like this would: // rule: :rule X //if (! (parentIsOptional || e.allowNone || rule.isOR)) { // return false; //} // 1. simple prefix token is adequate to ensure // ie rule0: X:rule0 (techncally this example will never match, but tokens will converge and parser won't loop infinitely ==> adequate) if (e.prefixTokenValue != null) { return true; } Assert.assert(e.contentTokens == null); // content tokens not relevant here // 2. OR rule only an explicit token will guarantee convergence // rule0: X | :rule0 --> infinite loop if :rule0 lacks a prefix // rule0: X [ Y | :rule0] --> will be ok, since parent X will // also // rule0: PAREN:rule1 // rule1: X | :rule0 --> algorithm will already have noted that rule0 is context independant, so this methcd wo't be called if (rule.isOR && e.prefixTokenValue == null) { return false; } // this would be non-sensical (not strictly invalid, but can never match with finite input). // rule: X:rule? // Q: what about the class of equivalent grammars like rule: X[:rule] etc? // acutally, let's not bother to test for this here. It won't create an infinite loop, it will just never match. //if (rule == childRule && rule.elements.length == 1) { // return false; //} // iterate from current element to first element of this rul for (var i:number = elementIndex- 1; i >= 0 ; i--) { var currentElement:GElement = rule.elements[i]; // i. ignore non-matching elements ie ^(vars)* if (SeepParserDefs.elementMayHaveNullMatch(currentElement)) { continue; } // an prefix-less named child rule - neither breaks nor guarantees eligibity //if (e.childRuleName && e.prefixTokenValue == null) { // continue; //} if (!currentElement.allowNone ) { // all we require is that a previous element must match something (otherwise we get an infinite loop) return true; } } return false; } private toIndexLabel(element:GElement, elementIndex:number, prev:string):string { return elementIndex.toString() + (element.tokenName ? ( "(" + element.tokenName + ")" ) : "") + (prev ? "," + prev : ""); } private toRuleLabel(rule:GRule, indexLabels:string):string { return " --> :" + rule.name + '[' + indexLabels + ']' } /** * Return has prefix. In the first instance, we're only going to return very simple instances * * @private - public for testing only */ static hasPrefix(rule:GRule, eIndex:number):boolean { var e:GElement = rule.elements[eIndex]; if (e.prefixTokenValue != null) { return true; } if (!rule.isOR) { for (var i:number = eIndex -1; i >=0; i--) { e = rule.elements[i]; if (!e.allowNone && !SeepParserDefs.elementMayHaveNullMatch(e) ) { // sufficient to have a required vlaue. Also, check for null match elements ie ^(vars)* return true } } } return false; } // getRulePrefix /** * Calculate suffix * @private - public for testing only */ static hasSuffix(rule:GRule, eIndex:number):boolean { var e:GElement = rule.elements[eIndex]; if (e.suffixTokenValue != null) { return true; } if (!rule.isOR) { // if a rule is an OR, only look at the selected element ie X | :rule | Y <--- X and Y don't matter for (var i:number = eIndex+1; i < rule.elements.length; i++) { e = rule.elements[i]; if (!e.allowNone && !SeepParserDefs.elementMayHaveNullMatch(e)) { // sufficient to have a required vlaue. Also, check for null match elements ie ^(vars)* return true; } } } return false; } /** * True if rule is not sensitive to context. ie: * rule0: PAREN(vars) * * @private public for testing only */ static ruleIsRightContextIndependant(r:GRule):boolean { var e:GElement; var breaks:boolean; var i:number; // 1. ORed rule - if all individual rules are context independant, then rule is if (r.isOR) { for (i = r.elements.length -1; i >=0; i--) { e = r.elements[i]; Assert.assert ( !SeepParserDefs.elementMayHaveNullMatch(e) ); // catch this is a validation somewhere breaks = GRuleContext.elementBreaksRightContextIndependance(e); if (breaks) { return false; } } return true; } // non- ORed for (i = r.elements.length -1; i >=0; i--) { e = r.elements[i]; if ( SeepParserDefs.elementMayHaveNullMatch(e) ) { continue; } breaks = GRuleContext.elementBreaksRightContextIndependance(e); if (breaks) { return false; } else if (!e.allowNone) { return true; // required element guarantes } } // iteration over elements Assert.fail(); // shouldn't get here -> have assumed each rule to have at least one non-optional (non null match) element // (and we're depending on this, so lets be sure to enforce it) return false; } // ruleIsContextIndependant /** * True is an elment breaks (although does not necessarily guarantee) */ private static elementBreaksRightContextIndependance(e:GElement):boolean { Assert.assert ( !SeepParserDefs.elementMayHaveNullMatch(e) ); // don't call with such an element // 1. suffix token ok if (e.suffixTokenValue) { // suffix does not break R-independance return false; } // 2. unnamed child rule ==> recurse if (e.childRule && e.childRuleName == null) { return !GRuleContext.ruleIsRightContextIndependant(e.childRule); } // 3. named child rule breaks // TO_OPTIMIZE - could recurse on childRule of higher precedence easily enough if (e.childRuleName) { return true; } // 4. token only or content tokens ok ie X or MY_CONTENT_TOKEN(exists) if (e.contentTokens || (e.prefixTokenValue && e.varName == null) ) { return false; } // which leaves non-suffix or non-content betoken non-childruled named vars ie X(var1) ==> breaks return true; } /** * Left context independant means that regardless of the context, the rule will propagate an identical set of tokens * */ static ruleIsLeftContextIndependant(r:GRule):boolean { var e:GElement var i:number; var breaks:boolean; if (r.isOR) { for (i = 0; i < r.elements.length; i++) { e = r.elements[i]; Assert.assert( !SeepParserDefs.elementMayHaveNullMatch(e) ); breaks = GRuleContext.elementBreaksLeftContextIndependance(e); if (breaks) { return false; } } return true; } for (i = 0; i < r.elements.length; i++) { e = r.elements[i]; // 1. ignore non-matching elements, ie ^(vars)* if ( SeepParserDefs.elementMayHaveNullMatch(e) ) { continue; } breaks = GRuleContext.elementBreaksLeftContextIndependance(e); if (breaks) { return false } else if (!e.allowNone) { return true; } } // iteration over rule.elements Assert.fail("bug - probably grammar doesn't have a required element " + r.tr()); // should never get here return false; } // ruleIsLeftContextIndependant private static elementBreaksLeftContextIndependance(e:GElement):boolean { Assert.assert(!SeepParserDefs.elementMayHaveNullMatch(e) ); // 1. prefix/ content token means --> ok if (e.prefixTokenValue || e.contentTokens) { return false; } // 2. unnamed rule --> recurse if (e.childRule && !e.childRuleName) { return !GRuleContext.ruleIsLeftContextIndependant(e.childRule); } // 3. name rule --> breaks if (e.childRuleName) { return true; } // nothing else to break return false; } // elementBreaksLeftContextIndependance clear():void { this.parentRules = new Array(); this.parentElementIndices = new Array() this.parentPrecedences = new Array() // TODO - clear other bits .. (although clear is used for debug, no } private _ruleInjectionsByStackcontext:{[index:string]:Array} = {} private injectRuleByStackContext(e:GElement, stackContext:string, targetRule:GRule, validateOnly:boolean):boolean { if (this.hasStackContext(stackContext) ) { e.childRule = this.getStackContext(stackContext); if (e.childRule == null) { return false; } if (!validateOnly) { e.ruleIsReference = true; } } else { if (!validateOnly) { e.childRule = targetRule.clone(); this.setStackContext(stackContext, e.childRule); e.ruleIsReference = false; } } var injectionInstances:Array; if (this._ruleInjectionsByStackcontext.hasOwnProperty(stackContext) ){ injectionInstances = this._ruleInjectionsByStackcontext[stackContext] } else { injectionInstances = []; this._ruleInjectionsByStackcontext[stackContext] = injectionInstances; } Assert.assert(injectionInstances.indexOf(e) < 0); injectionInstances.push(e); return true; } _stackContext:{[index:string]:GRule} = {} private hasStackContext(name:string):boolean { return this._stackContext.hasOwnProperty(name); } private getStackContext(name:string):GRule { Assert.assert(this.hasStackContext(name)); return this._stackContext[name] } private setStackContext(name:string, rule:GRule):void { Assert.assert(rule.clonedFrom != null); Assert.assert(!this.hasStackContext(name)); this._stackContext[name] = rule } // setStackContext /** * @private for test **/ getInjectionCount(contextName:string ="*"):number { var items:Array = null; if (contextName == "*") { var count:number = 0; var key:string for (key in this._ruleInjectionsByStackcontext) { var items2:Array = this._ruleInjectionsByStackcontext[key] count += items2.length; } return count; } items = this._ruleInjectionsByStackcontext[contextName]; return items ? items.length : 0; } tr():string { var out:string = "---- stack contexts -----\n" var key:string for (key in this._ruleInjectionsByStackcontext) { var items:Array = this._ruleInjectionsByStackcontext[key]; out += key + " # of instances = " + items.length.toString() + "\n" } console.log(out); return out; } // tractStackContext private static immediateParentIndexIsLower(indices:Array, currentIndex:number):boolean { var parentIndex:number = this.getImmediateParentIndex(indices); return ((parentIndex >= 0) && (parentIndex < currentIndex) ); } // immediateParentIndexIsLower private static getImmediateParentIndex(indices:Array):number { for (var i:number = indices.length -1; i >= 0; i-- ) { if (indices[i] >= 0) { return indices[i]; } } return -1; } private static getNumberOfParentsOfHigherIndex(indices:Array, index:number):number { var count:number = 0; for (var i:number = 0; i < indices.length; i++ ){ if ( (indices[i] >= 0) && (indices[i] > index) ) { count++; } } return count; } // getNumberOfParentsOfEqualIndex private static getNumberOfParentsOfEqualIndex(indices:Array, index:number):number { var count:number = 0; for (var i:number = 0; i < indices.length; i++ ){ if (indices[i] == index) { count++ } } return count; } // getNumberOfParentsOfEqualIndex private getLastParentRule(rules:Array, rule:GRule):GRule { for (var i:number = rules.length-1; i >= 0; i-- ) { if (rules[i] == rule || rules[i].clonedFrom == rule) { return rules[i]; } } return null; } // getLastClonedParentRule // ----- static MAX_RULE_DEPTH:number = 100; doInject(rule:GRule, e:GElement, elementIndex:number, parentIsOptional:boolean, elementOptional:boolean, elementTokens:Array, suffixTokens:Array):boolean { var targetRuleIndex:number var parentRuleIndex:number; var validateOnly:boolean = false; var cRule:GRule = this.getRule(e.childRuleName); if (cRule == null) { e.addError(GElement.ERROR_UNKNOWN_CHILD_RULE, "cannot resolve child rule " + e.childRuleName); return false; } var contextIndependent:boolean = GRuleContext.isContextIndependant(cRule); if (contextIndependent) { var cached:GRule = this._ruleCache.getCachedChildRule(e.childRuleName, null, null, false); } else { cached = this._ruleCache.getCachedChildRule(e.childRuleName, elementTokens, suffixTokens, e.skipToToken); } //consol.log("=> rule: " + e.childRuleName + " tokens:(" + _ruleCache.toTokenString(elementTokens) + ") suffix: (" + _ruleCache.toTokenString(suffixTokens) + ")"); if (cached) { e.ruleIsReference = true; e.childRule = cached; injectedRule = cached; var requireInit:boolean = this.requiresInit(e); //consol.log(" ==> retrieved from cache" + (requireInit ? " (for init)" : "") + (contextIndependent ? " (context independent) " : "" )); //return true; } else { e.ruleIsReference = false; var referenceRule:GRule = this._ruleCache.getCachedChildRule(e.childRuleName, null, null, false); if (!referenceRule) { Assert.fail(' XX ==> unable to clone'); } else { var injectedRule:GRule = referenceRule.clone( ); e.childRule = injectedRule; this._ruleCache.cacheRule(injectedRule, elementTokens, suffixTokens, e.skipToToken); //consol.log(" ==> cloning"); } //return true; } // validateOnly = true; // if caching by token is enabled, then this code is redundan - provided our convergence // criterion are met. So we'll keep this around until it's error checking can be integrated elsewhere. // and it very explicitly documents the convergence criterion var currentRuleIndex:number = this.getRuleIndex(rule); // decide whether to clone or add by reference if (this.parentRules.length > GRuleContext.MAX_RULE_DEPTH ) { var msg:string = "rules depth = " + this.parentRules.length.toString() + " , max depth = " + GRuleContext.MAX_RULE_DEPTH.toString(); // consol.log(msg) e.addError(GElement.ERROR_MAX_RULE_DEPTH_EXCEEDED, msg ); return false; } // 1. if we have a named rule references ie. (var):ruleName var targetRule:GRule = this.getRule(e.childRuleName); if (!targetRule) { // 2. ensure that the rule exists if (e.childRuleName.indexOf('\t') >= 0) { throw new SContextError('rulename has absorbed a tab "' + e.childRuleName + '"(pending parser fix)"') } e.addError(GElement.ERROR_UNKNOWN_CHILD_RULE); return false; } // 3. calcuate the indices, and specifically whether there is a parent rule // that has a higher index -- which we define as a targetRuleIndex = this.getRuleIndex(targetRule); if (targetRuleIndex < 0) { this.getRuleIndex(targetRule); Assert.assert(targetRuleIndex >= 0); // only anonmyous rules will return -1 } // 4. the simple case, of the parent rules having lower indices, and therefore no possibilty of recursion // simply clone // ---> TODO: generalize this 'safe to clone' to do we have an instance of this rule for the given context var safeToClone:boolean = GRuleContext.immediateParentIndexIsLower(this.parentPrecedences, targetRuleIndex); if (safeToClone) { // REFACTORING_IT_PROGRESS - to be merged var stackContext:string = this.toStackContext(elementIndex, targetRuleIndex, targetRule); if (stackContext == null) { console.log( ' bug' ); } e.stackContext = stackContext; this.injectRuleByStackContext(e, stackContext, targetRule, validateOnly); //e.childRule = targetRule.clone(false); } else { // 5. iterate up rule chain and pull out lists of rules with equal and higher indies if (targetRuleIndex < 0) { Assert.assert(currentRuleIndex >= 0); } // 5a. count the number of instances in the call chaing that are from var parentsOfEqualIndex:number = GRuleContext.getNumberOfParentsOfEqualIndex(this.parentPrecedences, targetRuleIndex ); var parentsOfHigherIndex:number = GRuleContext.getNumberOfParentsOfHigherIndex(this.parentPrecedences, targetRuleIndex); stackContext = this.toStackContext(elementIndex, targetRuleIndex, targetRule); if (stackContext != null) { e.stackContext = stackContext; return this.injectRuleByStackContext(e, stackContext, targetRule, validateOnly); } else { Assert.assert(e.hasError()); // returning a null context implies } } // has child rule if (injectedRule) { return injectedRule.isValid(); } return true } requiresInit(e:GElement):boolean { return e.ruleIsReference && this.isBase(e.childRule) && !this.isInitialized(e.childRule) } show():string { return this._ruleCache.show(); } } // class