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         // 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             debug(ATNConfigSet)
312                 auto before = PredictionContext.getAllContextNodes(config.context).length;
313             config.context = interpreter.getCachedContext(config.context);
314             debug(ATNConfigSet) {
315                 auto after = PredictionContext.getAllContextNodes(config.context).length;
316                 import std.stdio;
317                 writefln("ATNConfigSet: configs %s -> %s", before, after);
318             }
319         }
320     }
321 
322     public bool addAll(ATNConfig[] coll)
323     {
324 	foreach (ATNConfig c; coll) add(c);
325         return false;
326     }
327 
328     /**
329      * @uml
330      * @override
331      */
332     public override bool opEquals(Object o)
333     {
334 
335 	if (o is this) {
336             return true;
337         }
338         else {
339             if (o.classinfo != ATNConfigSet.classinfo) {
340                 return false;
341             }
342         }
343         ATNConfigSet other = cast(ATNConfigSet)o;
344         if (other.size != this.size)
345                     return false;
346         if (equal(this.configs, other.configs) != 0) {  // check stack context
347                 return false;
348         }
349 
350         return fullCtx == other.fullCtx &&
351             uniqueAlt == other.uniqueAlt &&
352             conflictingAlts == other.conflictingAlts &&
353             hasSemanticContext == other.hasSemanticContext &&
354             dipsIntoOuterContext == other.dipsIntoOuterContext;
355     }
356 
357         public static bool equals(Object aObj, Object bObj)
358         {
359             if (aObj is null || bObj is null)
360                 return false;
361             auto a = cast(ATNConfig) aObj;
362             auto b = cast(ATNConfig) bObj;
363             if (a is b)
364                 return true;
365             if (a.semanticContext is b.semanticContext)
366                 return a.state.stateNumber == b.state.stateNumber
367                     && a.alt == b.alt;
368             if (a.semanticContext is null || b.semanticContext is null)
369                 return false;
370             return a.state.stateNumber == b.state.stateNumber
371                 && a.alt == b.alt
372                 && a.semanticContext.opEquals(b.semanticContext);
373         }
374 
375     /**
376      * @uml
377      * @override
378      * @safe
379      * @nothrow
380      */
381     public override size_t toHash() @safe nothrow
382     {
383         if (readonly_) {
384             if (cachedHashCode == -1) {
385                 cachedHashCode = configs.map!(n => n.toHash).sum;
386             }
387             return cachedHashCode;
388         }
389         return configs.map!(n => n.toHash).sum;
390     }
391 
392     public int size()
393     {
394         return to!int(configs.length);
395     }
396 
397     public bool isEmpty()
398     {
399 	return !configs.length;
400     }
401 
402     public bool contains(ATNConfig o)
403     {
404         if (configLookup is null) {
405             throw new UnsupportedOperationException("This method is not implemented for readonly sets.");
406         }
407 
408         return configLookup.contains(o);
409     }
410 
411     public bool containsFast(ATNConfig obj)
412     {
413         if (configLookup is null) {
414             throw new UnsupportedOperationException("This method is not implemented for readonly sets.");
415         }
416 
417         return configLookup.containsFast(obj);
418     }
419 
420     public void clear()
421     {
422         if (readonly_) throw new IllegalStateException("This set is readonly");
423         configs.length = 0;
424         cachedHashCode = -1;
425         configLookup.clear;
426     }
427 
428     public void readonly(bool readonly)
429     {
430         readonly_ = readonly;
431         configLookup = null; // can't mod, no need for lookup cache
432     }
433 
434     /**
435      * @uml
436      * @override
437      */
438     public override string toString()
439     {
440 	auto buf = appender!string;
441         buf.put(to!string(elements));
442         if (hasSemanticContext) buf.put(format(",hasSemanticContext=%s", hasSemanticContext));
443         if (uniqueAlt != 0) // ATN.INVALID_ALT_NUMBER
444             buf.put(format(",uniqueAlt=%s", uniqueAlt));
445         if (!conflictingAlts.isEmpty)
446             buf.put(format(",conflictingAlts=%s", conflictingAlts));
447         if (dipsIntoOuterContext) buf.put(",dipsIntoOuterContext");
448         return buf.data;
449     }
450 
451     public final bool readonly()
452     {
453         return this.readonly_;
454     }
455 
456 }
457 
458 version(unittest) {
459     import dshould : be, equal, not, should;
460     import unit_threaded;
461     @Tags("atnConfigSet")
462 	@("atnConfigSetTest")
463 	unittest {
464             ATNConfigSet atnConfigSet = new ATNConfigSet;
465             atnConfigSet.should.not.be(null);
466             atnConfigSet.readonly.should.equal(false);
467             atnConfigSet.toString.should.equal("[]");
468             atnConfigSet.isEmpty.should.equal(true);
469             atnConfigSet.toHash.should.equal(0);
470         }
471 }