From bcfca2009ed3d4584d515ebf5ab918902a94a45d Mon Sep 17 00:00:00 2001 From: sharwell Date: Wed, 8 Feb 2012 23:50:55 -0600 Subject: [PATCH] DFA adaptive edge map optimization --- .../antlr/v4/runtime/atn/ATNSimulator.java | 2 +- .../v4/runtime/atn/LexerATNSimulator.java | 22 +-- .../v4/runtime/atn/ParserATNSimulator.java | 18 +- .../antlr/v4/runtime/dfa/AbstractEdgeMap.java | 44 +++++ .../antlr/v4/runtime/dfa/ArrayEdgeMap.java | 162 +++++++++++++++++ .../antlr/v4/runtime/dfa/DFASerializer.java | 15 +- .../org/antlr/v4/runtime/dfa/DFAState.java | 36 +++- .../src/org/antlr/v4/runtime/dfa/EdgeMap.java | 76 ++++++++ .../v4/runtime/dfa/SingletonEdgeMap.java | 135 ++++++++++++++ .../antlr/v4/runtime/dfa/SparseEdgeMap.java | 164 ++++++++++++++++++ tool/src/org/antlr/v4/tool/DOTGenerator.java | 31 ++-- 11 files changed, 652 insertions(+), 53 deletions(-) create mode 100644 runtime/Java/src/org/antlr/v4/runtime/dfa/AbstractEdgeMap.java create mode 100644 runtime/Java/src/org/antlr/v4/runtime/dfa/ArrayEdgeMap.java create mode 100644 runtime/Java/src/org/antlr/v4/runtime/dfa/EdgeMap.java create mode 100644 runtime/Java/src/org/antlr/v4/runtime/dfa/SingletonEdgeMap.java create mode 100644 runtime/Java/src/org/antlr/v4/runtime/dfa/SparseEdgeMap.java diff --git a/runtime/Java/src/org/antlr/v4/runtime/atn/ATNSimulator.java b/runtime/Java/src/org/antlr/v4/runtime/atn/ATNSimulator.java index e4515f07c..4f108a7f4 100644 --- a/runtime/Java/src/org/antlr/v4/runtime/atn/ATNSimulator.java +++ b/runtime/Java/src/org/antlr/v4/runtime/atn/ATNSimulator.java @@ -44,7 +44,7 @@ public abstract class ATNSimulator { public final ATN atn; static { - ERROR = new DFAState(new ATNConfigSet()); + ERROR = new DFAState(new ATNConfigSet(), 0, 0); ERROR.stateNumber = Integer.MAX_VALUE; } diff --git a/runtime/Java/src/org/antlr/v4/runtime/atn/LexerATNSimulator.java b/runtime/Java/src/org/antlr/v4/runtime/atn/LexerATNSimulator.java index 816060d31..906df621a 100644 --- a/runtime/Java/src/org/antlr/v4/runtime/atn/LexerATNSimulator.java +++ b/runtime/Java/src/org/antlr/v4/runtime/atn/LexerATNSimulator.java @@ -217,8 +217,8 @@ public class LexerATNSimulator extends ATNSimulator { } // if no edge, pop over to ATN interpreter, update DFA and return - if ( s.edges == null || t >= s.edges.length || t <= CharStream.EOF || - s.edges[t] == null ) + DFAState target = s.getTarget(t); + if ( target == null ) { try { ATN_failover++; @@ -229,9 +229,10 @@ public class LexerATNSimulator extends ATNSimulator { break loop; // dead end; no where to go, fall back on prev } } + else if ( target == ERROR ) { + break; + } - DFAState target = s.edges[t]; - if ( target == ERROR ) break; s = target; if ( s.isAcceptState ) { @@ -593,16 +594,9 @@ public class LexerATNSimulator extends ATNSimulator { } protected void addDFAEdge(@NotNull DFAState p, int t, @NotNull DFAState q) { - if (t < 0 || t > MAX_DFA_EDGE) return; // Only track edges within the DFA bounds - if ( p.edges==null ) { - // make room for tokens 1..n and -1 masquerading as index 0 - p.edges = new DFAState[MAX_DFA_EDGE+1]; // TODO: make adaptive + if ( p!=null ) { + p.setTarget(t, q); } -// if ( t==Token.EOF ) { -// System.out.println("state "+p+" has EOF edge"); -// t = 0; -// } - p.edges[t] = q; // connect } /** Add a new DFA state if there isn't one with this set of @@ -629,7 +623,7 @@ public class LexerATNSimulator extends ATNSimulator { */ @Nullable protected DFAState addDFAState(@NotNull ATNConfigSet configs) { - DFAState proposed = new DFAState(configs); + DFAState proposed = new DFAState(configs, 0, MAX_DFA_EDGE); DFAState existing = dfa[mode].states.get(proposed); if ( existing!=null ) return existing; diff --git a/runtime/Java/src/org/antlr/v4/runtime/atn/ParserATNSimulator.java b/runtime/Java/src/org/antlr/v4/runtime/atn/ParserATNSimulator.java index 51b31e458..280900339 100755 --- a/runtime/Java/src/org/antlr/v4/runtime/atn/ParserATNSimulator.java +++ b/runtime/Java/src/org/antlr/v4/runtime/atn/ParserATNSimulator.java @@ -365,7 +365,8 @@ public class ParserATNSimulator extends ATNSimulator { break; } // if no edge, pop over to ATN interpreter, update DFA and return - if ( s.edges == null || t >= s.edges.length || t < -1 || s.edges[t+1] == null ) { + DFAState target = s.getTarget(t); + if ( target == null ) { if ( dfa_debug && t>=0 ) System.out.println("no edge for "+parser.getTokenNames()[t]); int alt = -1; if ( dfa_debug ) { @@ -379,10 +380,10 @@ public class ParserATNSimulator extends ATNSimulator { // same alt; e.g., s0-A->:s1=>2-B->:s2=>2 // TODO: This next stuff kills edge, but extra states remain. :( if ( s.isAcceptState && alt!=-1 ) { - DFAState d = s.edges[input.LA(1)+1]; + DFAState d = s.getTarget(input.LA(1)); if ( d.isAcceptState && d.prediction==s.prediction ) { // we can carve it out. - s.edges[input.LA(1)+1] = ERROR; // IGNORE really not error + s.setTarget(input.LA(1), ERROR); // IGNORE really not error } } if ( dfa_debug ) { @@ -399,8 +400,7 @@ public class ParserATNSimulator extends ATNSimulator { throw nvae; } } - DFAState target = s.edges[t+1]; - if ( target == ERROR ) { + else if ( target == ERROR ) { throw noViableAlt(input, outerContext, s.configset, startIndex); } s = target; @@ -1290,17 +1290,15 @@ public class ParserATNSimulator extends ATNSimulator { } protected void addDFAEdge(@Nullable DFAState p, int t, @Nullable DFAState q) { - if ( p==null || t < -1 || q == null ) return; - if ( p.edges==null ) { - p.edges = new DFAState[atn.maxTokenType+1+1]; // TODO: make adaptive + if ( p!=null ) { + p.setTarget(t, q); } - p.edges[t+1] = q; // connect } /** See comment on LexerInterpreter.addDFAState. */ @Nullable protected DFAState addDFAState(@NotNull DFA dfa, @NotNull ATNConfigSet configs) { - DFAState proposed = new DFAState(configs); + DFAState proposed = new DFAState(configs, -1, atn.maxTokenType); DFAState existing = dfa.states.get(proposed); if ( existing!=null ) return existing; diff --git a/runtime/Java/src/org/antlr/v4/runtime/dfa/AbstractEdgeMap.java b/runtime/Java/src/org/antlr/v4/runtime/dfa/AbstractEdgeMap.java new file mode 100644 index 000000000..e7f3500dc --- /dev/null +++ b/runtime/Java/src/org/antlr/v4/runtime/dfa/AbstractEdgeMap.java @@ -0,0 +1,44 @@ +/* + * [The "BSD license"] + * Copyright (c) 2012 Sam Harwell + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.antlr.v4.runtime.dfa; + +/** + * + * @author Sam Harwell + */ +public abstract class AbstractEdgeMap implements EdgeMap { + + protected final int minIndex; + protected final int maxIndex; + + public AbstractEdgeMap(int minIndex, int maxIndex) { + this.minIndex = minIndex; + this.maxIndex = maxIndex; + } + +} diff --git a/runtime/Java/src/org/antlr/v4/runtime/dfa/ArrayEdgeMap.java b/runtime/Java/src/org/antlr/v4/runtime/dfa/ArrayEdgeMap.java new file mode 100644 index 000000000..e6dbc3c90 --- /dev/null +++ b/runtime/Java/src/org/antlr/v4/runtime/dfa/ArrayEdgeMap.java @@ -0,0 +1,162 @@ +/* + * [The "BSD license"] + * Copyright (c) 2012 Sam Harwell + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.antlr.v4.runtime.dfa; + +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * + * @author sam + */ +public class ArrayEdgeMap extends AbstractEdgeMap { + + private final T[] arrayData; + private int size; + + @SuppressWarnings("unchecked") + public ArrayEdgeMap(int minIndex, int maxIndex) { + super(minIndex, maxIndex); + arrayData = (T[])new Object[maxIndex - minIndex + 1]; + } + + @Override + public int size() { + return size; + } + + @Override + public boolean isEmpty() { + return size == 0; + } + + @Override + public boolean containsKey(int key) { + return get(key) != null; + } + + @Override + public T get(int key) { + if (key < minIndex || key > maxIndex) { + return null; + } + + return arrayData[key - minIndex]; + } + + @Override + public ArrayEdgeMap put(int key, T value) { + if (key >= minIndex && key <= maxIndex) { + T existing = arrayData[key - minIndex]; + arrayData[key - minIndex] = value; + if (existing == null && value != null) { + size++; + } else if (existing != null && value == null) { + size--; + } + } + + return this; + } + + @Override + public ArrayEdgeMap remove(int key) { + return put(key, null); + } + + @Override + public ArrayEdgeMap putAll(EdgeMap m) { + if (m instanceof ArrayEdgeMap) { + ArrayEdgeMap other = (ArrayEdgeMap)m; + int minOverlap = Math.max(minIndex, other.minIndex); + int maxOverlap = Math.min(maxIndex, other.maxIndex); + if (minOverlap > maxIndex || maxOverlap < minIndex) { + int removed = 0; + for (int i = minOverlap - this.minIndex; i <= maxOverlap - this.minIndex; i++) { + if (arrayData[i] != null) { + removed++; + } + } + + int added = 0; + for (int i = minOverlap - other.minIndex; i <= maxOverlap - other.minIndex; i++) { + if (other.arrayData[i] != null) { + added++; + } + } + + System.arraycopy(other.arrayData, minOverlap - other.minIndex, this.arrayData, minOverlap - this.minIndex, maxOverlap - minOverlap + 1); + size = size + added - removed; + } + + return this; + } else if (m instanceof SingletonEdgeMap) { + SingletonEdgeMap other = (SingletonEdgeMap)m; + return put(other.getKey(), other.getValue()); + } else if (m instanceof SparseEdgeMap) { + SparseEdgeMap other = (SparseEdgeMap)m; + int[] keys = other.getKeys(); + List values = other.getValues(); + ArrayEdgeMap result = this; + for (int i = 0; i < values.size(); i++) { + result = result.put(keys[i], values.get(i)); + } + return result; + } else { + throw new UnsupportedOperationException(String.format("EdgeMap of type %s is supported yet.", m.getClass().getName())); + } + } + + @Override + public ArrayEdgeMap clear() { + Arrays.fill(arrayData, null); + return this; + } + + @Override + public Map toMap() { + if (isEmpty()) { + return Collections.emptyMap(); + } + + Map result = new HashMap(); + for (int i = 0; i < arrayData.length; i++) { + if (arrayData[i] == null) { + continue; + } + + result.put(i + minIndex, arrayData[i]); + } + + return result; + } + +} diff --git a/runtime/Java/src/org/antlr/v4/runtime/dfa/DFASerializer.java b/runtime/Java/src/org/antlr/v4/runtime/dfa/DFASerializer.java index face14f53..c2f1300f5 100644 --- a/runtime/Java/src/org/antlr/v4/runtime/dfa/DFASerializer.java +++ b/runtime/Java/src/org/antlr/v4/runtime/dfa/DFASerializer.java @@ -53,13 +53,12 @@ public class DFASerializer { Map states = dfa.states; if ( states!=null ) { for (DFAState s : states.values()) { - int n = 0; - if ( s.edges!=null ) n = s.edges.length; - for (int i=0; i edges = s.getEdgeMap(); + for (Map.Entry entry : edges.entrySet()) { + DFAState t = entry.getValue(); if ( t!=null && t.stateNumber != Integer.MAX_VALUE ) { buf.append(getStateString(s)); - String label = getEdgeLabel(i); + String label = getEdgeLabel(entry.getKey()); buf.append("-"+label+"->"+ getStateString(t)+'\n'); } } @@ -72,9 +71,9 @@ public class DFASerializer { protected String getEdgeLabel(int i) { String label; - if ( i==0 ) return "EOF"; - if ( tokenNames!=null ) label = tokenNames[i-1]; - else label = String.valueOf(i-1); + if ( i==-1 ) return "EOF"; + if ( tokenNames!=null ) label = tokenNames[i]; + else label = String.valueOf(i); return label; } diff --git a/runtime/Java/src/org/antlr/v4/runtime/dfa/DFAState.java b/runtime/Java/src/org/antlr/v4/runtime/dfa/DFAState.java index 7ed0be952..74c888040 100644 --- a/runtime/Java/src/org/antlr/v4/runtime/dfa/DFAState.java +++ b/runtime/Java/src/org/antlr/v4/runtime/dfa/DFAState.java @@ -34,8 +34,10 @@ import org.antlr.v4.runtime.atn.ATNConfigSet; import org.antlr.v4.runtime.atn.SemanticContext; import org.antlr.v4.runtime.misc.Nullable; +import java.util.Collections; import java.util.HashSet; import java.util.List; +import java.util.Map; import java.util.Set; /** A DFA state represents a set of possible ATN configurations. @@ -74,7 +76,9 @@ public class DFAState { /** edges[symbol] points to target of symbol */ @Nullable - public DFAState[] edges; + private EdgeMap edges; + private final int minSymbol; + private final int maxSymbol; public boolean isAcceptState = false; @@ -135,11 +139,35 @@ public class DFAState { } } - public DFAState() { } + public DFAState(ATNConfigSet configs, int minSymbol, int maxSymbol) { + this.configset = configs; + this.minSymbol = minSymbol; + this.maxSymbol = maxSymbol; + } - public DFAState(int stateNumber) { this.stateNumber = stateNumber; } + public DFAState getTarget(int symbol) { + if (edges == null) { + return null; + } - public DFAState(ATNConfigSet configs) { this.configset = configs; } + return edges.get(symbol); + } + + public void setTarget(int symbol, DFAState target) { + if (edges == null) { + edges = new SingletonEdgeMap(minSymbol, maxSymbol); + } + + edges = edges.put(symbol, target); + } + + public Map getEdgeMap() { + if (edges == null) { + return Collections.emptyMap(); + } + + return edges.toMap(); + } /** Get the set of all alts mentioned by all ATN configurations in this * DFA state. diff --git a/runtime/Java/src/org/antlr/v4/runtime/dfa/EdgeMap.java b/runtime/Java/src/org/antlr/v4/runtime/dfa/EdgeMap.java new file mode 100644 index 000000000..7db4a4b10 --- /dev/null +++ b/runtime/Java/src/org/antlr/v4/runtime/dfa/EdgeMap.java @@ -0,0 +1,76 @@ +/* + * [The "BSD license"] + * Copyright (c) 2012 Sam Harwell + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.antlr.v4.runtime.dfa; + +//import java.util.Collection; + +import java.util.Map; +import org.antlr.v4.runtime.misc.NotNull; +import org.antlr.v4.runtime.misc.Nullable; + +//import java.util.Set; + +/** + * + * @author Sam Harwell + */ +public interface EdgeMap { + + int size(); + + boolean isEmpty(); + + boolean containsKey(int key); + +// boolean containsValue(T value); + + @Nullable + T get(int key); + + @NotNull + EdgeMap put(int key, @Nullable T value); + + @NotNull + EdgeMap remove(int key); + + @NotNull + EdgeMap putAll(@NotNull EdgeMap m); + + @NotNull + EdgeMap clear(); + + @NotNull + Map toMap(); + +// Set keySet(); +// +// Collection values(); +// +// Set> entrySet(); + +} diff --git a/runtime/Java/src/org/antlr/v4/runtime/dfa/SingletonEdgeMap.java b/runtime/Java/src/org/antlr/v4/runtime/dfa/SingletonEdgeMap.java new file mode 100644 index 000000000..2515141e0 --- /dev/null +++ b/runtime/Java/src/org/antlr/v4/runtime/dfa/SingletonEdgeMap.java @@ -0,0 +1,135 @@ +/* + * [The "BSD license"] + * Copyright (c) 2012 Sam Harwell + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.antlr.v4.runtime.dfa; + +import java.util.Collections; +import java.util.Map; + +/** + * + * @author Sam Harwell + */ +public class SingletonEdgeMap extends AbstractEdgeMap { + + private int key; + private T value; + + public SingletonEdgeMap(int minIndex, int maxIndex) { + super(minIndex, maxIndex); + } + + public SingletonEdgeMap(int minIndex, int maxIndex, int key, T value) { + super(minIndex, maxIndex); + if (key >= minIndex && key <= maxIndex) { + this.key = key; + this.value = value; + } + } + + public int getKey() { + return key; + } + + public T getValue() { + return value; + } + + @Override + public int size() { + return value != null ? 1 : 0; + } + + @Override + public boolean isEmpty() { + return value == null; + } + + @Override + public boolean containsKey(int key) { + return key == this.key && value != null; + } + + @Override + public T get(int key) { + if (key == this.key) { + return value; + } + + return null; + } + + @Override + public EdgeMap put(int key, T value) { + if (key < minIndex || key > maxIndex) { + return this; + } + + if (key == this.key || this.value == null) { + this.key = key; + this.value = value; + return this; + } else if (value != null) { + EdgeMap result = new SparseEdgeMap(minIndex, maxIndex); + result = result.put(this.key, this.value); + result = result.put(key, value); + return result; + } else { + return this; + } + } + + @Override + public SingletonEdgeMap remove(int key) { + if (key == this.key) { + this.value = null; + } + + return this; + } + + @Override + public EdgeMap putAll(EdgeMap m) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public SingletonEdgeMap clear() { + this.value = null; + return this; + } + + @Override + public Map toMap() { + if (isEmpty()) { + return Collections.emptyMap(); + } + + return Collections.singletonMap(key, value); + } + +} diff --git a/runtime/Java/src/org/antlr/v4/runtime/dfa/SparseEdgeMap.java b/runtime/Java/src/org/antlr/v4/runtime/dfa/SparseEdgeMap.java new file mode 100644 index 000000000..f5e3c8299 --- /dev/null +++ b/runtime/Java/src/org/antlr/v4/runtime/dfa/SparseEdgeMap.java @@ -0,0 +1,164 @@ +/* + * [The "BSD license"] + * Copyright (c) 2012 Sam Harwell + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.antlr.v4.runtime.dfa; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +/** + * + * @author Sam Harwell + */ +public class SparseEdgeMap extends AbstractEdgeMap { + private static final int DEFAULT_MAX_SIZE = 5; + + private final int[] keys; + private final List values; + + public SparseEdgeMap(int minIndex, int maxIndex) { + this(minIndex, maxIndex, DEFAULT_MAX_SIZE); + } + + public SparseEdgeMap(int minIndex, int maxIndex, int maxSparseSize) { + super(minIndex, maxIndex); + this.keys = new int[maxSparseSize]; + this.values = new ArrayList(maxSparseSize); + } + + public int[] getKeys() { + return keys; + } + + public List getValues() { + return values; + } + + public int getMaxSparseSize() { + return keys.length; + } + + @Override + public int size() { + return values.size(); + } + + @Override + public boolean isEmpty() { + return values.isEmpty(); + } + + @Override + public boolean containsKey(int key) { + return get(key) != null; + } + + @Override + public T get(int key) { + int index = Arrays.binarySearch(keys, 0, size(), key); + if (index < 0) { + return null; + } + + return values.get(index); + } + + @Override + public EdgeMap put(int key, T value) { + if (key < minIndex || key > maxIndex) { + return this; + } + + if (value == null) { + return remove(key); + } + + int index = Arrays.binarySearch(keys, 0, size(), key); + if (index >= 0) { + // replace existing entry + values.set(index, value); + return this; + } + + assert index < 0 && value != null; + if (size() < getMaxSparseSize()) { + // stay sparse and add new entry + int insertIndex = -index - 1; + System.arraycopy(keys, insertIndex, keys, insertIndex + 1, keys.length - insertIndex - 1); + keys[insertIndex] = key; + values.add(insertIndex, value); + return this; + } + + assert size() == getMaxSparseSize(); + ArrayEdgeMap arrayMap = new ArrayEdgeMap(minIndex, maxIndex); + arrayMap = arrayMap.putAll(this); + arrayMap.put(key, value); + return arrayMap; + } + + @Override + public SparseEdgeMap remove(int key) { + int index = Arrays.binarySearch(keys, 0, size(), key); + if (index >= 0) { + System.arraycopy(keys, index + 1, keys, index, size() - index - 1); + values.remove(index); + } + + return this; + } + + @Override + public EdgeMap putAll(EdgeMap m) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public SparseEdgeMap clear() { + values.clear(); + return this; + } + + @Override + public Map toMap() { + if (isEmpty()) { + return Collections.emptyMap(); + } + + Map result = new LinkedHashMap(); + for (int i = 0; i < size(); i++) { + result.put(keys[i], values.get(i)); + } + + return result; + } + +} diff --git a/tool/src/org/antlr/v4/tool/DOTGenerator.java b/tool/src/org/antlr/v4/tool/DOTGenerator.java index fa55ace2f..40ad2a2a9 100644 --- a/tool/src/org/antlr/v4/tool/DOTGenerator.java +++ b/tool/src/org/antlr/v4/tool/DOTGenerator.java @@ -84,22 +84,21 @@ public class DOTGenerator { } for (DFAState d : dfa.states.keySet()) { - if ( d.edges!=null ) { - for (int i = 0; i < d.edges.length; i++) { - DFAState target = d.edges[i]; - if ( target==null) continue; - if ( target.stateNumber == Integer.MAX_VALUE ) continue; - int ttype = i-1; // we shift up for EOF as -1 for parser - String label = String.valueOf(ttype); - if ( isLexer ) label = "'"+getEdgeLabel(String.valueOf((char) i))+"'"; - else if ( grammar!=null ) label = grammar.getTokenDisplayName(ttype); - ST st = stlib.getInstanceOf("edge"); - st.add("label", label); - st.add("src", "s"+d.stateNumber); - st.add("target", "s"+target.stateNumber); - st.add("arrowhead", arrowhead); - dot.add("edges", st); - } + Map edges = d.getEdgeMap(); + for (Map.Entry entry : edges.entrySet()) { + DFAState target = entry.getValue(); + if ( target==null) continue; + if ( target.stateNumber == Integer.MAX_VALUE ) continue; + int ttype = entry.getKey(); + String label = String.valueOf(ttype); + if ( isLexer ) label = "'"+getEdgeLabel(String.valueOf((char)ttype))+"'"; + else if ( grammar!=null ) label = grammar.getTokenDisplayName(ttype); + ST st = stlib.getInstanceOf("edge"); + st.add("label", label); + st.add("src", "s"+d.stateNumber); + st.add("target", "s"+target.stateNumber); + st.add("arrowhead", arrowhead); + dot.add("edges", st); } }