forked from jasder/antlr
add ctx cache.
This commit is contained in:
parent
9627652b67
commit
ac4f00524e
|
@ -30,6 +30,7 @@
|
|||
package org.antlr.v4.runtime.atn;
|
||||
|
||||
import org.antlr.v4.runtime.misc.IntervalSet;
|
||||
import org.antlr.v4.runtime.misc.Nullable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
|
@ -102,8 +103,8 @@ public class ATNConfigSet implements Set<ATNConfig> {
|
|||
public ATNConfigSet(boolean fullCtx) { this.fullCtx = fullCtx; }
|
||||
public ATNConfigSet() { this.fullCtx = true; }
|
||||
|
||||
public ATNConfigSet(ATNConfigSet old) {
|
||||
addAll(old);
|
||||
public ATNConfigSet(ATNConfigSet old, PredictionContextCache contextCache) {
|
||||
addAll(old, contextCache);
|
||||
this.fullCtx = old.fullCtx;
|
||||
this.uniqueAlt = old.uniqueAlt;
|
||||
this.conflictingAlts = old.conflictingAlts;
|
||||
|
@ -111,24 +112,32 @@ public class ATNConfigSet implements Set<ATNConfig> {
|
|||
this.dipsIntoOuterContext = old.dipsIntoOuterContext;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean add(ATNConfig e) {
|
||||
return add(e, null);
|
||||
}
|
||||
|
||||
/** Adding a new config means merging contexts with existing configs for
|
||||
* (s, i, pi, _)
|
||||
* We use (s,i,pi) as key
|
||||
*/
|
||||
@Override
|
||||
public boolean add(ATNConfig value) {
|
||||
Key key = new Key(value);
|
||||
public boolean add(ATNConfig config, @Nullable PredictionContextCache contextCache) {
|
||||
Key key = new Key(config);
|
||||
ATNConfig existing = configToContext.get(key);
|
||||
if ( existing==null ) { // nothing there yet; easy, just add
|
||||
configToContext.put(key, value);
|
||||
configToContext.put(key, config);
|
||||
return true;
|
||||
}
|
||||
// a previous (s,i,pi,_), merge with it and save result
|
||||
boolean rootIsWildcard = !fullCtx;
|
||||
PredictionContext merged =
|
||||
PredictionContext.merge(existing.context, value.context, rootIsWildcard);
|
||||
PredictionContext.merge(existing.context, config.context, rootIsWildcard);
|
||||
// no need to check for existing.context, config.context in cache
|
||||
// since only way to create new graphs is "call rule" and here. We
|
||||
// cache at both places.
|
||||
if ( contextCache!=null ) merged = contextCache.add(merged);
|
||||
existing.reachesIntoOuterContext =
|
||||
Math.max(existing.reachesIntoOuterContext, value.reachesIntoOuterContext);
|
||||
Math.max(existing.reachesIntoOuterContext, config.reachesIntoOuterContext);
|
||||
existing.context = merged; // replace context; no need to alt mapping
|
||||
return true;
|
||||
}
|
||||
|
@ -161,17 +170,23 @@ public class ATNConfigSet implements Set<ATNConfig> {
|
|||
if ( configToContext.isEmpty() ) return;
|
||||
|
||||
for (ATNConfig config : configToContext.values()) {
|
||||
int before = PredictionContext.getAllContextNodes(config.context).size();
|
||||
// int before = PredictionContext.getAllContextNodes(config.context).size();
|
||||
config.context = interpreter.getCachedContext(config.context);
|
||||
int after = PredictionContext.getAllContextNodes(config.context).size();
|
||||
System.out.println("configs "+before+"->"+after);
|
||||
// int after = PredictionContext.getAllContextNodes(config.context).size();
|
||||
// System.out.println("configs "+before+"->"+after);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean addAll(Collection<? extends ATNConfig> coll) {
|
||||
public boolean addAll(Collection<? extends ATNConfig> c) {
|
||||
return addAll(c, null);
|
||||
}
|
||||
|
||||
public boolean addAll(Collection<? extends ATNConfig> coll,
|
||||
PredictionContextCache contextCache)
|
||||
{
|
||||
for (ATNConfig c : coll) {
|
||||
add(c);
|
||||
add(c, contextCache);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -34,10 +34,8 @@ import org.antlr.v4.runtime.misc.IntervalSet;
|
|||
import org.antlr.v4.runtime.misc.NotNull;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.IdentityHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public abstract class ATNSimulator {
|
||||
/** Must distinguish between missing edge and edge we know leads nowhere */
|
||||
|
@ -47,14 +45,32 @@ public abstract class ATNSimulator {
|
|||
public final ATN atn;
|
||||
|
||||
/** The context cache maps all PredictionContext objects that are equals()
|
||||
* to a single cached copy. This cache be shared across all contexts
|
||||
* to a single cached copy. This cache is shared across all contexts
|
||||
* in all ATNConfigs in all DFA states. We rebuild each ATNConfigSet
|
||||
* to use only cached nodes/graphs in addDFAState(). We don't want to
|
||||
* fill this during closure() since there are lots of contexts that
|
||||
* pop up but are not used ever again. It also greatly slows down closure().
|
||||
*/
|
||||
protected final Map<PredictionContext, PredictionContext> contextCache =
|
||||
new HashMap<PredictionContext, PredictionContext>();
|
||||
protected final PredictionContextCache sharedContextCache =
|
||||
new PredictionContextCache("shared DFA state context cache");
|
||||
|
||||
/** This context cache tracks all context graphs used during a single
|
||||
* ATN-based prediction operation. There will be significant context graph
|
||||
* sharing among ATNConfigSets because all sets are derived from the
|
||||
* same starting context.
|
||||
*
|
||||
* This cache is blown away after each adaptivePredict()
|
||||
* because we cache everything within ATNConfigSets that become DFA
|
||||
* states in sharedContextCache. (Sam thinks of this as an analogy to
|
||||
* the nursery in a generational GC; then, sharedContextCache would be
|
||||
* the mature generation.)
|
||||
*
|
||||
* In Sam's version, this is a parameter passed down through all of
|
||||
* the methods, but it gets pretty unwieldy as there are already
|
||||
* a crapload of parameters. Consequently, I'm using a field as a
|
||||
* "parameter" despite it being generally poor coding style.
|
||||
*/
|
||||
protected PredictionContextCache contextCache;
|
||||
|
||||
static {
|
||||
ERROR = new DFAState(new ATNConfigSet());
|
||||
|
@ -71,7 +87,7 @@ public abstract class ATNSimulator {
|
|||
IdentityHashMap<PredictionContext, PredictionContext> visited =
|
||||
new IdentityHashMap<PredictionContext, PredictionContext>();
|
||||
return PredictionContext.getCachedContext(context,
|
||||
contextCache,
|
||||
sharedContextCache,
|
||||
visited);
|
||||
}
|
||||
|
||||
|
|
|
@ -240,8 +240,8 @@ import java.util.Set;
|
|||
* holds the decision were evaluating
|
||||
*/
|
||||
public class ParserATNSimulator<Symbol extends Token> extends ATNSimulator {
|
||||
public static boolean debug = true;
|
||||
public static boolean debug_list_atn_decisions = false;
|
||||
public static boolean debug = false;
|
||||
public static boolean debug_list_atn_decisions = true;
|
||||
public static boolean dfa_debug = false;
|
||||
public static boolean retry_debug = false;
|
||||
|
||||
|
@ -316,6 +316,7 @@ public class ParserATNSimulator<Symbol extends Token> extends ATNSimulator {
|
|||
public int predictATN(@NotNull DFA dfa, @NotNull TokenStream input,
|
||||
@Nullable ParserRuleContext<?> outerContext)
|
||||
{
|
||||
contextCache = new PredictionContextCache("predict ctx cache");
|
||||
if ( outerContext==null ) outerContext = ParserRuleContext.EMPTY;
|
||||
if ( debug || debug_list_atn_decisions ) {
|
||||
System.out.println("ATN decision "+dfa.decision+
|
||||
|
@ -344,6 +345,7 @@ public class ParserATNSimulator<Symbol extends Token> extends ATNSimulator {
|
|||
throw nvae;
|
||||
}
|
||||
finally {
|
||||
contextCache = null; // wack the cache
|
||||
input.seek(index);
|
||||
input.release(m);
|
||||
}
|
||||
|
@ -964,7 +966,7 @@ public class ParserATNSimulator<Symbol extends Token> extends ATNSimulator {
|
|||
// don't see past end of a rule for any nongreedy decision
|
||||
if ( debug ) System.out.println("NONGREEDY at stop state of "+
|
||||
getRuleName(config.state.ruleIndex));
|
||||
configs.add(config);
|
||||
configs.add(config, contextCache);
|
||||
return;
|
||||
}
|
||||
// We hit rule end. If we have context info, use it
|
||||
|
@ -1007,6 +1009,7 @@ public class ParserATNSimulator<Symbol extends Token> extends ATNSimulator {
|
|||
{
|
||||
config.context =
|
||||
new SingletonPredictionContext(config.context, config.state.stateNumber);
|
||||
if ( contextCache!=null ) config.context = contextCache.add(config.context);
|
||||
// alter config; it's ok, since all calls to closure pass in a fresh config for us to chase
|
||||
if ( debug ) System.out.println("Loop back; push "+config.state.stateNumber+", stack="+config.context);
|
||||
}
|
||||
|
@ -1033,7 +1036,7 @@ public class ParserATNSimulator<Symbol extends Token> extends ATNSimulator {
|
|||
ATNState p = config.state;
|
||||
// optimization
|
||||
if ( !p.onlyHasEpsilonTransitions() ) {
|
||||
configs.add(config);
|
||||
configs.add(config, contextCache);
|
||||
if ( config.semanticContext!=null && config.semanticContext!= SemanticContext.NONE ) {
|
||||
configs.hasSemanticContext = true;
|
||||
}
|
||||
|
@ -1141,9 +1144,9 @@ public class ParserATNSimulator<Symbol extends Token> extends ATNSimulator {
|
|||
System.out.println("CALL rule "+getRuleName(t.target.ruleIndex)+
|
||||
", ctx="+config.context);
|
||||
}
|
||||
ATNState p = config.state;
|
||||
PredictionContext newContext =
|
||||
new SingletonPredictionContext(config.context, p.stateNumber);
|
||||
new SingletonPredictionContext(config.context, config.state.stateNumber);
|
||||
if ( contextCache!=null ) newContext = contextCache.add(newContext);
|
||||
return new ATNConfig(config, t.target, newContext);
|
||||
}
|
||||
|
||||
|
@ -1446,10 +1449,10 @@ public class ParserATNSimulator<Symbol extends Token> extends ATNSimulator {
|
|||
DFAState newState = proposed;
|
||||
|
||||
newState.stateNumber = dfa.states.size();
|
||||
System.out.println("Before opt, cache size = "+contextCache.size());
|
||||
// System.out.println("Before opt, cache size = "+ sharedContextCache.size());
|
||||
configs.optimizeConfigs(this);
|
||||
System.out.println("After opt, cache size = " + contextCache.size());
|
||||
newState.configs = new ATNConfigSet(configs);
|
||||
// System.out.println("After opt, cache size = " + sharedContextCache.size());
|
||||
newState.configs = new ATNConfigSet(configs, contextCache);
|
||||
|
||||
dfa.states.put(newState, newState);
|
||||
if ( debug ) System.out.println("adding new DFA state: "+newState);
|
||||
|
|
|
@ -389,7 +389,7 @@ public abstract class PredictionContext implements Iterable<SingletonPredictionC
|
|||
// From Sam
|
||||
public static PredictionContext getCachedContext(
|
||||
@NotNull PredictionContext context,
|
||||
@NotNull Map<PredictionContext, PredictionContext> contextCache,
|
||||
@NotNull PredictionContextCache contextCache,
|
||||
@NotNull IdentityHashMap<PredictionContext, PredictionContext> visited)
|
||||
{
|
||||
if (context.isEmpty()) {
|
||||
|
@ -426,7 +426,7 @@ public abstract class PredictionContext implements Iterable<SingletonPredictionC
|
|||
}
|
||||
|
||||
if (!changed) {
|
||||
contextCache.put(context, context);
|
||||
contextCache.add(context);
|
||||
visited.put(context, context);
|
||||
return context;
|
||||
}
|
||||
|
@ -443,7 +443,7 @@ public abstract class PredictionContext implements Iterable<SingletonPredictionC
|
|||
updated = new ArrayPredictionContext(parents, arrayPredictionContext.invokingStates);
|
||||
}
|
||||
|
||||
contextCache.put(updated, updated);
|
||||
contextCache.add(updated);
|
||||
visited.put(updated, updated);
|
||||
visited.put(context, updated);
|
||||
|
||||
|
|
|
@ -0,0 +1,41 @@
|
|||
package org.antlr.v4.runtime.atn;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/** Used to cache PredictionContext objects. Its use for both the shared
|
||||
* context cash associated with contacts in DFA states as well as the
|
||||
* transient cash used for adaptivePredict(). This cache can be used for
|
||||
* both lexers and parsers.
|
||||
*/
|
||||
public class PredictionContextCache {
|
||||
protected String name;
|
||||
protected Map<PredictionContext, PredictionContext> cache =
|
||||
new HashMap<PredictionContext, PredictionContext>();
|
||||
|
||||
public PredictionContextCache(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
/** 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.
|
||||
*/
|
||||
public PredictionContext add(PredictionContext ctx) {
|
||||
if ( ctx==PredictionContext.EMPTY ) return PredictionContext.EMPTY;
|
||||
PredictionContext existing = cache.get(ctx);
|
||||
if ( existing!=null ) {
|
||||
System.out.println(name+" reuses "+existing);
|
||||
return existing;
|
||||
}
|
||||
cache.put(ctx, ctx);
|
||||
return ctx;
|
||||
}
|
||||
|
||||
public PredictionContext get(PredictionContext ctx) {
|
||||
return cache.get(ctx);
|
||||
}
|
||||
|
||||
public int size() {
|
||||
return cache.size();
|
||||
}
|
||||
}
|
|
@ -1,32 +1,12 @@
|
|||
grammar U;
|
||||
lexer grammar U;
|
||||
|
||||
@members {public static boolean java5 = true;}
|
||||
X : 'a' -> skip ;
|
||||
Y : 'z' -> skip, more ;
|
||||
// (RULE C (BLOCK (LEXER_ALT_ACTION (ALT 'x') (LEXER_ACTION_CALL mode ISLAND))))
|
||||
C: 'x' -> mode(ISLAND) ;
|
||||
// (RULE A (BLOCK (LEXER_ALT_ACTION (ALT 'a') (LEXER_ACTION_CALL mode ISLAND) skip)))
|
||||
A: 'b' -> mode(ISLAND), skip ;
|
||||
|
||||
prog: ( enumDecl
|
||||
| stat
|
||||
)*
|
||||
EOF
|
||||
;
|
||||
mode INSIDE;
|
||||
|
||||
enumDecl
|
||||
: {java5}? 'enum' ID '{' ID (',' ID)* '}'
|
||||
;
|
||||
|
||||
args
|
||||
: arg (',' arg )*
|
||||
;
|
||||
|
||||
arg
|
||||
: INT
|
||||
;
|
||||
|
||||
stat: ID '=' expr ';' ;
|
||||
|
||||
expr: ID {System.out.println("ID "+$ID.text);}
|
||||
| {!java5}? 'enum' {System.out.println("ID enum");}
|
||||
| INT
|
||||
;
|
||||
|
||||
ID : [a-zA-Z]+ ;
|
||||
INT : [0-9]+ ;
|
||||
WS : [ \t\n\r]+ -> skip ;
|
||||
B : '<' ;
|
||||
|
|
Loading…
Reference in New Issue