All Downloads are FREE. Search and download functionalities are using the official Maven repository.

JavaScript.src.antlr4.PredictionContext.js Maven / Gradle / Ivy

There is a newer version: 4.13.2
Show newest version
//
// [The "BSD license"]
//  Copyright (c) 2012 Terence Parr
//  Copyright (c) 2012 Sam Harwell
//  Copyright (c) 2014 Eric Vergnaud
//  All rights reserved.
//
//  Redistribution and use in source and binary forms, with or without
//  modification, are permitted provided that the following conditions
//  are met:
//
//  1. Redistributions of source code must retain the above copyright
//     notice, this list of conditions and the following disclaimer.
//  2. Redistributions in binary form must reproduce the above copyright
//     notice, this list of conditions and the following disclaimer in the
//     documentation and/or other materials provided with the distribution.
//  3. The name of the author may not be used to endorse or promote products
//     derived from this software without specific prior written permission.
//
//  THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
//  IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
//  OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
//  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
//  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
//  NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
//  DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
//  THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
//  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
//  THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
///

var RuleContext = require('./RuleContext').RuleContext;

function PredictionContext(cachedHashString) {
	this.cachedHashString = cachedHashString;
}

// Represents {@code $} in local context prediction, which means wildcard.
// {@code//+x =//}.
// /
PredictionContext.EMPTY = null;

// Represents {@code $} in an array in full context mode, when {@code $}
// doesn't mean wildcard: {@code $ + x = [$,x]}. Here,
// {@code $} = {@link //EMPTY_RETURN_STATE}.
// /
PredictionContext.EMPTY_RETURN_STATE = 0x7FFFFFFF;

PredictionContext.globalNodeCount = 1;
PredictionContext.id = PredictionContext.globalNodeCount;

