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