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 }