// Stores the computed hash code of this {@link PredictionContext}. The hash
// code is computed in parts to match the following reference algorithm.
//
// 
// private int referenceHashCode() {
// int hash = {@link MurmurHash//initialize MurmurHash.initialize}({@link
// //INITIAL_HASH});
//
// for (int i = 0; i < {@link //size()}; i++) {
// hash = {@link MurmurHash//update MurmurHash.update}(hash, {@link //getParent
// getParent}(i));
// }
//
// for (int i = 0; i < {@link //size()}; i++) {
// hash = {@link MurmurHash//update MurmurHash.update}(hash, {@link
// //getReturnState getReturnState}(i));
// }
//
// hash = {@link MurmurHash//finish MurmurHash.finish}(hash, 2// {@link
// //size()});
// return hash;
// }
// 
// / // This means only the {@link //EMPTY} context is in set. PredictionContext.prototype.isEmpty = function() { return this === PredictionContext.EMPTY; }; PredictionContext.prototype.hasEmptyPath = function() { return this.getReturnState(this.length - 1) === PredictionContext.EMPTY_RETURN_STATE; }; PredictionContext.prototype.hashString = function() { return this.cachedHashString; }; function calculateHashString(parent, returnState) { return "" + parent + returnState; } function calculateEmptyHashString() { return ""; } // Used to cache {@link PredictionContext} objects. Its used for the shared // context cash associated with contexts in DFA states. This cache // can be used for both lexers and parsers. function PredictionContextCache() { this.cache = {}; return this; } // Add a context to the cache and return it. If the context already exists, // return that one instead and do not add a new context to the cache. // Protect shared cache from unsafe thread access. // PredictionContextCache.prototype.add = function(ctx) { if (ctx === PredictionContext.EMPTY) { return PredictionContext.EMPTY; } var existing = this.cache[ctx] || null; if (existing !== null) { return existing; } this.cache[ctx] = ctx; return ctx; }; PredictionContextCache.prototype.get = function(ctx) { return this.cache[ctx] || null; }; Object.defineProperty(PredictionContextCache.prototype, "length", { get : function() { return this.cache.length; } }); function SingletonPredictionContext(parent, returnState) { var hashString = parent !== null ? calculateHashString(parent, returnState) : calculateEmptyHashString(); PredictionContext.call(this, hashString); this.parentCtx = parent; this.returnState = returnState; } SingletonPredictionContext.prototype = Object.create(PredictionContext.prototype); SingletonPredictionContext.prototype.contructor = SingletonPredictionContext; SingletonPredictionContext.create = function(parent, returnState) { if (returnState === PredictionContext.EMPTY_RETURN_STATE && parent === null) { // someone can pass in the bits of an array ctx that mean $ return PredictionContext.EMPTY; } else { return new SingletonPredictionContext(parent, returnState); } }; Object.defineProperty(SingletonPredictionContext.prototype, "length", { get : function() { return 1; } }); SingletonPredictionContext.prototype.getParent = function(index) { return this.parentCtx; }; SingletonPredictionContext.prototype.getReturnState = function(index) { return this.returnState; }; SingletonPredictionContext.prototype.equals = function(other) { if (this === other) { return true; } else if (!(other instanceof SingletonPredictionContext)) { return false; } else if (this.hashString() !== other.hashString()) { return false; // can't be same if hash is different } else { if(this.returnState !== other.returnState) return false; else if(this.parentCtx==null) return other.parentCtx==null else return this.parentCtx.equals(other.parentCtx); } }; SingletonPredictionContext.prototype.hashString = function() { return this.cachedHashString; }; SingletonPredictionContext.prototype.toString = function() { var up = this.parentCtx === null ? "" : this.parentCtx.toString(); if (up.length === 0) { if (this.returnState === this.EMPTY_RETURN_STATE) { return "$"; } else { return "" + this.returnState; } } else { return "" + this.returnState + " " + up; } }; function EmptyPredictionContext() { SingletonPredictionContext.call(this, null, PredictionContext.EMPTY_RETURN_STATE); return this; } EmptyPredictionContext.prototype = Object.create(SingletonPredictionContext.prototype); EmptyPredictionContext.prototype.constructor = EmptyPredictionContext; EmptyPredictionContext.prototype.isEmpty = function() { return true; }; EmptyPredictionContext.prototype.getParent = function(index) { return null; }; EmptyPredictionContext.prototype.getReturnState = function(index) { return this.returnState; }; EmptyPredictionContext.prototype.equals = function(other) { return this === other; }; EmptyPredictionContext.prototype.toString = function() { return "$"; }; PredictionContext.EMPTY = new EmptyPredictionContext(); function ArrayPredictionContext(parents, returnStates) { // Parent can be null only if full ctx mode and we make an array // from {@link //EMPTY} and non-empty. We merge {@link //EMPTY} by using // null parent and // returnState == {@link //EMPTY_RETURN_STATE}. var hash = calculateHashString(parents, returnStates); PredictionContext.call(this, hash); this.parents = parents; this.returnStates = returnStates; return this; } ArrayPredictionContext.prototype = Object.create(PredictionContext.prototype); ArrayPredictionContext.prototype.constructor = ArrayPredictionContext; ArrayPredictionContext.prototype.isEmpty = function() { // since EMPTY_RETURN_STATE can only appear in the last position, we // don't need to verify that size==1 return this.returnStates[0] === PredictionContext.EMPTY_RETURN_STATE; }; Object.defineProperty(ArrayPredictionContext.prototype, "length", { get : function() { return this.returnStates.length; } }); ArrayPredictionContext.prototype.getParent = function(index) { return this.parents[index]; }; ArrayPredictionContext.prototype.getReturnState = function(index) { return this.returnStates[index]; }; ArrayPredictionContext.prototype.equals = function(other) { if (this === other) { return true; } else if (!(other instanceof ArrayPredictionContext)) { return false; } else if (this.hashString !== other.hashString()) { return false; // can't be same if hash is different } else { return this.returnStates === other.returnStates && this.parents === other.parents; } }; ArrayPredictionContext.prototype.toString = function() { if (this.isEmpty()) { return "[]"; } else { var s = "["; for (var i = 0; i < this.returnStates.length; i++) { if (i > 0) { s = s + ", "; } if (this.returnStates[i] === PredictionContext.EMPTY_RETURN_STATE) { s = s + "$"; continue; } s = s + this.returnStates[i]; if (this.parents[i] !== null) { s = s + " " + this.parents[i]; } else { s = s + "null"; } } return s + "]"; } }; // Convert a {@link RuleContext} tree to a {@link PredictionContext} graph. // Return {@link //EMPTY} if {@code outerContext} is empty or null. // / function predictionContextFromRuleContext(atn, outerContext) { if (outerContext === undefined || outerContext === null) { outerContext = RuleContext.EMPTY; } // if we are in RuleContext of start rule, s, then PredictionContext // is EMPTY. Nobody called us. (if we are empty, return empty) if (outerContext.parentCtx === null || outerContext === RuleContext.EMPTY) { return PredictionContext.EMPTY; } // If we have a parent, convert it to a PredictionContext graph var parent = predictionContextFromRuleContext(atn, outerContext.parentCtx); var state = atn.states[outerContext.invokingState]; var transition = state.transitions[0]; return SingletonPredictionContext.create(parent, transition.followState.stateNumber); } function calculateListsHashString(parents, returnStates) { var s = ""; parents.map(function(p) { s = s + p; }); returnStates.map(function(r) { s = s + r; }); return s; } function merge(a, b, rootIsWildcard, mergeCache) { // share same graph if both same if (a === b) { return a; } if (a instanceof SingletonPredictionContext && b instanceof SingletonPredictionContext) { return mergeSingletons(a, b, rootIsWildcard, mergeCache); } // At least one of a or b is array // If one is $ and rootIsWildcard, return $ as// wildcard if (rootIsWildcard) { if (a instanceof EmptyPredictionContext) { return a; } if (b instanceof EmptyPredictionContext) { return b; } } // convert singleton so both are arrays to normalize if (a instanceof SingletonPredictionContext) { a = new ArrayPredictionContext([a.getParent()], [a.returnState]); } if (b instanceof SingletonPredictionContext) { b = new ArrayPredictionContext([b.getParent()], [b.returnState]); } return mergeArrays(a, b, rootIsWildcard, mergeCache); } // // Merge two {@link SingletonPredictionContext} instances. // //

