1 /*
2  * Copyright (c) 2012-2018 The ANTLR Project. All rights reserved.
3  * Use of this file is governed by the BSD 3-clause license that
4  * can be found in the LICENSE.txt file in the project root.
5  */
6 
7 module antlr.v4.runtime.atn.ATNConfigSet;
8 
9 import antlr.v4.runtime.IllegalStateException;
10 import antlr.v4.runtime.UnsupportedOperationException;
11 import antlr.v4.runtime.atn.ATNConfig;
12 import antlr.v4.runtime.atn.ATNState;
13 import antlr.v4.runtime.atn.AbstractConfigHashSet;
14 import antlr.v4.runtime.atn.InterfaceATNSimulator;
15 import antlr.v4.runtime.atn.PredictionContext;
16 import antlr.v4.runtime.atn.SemanticContext;
17 import antlr.v4.runtime.misc.AbstractEqualityComparator;
18 import antlr.v4.runtime.misc;
19 import std.algorithm.comparison : equal, max;
20 import std.algorithm.iteration : map, sum;
21 import std.array;
22 import std.bitmanip;
23 import std.conv;
24 import std.format;
25 
26 
27 /**
28  * Specialized {@link Set}{@code <}{@link ATNConfig}{@code >} that can track
29  * info about the set, with support for combining similar configurations using a
30  * graph-structured stack.
31  *
32  * The reason that we need this is because we don't want the hash map to use
33  * the standard hash code and equals. We need all configurations with the same
34  * {@code (s,i,_,semctx)} to be equal. Unfortunately, this key effectively doubles
35  * the number of objects associated with ATNConfigs. The other solution is to
36  * use a hash table that lets us specify the equals/hashcode operation.
37  */
38 class ATNConfigSet
39 {
40 
41     // Class ConfigHashSet
42     /**
43      * The reason that we need this is because we don't want the hash map to use
44      * the standard hash code and equals. We need all configurations with the same
45      * {@code (s,i,_,semctx)} to be equal. Unfortunately, this key effectively doubles
46      * the number of objects associated with ATNConfigs. The other solution is to
47      * use a hash table that lets us specify the equals/hashcode operation.
48      */
49     class ConfigHashSet : AbstractConfigHashSet
50     {
51 
52         public this()
53         {
54             super(&ConfigEqualityComparator.hashOf, &ConfigEqualityComparator.opEquals);
55         }
56 
57     }
58     // Singleton ConfigEqualityComparator
59     /**
60      * TODO add class description
61      */
62     static class ConfigEqualityComparator : AbstractEqualityComparator!ATNConfig
63     {
64 
65         /**
66          * The single instance of ConfigEqualityComparator.
67          */
68         private static __gshared ATNConfigSet.ConfigEqualityComparator instance_;
69 
70         /**
71          * @uml
72          * @nothrow
73          * @safe
74          */
75         public static size_t hashOf(Object obj) @safe nothrow
76         {
77             auto o = cast(ATNConfig) obj;
78             size_t hashCode = 7;
79             hashCode = 31 * hashCode + o.state.stateNumber;
80             hashCode = 31 * hashCode + o.alt;
81             if (o.semanticContext)
82                 hashCode = 31 * hashCode + o.semanticContext.toHash;
83             return hashCode;
84         }
85         
86         public static bool opEquals(Object aObj, Object bObj)
87         {
88             if (aObj is null || bObj is null)
89                 return false;
90 
91             auto a = cast(ATNConfig) aObj;
92             auto b = cast(ATNConfig) bObj;
93             if (a is b)
94                 return true;
95 
96             if (a.semanticContext is b.semanticContext)
97                 return a.state.stateNumber == b.state.stateNumber
98                     && a.alt == b.alt;
99             if (a.semanticContext is null || b.semanticContext is null)
100                 return false;
101             return a.state.stateNumber == b.state.stateNumber
102                 && a.alt == b.alt
103                 && a.semanticContext.opEquals(b.semanticContext);
104         }
105 
106         /**
107          * Creates the single instance of ConfigEqualityComparator.
108          */
109         private shared static this()
110         {
111             instance_ = new ConfigEqualityComparator;
112         }
113 
114         /**
115          * Returns: A single instance of ConfigEqualityComparator.
116          */
117         public static ConfigEqualityComparator instance()
118         {
119             return instance_;
120         }
121 
122     }
123 
124     /**
125      * @uml
126      * Indicates that the set of configurations is read-only. Do not
127      * allow any code to manipulate the set; DFA states will point at
128      * the sets and they must not change. This does not protect the other
129      * fields; in particular, conflictingAlts is set after
130      * we've made this readonly.
131      * @read
132      */
133     public bool readonly_ = false;
134 
135     public AbstractConfigHashSet configLookup;
136 
137     /**
138      * @uml
139      * Track the elements as they are added to the set; supports get(i)
140      */
141     public ATNConfig[] configs;
142 
143     /**
144      * @uml
145      * TODO: these fields make me pretty uncomfortable but nice to pack up info together, saves recomputation
146      * TODO: can we track conflicts as they are added to save scanning configs later?
147      */
148     public int uniqueAlt;
149 
150     /**
151      * @uml
152      * Currently this is only used when we detect SLL conflict; this does
153      * not necessarily represent the ambiguous alternatives. In fact,
154      * I should also point out that this seems to include predicated alternatives
155      * that have predicates that evaluate to false. Computed in computeTargetState().
156      */
157     public BitSet conflictingAlts;
158 
159     /**
160      * @uml
161      * Used in parser and lexer. In lexer, it indicates we hit a pred
162      * while computing a closure operation.  Don't make a DFA state from this.
163      */
164     public bool hasSemanticContext;
165 
166     public bool dipsIntoOuterContext;
167 
168     /**
169      * @uml
170      * Indicates that this configuration set is part of a full context
171      * LL prediction. It will be used to determine how to merge $. With SLL
172      * it's a wildcard whereas it is not for LL context merge.
173      * @final
174      */
175     public bool fullCtx;
176 
177     private size_t cachedHashCode = -1;
178 
179     public this(bool fullCtx)
180     {
181         configLookup = new ConfigHashSet();
182         this.fullCtx = fullCtx;
183     }
184 
185     public this()
186     {
187         this(true);
188     }
189 
190     public this(ATNConfigSet old)
191     {
192         this(old.fullCtx);
193         addAll(old.configs);
194         this.uniqueAlt = old.uniqueAlt;
195         this.conflictingAlts = old.conflictingAlts;
196         this.hasSemanticContext = old.hasSemanticContext;
197         this.dipsIntoOuterContext = old.dipsIntoOuterContext;
198     }
199 
200     public bool add(ATNConfig config)
201     {
202         return add(config, null);
203     }
204 
205     /**
206      * Adding a new config means merging contexts with existing configs for
207      * {@code (s, i, pi, _)}, where {@code s} is the
208      * {@link ATNConfig#state}, {@code i} is the {@link ATNConfig#alt}, and
209      * {@code pi} is the {@link ATNConfig#semanticContext}. We use
210      * {@code (s,i,pi)} as key.
211      *
212      * <p>This method updates {@link #dipsIntoOuterContext} and
213      * {@link #hasSemanticContext} when necessary.</p>
214      */
215     public bool add(ATNConfig config, DoubleKeyMap!(PredictionContext, PredictionContext, PredictionContext) mergeCache)
216     {
217 	if (readonly_)
218             throw new IllegalStateException("This set is readonly");
219 
220         if (config.semanticContext != SemanticContext.NONE) {
221             hasSemanticContext = true;
222         }
223         if (config.getOuterContextDepth > 0) {
224             dipsIntoOuterContext = true;
225         }
226         // import std.stdio;
227         // writefln("x2x2 config = %s, configs = %s", config, configs);
228         ATNConfig existing = configLookup.getOrAdd(config);
229 
230         if (existing is config) { // we added this new one
231             cachedHashCode = -1;
232             configs ~= config;  // track order here
233             // writefln("x2x2 111111111111 config = %s, configs = %s", config.context, configs);
234             return true;
235         }
236 
237         // a previous (s,i,pi,_), merge with it and save result
238         bool rootIsWildcard = !fullCtx;
239 
240         PredictionContext merged =
241             PredictionContext.merge(existing.context, config.context, rootIsWildcard, mergeCache);
242         // no need to check for existing.context, config.context in cache
243         // since only way to create new graphs is "call rule" and here. We
244         // cache at both places.
245         existing.reachesIntoOuterContext =
246             max(existing.reachesIntoOuterContext, config.reachesIntoOuterContext);
247 
248         // make sure to preserve the precedence filter suppression during the merge
249         if (config.isPrecedenceFilterSuppressed()) {
250             existing.setPrecedenceFilterSuppressed(true);
251         }
252         existing.context = merged; // replace context; no need to alt mapping
253         return true;
254     }
255 
256     /**
257      * @uml
258      * Return a List holding list of configs
259      */
260     public ATNConfig[] elements()
261     {
262         return configs;
263     }
264 
265     public ATNState[] getStates()
266     {
267         ATNState[] states;
268         foreach (ATNConfig c; configs) {
269             states ~= c.state;
270         }
271         return states;
272     }
273 
274     /**
275      * @uml
276      * Gets the complete set of represented alternatives for the configuration
277      * set.
278      *
279      *  @return the set of represented alternatives in this configuration set
280      */
281     public BitSet getAlts()
282     {
283         BitSet alts;
284         foreach (ATNConfig config; configs) {
285             alts.set(config.alt, true);
286         }
287         return alts;
288     }
289 
290     public SemanticContext[] getPredicates()
291     {
292 	SemanticContext[] preds;
293         foreach (ATNConfig c; configs) {
294             if (c.semanticContext != SemanticContext.NONE) {
295                 preds ~= c.semanticContext;
296             }
297         }
298         return preds;
299     }
300 
301     public ATNConfig get(int i)
302     {
303         return configs[i];
304     }
305 
306     public void optimizeConfigs(InterfaceATNSimulator interpreter)
307     {
308 	if (readonly_) throw new IllegalStateException("This set is readonly");
309         if (configLookup.isEmpty) return;
310         foreach (ref ATNConfig config; configs) {
311             //			int before = PredictionContext.getAllContextNodes(config.context).size();
312             config.context = interpreter.getCachedContext(config.context);
313             //			int after = PredictionContext.getAllContextNodes(config.context).size();
314             //			System.out.println("configs "+before+"->"+after);
315         }
316     }
317 
318     public bool addAll(ATNConfig[] coll)
319     {
320 	foreach (ATNConfig c; coll) add(c);
321         return false;
322     }
323 
324     /**
325      * @uml
326      * @override
327      */
328     public override bool opEquals(Object o)
329     {
330 
331 	if (o is this) {
332             return true;
333         }
334         else {
335             if (o.classinfo != ATNConfigSet.classinfo) {
336                 return false;
337             }
338         }
339         ATNConfigSet other = cast(ATNConfigSet)o;
340         if (other.size != this.size)
341                     return false;
342         if (equal(this.configs, other.configs) != 0) {  // check stack context
343                 return false;
344         }
345 
346         return fullCtx == other.fullCtx &&
347             uniqueAlt == other.uniqueAlt &&
348             conflictingAlts == other.conflictingAlts &&
349             hasSemanticContext == other.hasSemanticContext &&
350             dipsIntoOuterContext == other.dipsIntoOuterContext;
351     }
352     
353         public static bool equals(Object aObj, Object bObj)
354         {
355             if (aObj is null || bObj is null)
356                 return false;
357             auto a = cast(ATNConfig) aObj;
358             auto b = cast(ATNConfig) bObj;
359             if (a is b)
360                 return true;
361             if (a.semanticContext is b.semanticContext)
362                 return a.state.stateNumber == b.state.stateNumber
363                     && a.alt == b.alt;
364             if (a.semanticContext is null || b.semanticContext is null)
365                 return false;
366             return a.state.stateNumber == b.state.stateNumber
367                 && a.alt == b.alt
368                 && a.semanticContext.opEquals(b.semanticContext);
369         }
370  
371     /**
372      * @uml
373      * @override
374      * @safe
375      * @nothrow
376      */
377     public override size_t toHash() @safe nothrow
378     {
379         if (readonly_) {
380             if (cachedHashCode == -1) {
381                 cachedHashCode = configs.map!(n => n.toHash).sum;
382             }
383             return cachedHashCode;
384         }
385         return configs.map!(n => n.toHash).sum;
386     }
387 
388     public int size()
389     {
390         return to!int(configs.length);
391     }
392 
393     public bool isEmpty()
394     {
395 	return !configs.length;
396     }
397 
398     public bool contains(ATNConfig o)
399     {
400         if (configLookup is null) {
401             throw new UnsupportedOperationException("This method is not implemented for readonly sets.");
402         }
403 
404         return configLookup.contains(o);
405     }
406 
407     public bool containsFast(ATNConfig obj)
408     {
409         if (configLookup is null) {
410             throw new UnsupportedOperationException("This method is not implemented for readonly sets.");
411         }
412 
413         return configLookup.containsFast(obj);
414     }
415 
416     public void clear()
417     {
418         if (readonly_) throw new IllegalStateException("This set is readonly");
419         configs.length = 0;
420         cachedHashCode = -1;
421         configLookup.clear;
422     }
423 
424     public void readonly(bool readonly)
425     {
426         readonly_ = readonly;
427         configLookup = null; // can't mod, no need for lookup cache
428     }
429 
430     /**
431      * @uml
432      * @override
433      */
434     public override string toString()
435     {
436 	auto buf = appender!string;
437         buf.put(to!string(elements));
438         if (hasSemanticContext) buf.put(format(",hasSemanticContext=%s", hasSemanticContext));
439         if (uniqueAlt != 0) // ATN.INVALID_ALT_NUMBER
440             buf.put(format(",uniqueAlt=%s", uniqueAlt));
441         if (!conflictingAlts.isEmpty)
442             buf.put(format(",conflictingAlts=%s", conflictingAlts));
443         if (dipsIntoOuterContext) buf.put(",dipsIntoOuterContext");
444         return buf.data;
445     }
446 
447     public final bool readonly()
448     {
449         return this.readonly_;
450     }
451 
452 }
453 
454 version(unittest) {
455     import dshould : be, equal, not, should;
456     import unit_threaded;
457     @Tags("atnConfigSet")
458 	@("atnConfigSetTest")
459 	unittest {
460             ATNConfigSet atnConfigSet = new ATNConfigSet;
461             atnConfigSet.should.not.be(null);
462             atnConfigSet.readonly.should.equal(false);
463             atnConfigSet.toString.should.equal("[]");
464             atnConfigSet.isEmpty.should.equal(true);
465             atnConfigSet.toHash.should.equal(0);
466         }
467 }