1 /*
2  * Copyright (c) 2012-2019 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 
227         ATNConfig existing = configLookup.getOrAdd(config);
228 
229         if (existing is config) { // we added this new one
230             cachedHashCode = -1;
231             configs ~= config;  // track order here
232             return true;
233         }
234 
235         // a previous (s,i,pi,_), merge with it and save result
236         bool rootIsWildcard = !fullCtx;
237 
238         PredictionContext merged =
239             PredictionContext.merge(existing.context, config.context, rootIsWildcard, mergeCache);
240         // no need to check for existing.context, config.context in cache
241         // since only way to create new graphs is "call rule" and here. We
242         // cache at both places.
243         existing.reachesIntoOuterContext =
244             max(existing.reachesIntoOuterContext, config.reachesIntoOuterContext);
245 
246         // make sure to preserve the precedence filter suppression during the merge
247         if (config.isPrecedenceFilterSuppressed()) {
248             existing.setPrecedenceFilterSuppressed(true);
249         }
250         existing.context = merged; // replace context; no need to alt mapping
251         return true;
252     }
253 
254     /**
255      * @uml
256      * Return a List holding list of configs
257      */
258     public ATNConfig[] elements()
259     {
260         return configs;
261     }
262 
263     public ATNState[] getStates()
264     {
265         ATNState[] states;
266         foreach (ATNConfig c; configs) {
267             states ~= c.state;
268         }
269         return states;
270     }
271 
272     /**
273      * @uml
274      * Gets the complete set of represented alternatives for the configuration
275      * set.
276      *
277      *  @return the set of represented alternatives in this configuration set
278      */
279     public BitSet getAlts()
280     {
281         BitSet alts;
282         foreach (ATNConfig config; configs) {
283             alts.set(config.alt, true);
284         }
285         return alts;
286     }
287 
288     public SemanticContext[] getPredicates()
289     {
290     SemanticContext[] preds;
291         foreach (ATNConfig c; configs) {
292             if (c.semanticContext != SemanticContext.NONE) {
293                 preds ~= c.semanticContext;
294             }
295         }
296         return preds;
297     }
298 
299     public ATNConfig get(int i)
300     {
301         return configs[i];
302     }
303 
304     public void optimizeConfigs(InterfaceATNSimulator interpreter)
305     {
306     if (readonly_) throw new IllegalStateException("This set is readonly");
307         if (configLookup.isEmpty) return;
308         foreach (ref ATNConfig config; configs) {
309             debug(ATNConfigSet)
310                 auto before = PredictionContext.getAllContextNodes(config.context).length;
311             config.context = interpreter.getCachedContext(config.context);
312             debug(ATNConfigSet) {
313                 auto after = PredictionContext.getAllContextNodes(config.context).length;
314                 import std.stdio;
315                 writefln("ATNConfigSet: configs %s -> %s", before, after);
316             }
317         }
318     }
319 
320     public bool addAll(ATNConfig[] coll)
321     {
322     foreach (ATNConfig c; coll) add(c);
323         return false;
324     }
325 
326     /**
327      * @uml
328      * @override
329      */
330     public override bool opEquals(Object o)
331     {
332 
333     if (o is this) {
334             return true;
335         }
336         else {
337             if (o.classinfo != ATNConfigSet.classinfo) {
338                 return false;
339             }
340         }
341         ATNConfigSet other = cast(ATNConfigSet)o;
342         if (other.size != this.size)
343                     return false;
344         if (equal(this.configs, other.configs) != 0) {  // check stack context
345                 return false;
346         }
347 
348         return fullCtx == other.fullCtx &&
349             uniqueAlt == other.uniqueAlt &&
350             conflictingAlts == other.conflictingAlts &&
351             hasSemanticContext == other.hasSemanticContext &&
352             dipsIntoOuterContext == other.dipsIntoOuterContext;
353     }
354 
355         public static bool equals(Object aObj, Object bObj)
356         {
357             if (aObj is bObj)
358                 return true;
359             auto a = cast(ATNConfig) aObj;
360             auto b = cast(ATNConfig) bObj;
361             if (a is null || b is null)
362                 return false;
363             if (a.semanticContext is b.semanticContext)
364                 return a.state.stateNumber == b.state.stateNumber
365                     && a.alt == b.alt;
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 std.typecons : tuple;
457     import unit_threaded;
458 
459     @Tags("atnConfigSet")
460     @("atnConfigSetTest")
461     unittest {
462             ATNConfigSet atnConfigSet = new ATNConfigSet;
463             atnConfigSet.should.not.be(null);
464             atnConfigSet.readonly.should.equal(false);
465             atnConfigSet.toString.should.equal("[]");
466             atnConfigSet.isEmpty.should.equal(true);
467             atnConfigSet.toHash.should.equal(0);
468         }
469 }