Stack tops equal, parents merge is same; return left graph.
//

// //

Same stack top, parents differ; merge parents giving array node, then // remainders of those graphs. A new root node is created to point to the // merged parents.
//

// //

Different stack tops pointing to same parent. Make array node for the // root where both element in the root point to the same (original) // parent.
//

// //

Different stack tops pointing to different parents. Make array node for // the root where each element points to the corresponding original // parent.
//

// // @param a the first {@link SingletonPredictionContext} // @param b the second {@link SingletonPredictionContext} // @param rootIsWildcard {@code true} if this is a local-context merge, // otherwise false to indicate a full-context merge // @param mergeCache // / function mergeSingletons(a, b, rootIsWildcard, mergeCache) { if (mergeCache !== null) { var previous = mergeCache.get(a, b); if (previous !== null) { return previous; } previous = mergeCache.get(b, a); if (previous !== null) { return previous; } } var rootMerge = mergeRoot(a, b, rootIsWildcard); if (rootMerge !== null) { if (mergeCache !== null) { mergeCache.set(a, b, rootMerge); } return rootMerge; } if (a.returnState === b.returnState) { var parent = merge(a.parentCtx, b.parentCtx, rootIsWildcard, mergeCache); // if parent is same as existing a or b parent or reduced to a parent, // return it if (parent === a.parentCtx) { return a; // ax + bx = ax, if a=b } if (parent === b.parentCtx) { return b; // ax + bx = bx, if a=b } // else: ax + ay = a'[x,y] // merge parents x and y, giving array node with x,y then remainders // of those graphs. dup a, a' points at merged array // new joined parent so create new singleton pointing to it, a' var spc = SingletonPredictionContext.create(parent, a.returnState); if (mergeCache !== null) { mergeCache.set(a, b, spc); } return spc; } else { // a != b payloads differ // see if we can collapse parents due to $+x parents if local ctx var singleParent = null; if (a === b || (a.parentCtx !== null && a.parentCtx === b.parentCtx)) { // ax + // bx = // [a,b]x singleParent = a.parentCtx; } if (singleParent !== null) { // parents are same // sort payloads and use same parent var payloads = [ a.returnState, b.returnState ]; if (a.returnState > b.returnState) { payloads[0] = b.returnState; payloads[1] = a.returnState; } var parents = [ singleParent, singleParent ]; var apc = new ArrayPredictionContext(parents, payloads); if (mergeCache !== null) { mergeCache.set(a, b, apc); } return apc; } // parents differ and can't merge them. Just pack together // into array; can't merge. // ax + by = [ax,by] var payloads = [ a.returnState, b.returnState ]; var parents = [ a.parentCtx, b.parentCtx ]; if (a.returnState > b.returnState) { // sort by payload payloads[0] = b.returnState; payloads[1] = a.returnState; parents = [ b.parentCtx, a.parentCtx ]; } var a_ = new ArrayPredictionContext(parents, payloads); if (mergeCache !== null) { mergeCache.set(a, b, a_); } return a_; } } // // Handle case where at least one of {@code a} or {@code b} is // {@link //EMPTY}. In the following diagrams, the symbol {@code $} is used // to represent {@link //EMPTY}. // //

Local-Context Merges

// //

These local-context merge operations are used when {@code rootIsWildcard} // is true.

// //

{@link //EMPTY} is superset of any graph; return {@link //EMPTY}.
//

// //

{@link //EMPTY} and anything is {@code //EMPTY}, so merged parent is // {@code //EMPTY}; return left graph.
//

// //

Special case of last merge if local context.
//

// //

Full-Context Merges

// //

These full-context merge operations are used when {@code rootIsWildcard} // is false.

// //

// //

Must keep all contexts; {@link //EMPTY} in array is a special value (and // null parent).
//

// //

// // @param a the first {@link SingletonPredictionContext} // @param b the second {@link SingletonPredictionContext} // @param rootIsWildcard {@code true} if this is a local-context merge, // otherwise false to indicate a full-context merge // / function mergeRoot(a, b, rootIsWildcard) { if (rootIsWildcard) { if (a === PredictionContext.EMPTY) { return PredictionContext.EMPTY; // // + b =// } if (b === PredictionContext.EMPTY) { return PredictionContext.EMPTY; // a +// =// } } else { if (a === PredictionContext.EMPTY && b === PredictionContext.EMPTY) { return PredictionContext.EMPTY; // $ + $ = $ } else if (a === PredictionContext.EMPTY) { // $ + x = [$,x] var payloads = [ b.returnState, PredictionContext.EMPTY_RETURN_STATE ]; var parents = [ b.parentCtx, null ]; return new ArrayPredictionContext(parents, payloads); } else if (b === PredictionContext.EMPTY) { // x + $ = [$,x] ($ is always first if present) var payloads = [ a.returnState, PredictionContext.EMPTY_RETURN_STATE ]; var parents = [ a.parentCtx, null ]; return new ArrayPredictionContext(parents, payloads); } } return null; } // // Merge two {@link ArrayPredictionContext} instances. // //

Different tops, different parents.
//

// //

Shared top, same parents.
//

// //

Shared top, different parents.
//

// //

Shared top, all shared parents.
//

// //

Equal tops, merge parents and reduce top to // {@link SingletonPredictionContext}.
//

// / function mergeArrays(a, b, rootIsWildcard, mergeCache) { if (mergeCache !== null) { var previous = mergeCache.get(a, b); if (previous !== null) { return previous; } previous = mergeCache.get(b, a); if (previous !== null) { return previous; } } // merge sorted payloads a + b => M var i = 0; // walks a var j = 0; // walks b var k = 0; // walks target M array var mergedReturnStates = []; var mergedParents = []; // walk and merge to yield mergedParents, mergedReturnStates while (i < a.returnStates.length && j < b.returnStates.length) { var a_parent = a.parents[i]; var b_parent = b.parents[j]; if (a.returnStates[i] === b.returnStates[j]) { // same payload (stack tops are equal), must yield merged singleton var payload = a.returnStates[i]; // $+$ = $ var bothDollars = payload === PredictionContext.EMPTY_RETURN_STATE && a_parent === null && b_parent === null; var ax_ax = (a_parent !== null && b_parent !== null && a_parent === b_parent); // ax+ax // -> // ax if (bothDollars || ax_ax) { mergedParents[k] = a_parent; // choose left mergedReturnStates[k] = payload; } else { // ax+ay -> a'[x,y] var mergedParent = merge(a_parent, b_parent, rootIsWildcard, mergeCache); mergedParents[k] = mergedParent; mergedReturnStates[k] = payload; } i += 1; // hop over left one as usual j += 1; // but also skip one in right side since we merge } else if (a.returnStates[i] < b.returnStates[j]) { // copy a[i] to M mergedParents[k] = a_parent; mergedReturnStates[k] = a.returnStates[i]; i += 1; } else { // b > a, copy b[j] to M mergedParents[k] = b_parent; mergedReturnStates[k] = b.returnStates[j]; j += 1; } k += 1; } // copy over any payloads remaining in either array if (i < a.returnStates.length) { for (var p = i; p < a.returnStates.length; p++) { mergedParents[k] = a.parents[p]; mergedReturnStates[k] = a.returnStates[p]; k += 1; } } else { for (var p = j; p < b.returnStates.length; p++) { mergedParents[k] = b.parents[p]; mergedReturnStates[k] = b.returnStates[p]; k += 1; } } // trim merged if we combined a few that had same stack tops if (k < mergedParents.length) { // write index < last position; trim if (k === 1) { // for just one merged element, return singleton top var a_ = SingletonPredictionContext.create(mergedParents[0], mergedReturnStates[0]); if (mergeCache !== null) { mergeCache.set(a, b, a_); } return a_; } mergedParents = mergedParents.slice(0, k); mergedReturnStates = mergedReturnStates.slice(0, k); } var M = new ArrayPredictionContext(mergedParents, mergedReturnStates); // if we created same array as a or b, return that instead // TODO: track whether this is possible above during merge sort for speed if (M === a) { if (mergeCache !== null) { mergeCache.set(a, b, a); } return a; } if (M === b) { if (mergeCache !== null) { mergeCache.set(a, b, b); } return b; } combineCommonParents(mergedParents); if (mergeCache !== null) { mergeCache.set(a, b, M); } return M; } // // Make pass over all M {@code parents}; merge any {@code equals()} // ones. // / function combineCommonParents(parents) { var uniqueParents = {}; for (var p = 0; p < parents.length; p++) { var parent = parents[p]; if (!(parent in uniqueParents)) { uniqueParents[parent] = parent; } } for (var q = 0; q < parents.length; q++) { parents[q] = uniqueParents[parents[q]]; } } function getCachedPredictionContext(context, contextCache, visited) { if (context.isEmpty()) { return context; } var existing = visited[context] || null; if (existing !== null) { return existing; } existing = contextCache.get(context); if (existing !== null) { visited[context] = existing; return existing; } var changed = false; var parents = []; for (var i = 0; i < parents.length; i++) { var parent = getCachedPredictionContext(context.getParent(i), contextCache, visited); if (changed || parent !== context.getParent(i)) { if (!changed) { parents = []; for (var j = 0; j < context.length; j++) { parents[j] = context.getParent(j); } changed = true; } parents[i] = parent; } } if (!changed) { contextCache.add(context); visited[context] = context; return context; } var updated = null; if (parents.length === 0) { updated = PredictionContext.EMPTY; } else if (parents.length === 1) { updated = SingletonPredictionContext.create(parents[0], context .getReturnState(0)); } else { updated = new ArrayPredictionContext(parents, context.returnStates); } contextCache.add(updated); visited[updated] = updated; visited[context] = updated; return updated; } // ter's recursive version of Sam's getAllNodes() function getAllContextNodes(context, nodes, visited) { if (nodes === null) { nodes = []; return getAllContextNodes(context, nodes, visited); } else if (visited === null) { visited = {}; return getAllContextNodes(context, nodes, visited); } else { if (context === null || visited[context] !== null) { return nodes; } visited[context] = context; nodes.push(context); for (var i = 0; i < context.length; i++) { getAllContextNodes(context.getParent(i), nodes, visited); } return nodes; } } exports.merge = merge; exports.PredictionContext = PredictionContext; exports.PredictionContextCache = PredictionContextCache; exports.SingletonPredictionContext = SingletonPredictionContext; exports.predictionContextFromRuleContext = predictionContextFromRuleContext; exports.getCachedPredictionContext = getCachedPredictionContext;




© 2015 - 2025 Weber Informatics LLC | Privacy Policy