1 module antlr.v4.runtime.UnbufferedTokenStream; 2 3 import std.format; 4 import std.array; 5 import std.conv; 6 import std.algorithm; 7 import antlr.v4.runtime.RuleContext; 8 import antlr.v4.runtime.TokenStream; 9 import antlr.v4.runtime.Token; 10 import antlr.v4.runtime.TokenSource; 11 import antlr.v4.runtime.TokenConstantDefinition; 12 import antlr.v4.runtime.UnsupportedOperationException; 13 import antlr.v4.runtime.IllegalArgumentException; 14 import antlr.v4.runtime.IllegalStateException; 15 import antlr.v4.runtime.WritableToken; 16 import antlr.v4.runtime.misc.Interval; 17 18 /** 19 * TODO add class description 20 */ 21 class UnbufferedTokenStream : TokenStream 22 { 23 24 protected TokenSource tokenSource; 25 26 /** 27 * @uml 28 * A moving window buffer of the data being scanned. While there's a marker, 29 * we keep adding to buffer. Otherwise, {@link #consume consume()} resets so 30 * we start filling at index 0 again. 31 */ 32 protected Token[] tokens; 33 34 /** 35 * @uml 36 * The number of tokens currently in {@link #tokens tokens}. 37 * 38 * <p>This is not the buffer capacity, that's {@code tokens.length}.</p> 39 */ 40 protected int n; 41 42 /** 43 * @uml 44 * 0..n-1 index into {@link #tokens tokens} of next token. 45 * 46 * <p>The {@code LT(1)} token is {@code tokens[p]}. If {@code p == n}, we are 47 * out of buffered tokens.</p> 48 */ 49 protected int p = 0; 50 51 /** 52 * @uml 53 * Count up with {@link #mark mark()} and down with 54 * {@link #release release()}. When we {@code release()} the last mark, 55 * {@code numMarkers} reaches 0 and we reset the buffer. Copy 56 * {@code tokens[p]..tokens[n-1]} to {@code tokens[0]..tokens[(n-1)-p]}. 57 */ 58 protected int numMarkers = 0; 59 60 protected Token lastToken; 61 62 protected Token lastTokenBufferStart; 63 64 protected int currentTokenIndex = 0; 65 66 public this(TokenSource tokenSource) 67 { 68 this(tokenSource, 256); 69 } 70 71 public this(TokenSource tokenSource, int bufferSize) 72 { 73 this.tokenSource = tokenSource; 74 tokens = new Token[bufferSize]; 75 n = 0; 76 fill(1); // prime the pump 77 } 78 79 public Token get(int i) 80 { 81 int bufferStartIndex = getBufferStartIndex(); 82 assert(i < bufferStartIndex || i >= bufferStartIndex + n, 83 format("get(%1$s) outside buffer: %2$s..%3$s", i, bufferStartIndex, 84 bufferStartIndex+n)); 85 return tokens[i - bufferStartIndex]; 86 } 87 88 public Token LT(int i) 89 { 90 if ( i==-1 ) { 91 return lastToken; 92 } 93 94 sync(i); 95 int index = p + i - 1; 96 assert(index >= 0, format("LT(%s) gives negative index",i)); 97 if ( index >= n ) { 98 assert (n > 0 && tokens[n-1].getType() == TokenConstantDefinition.EOF); 99 return tokens[n-1]; 100 } 101 return tokens[index]; 102 } 103 104 public int LA(int i) 105 { 106 return LT(i).getType(); 107 } 108 109 public TokenSource getTokenSource() 110 { 111 return tokenSource; 112 } 113 114 public string getText() 115 { 116 return ""; 117 } 118 119 public string getText(RuleContext ctx) 120 { 121 return getText(ctx.getSourceInterval()); 122 } 123 124 public string getText(Token start, Token stop) 125 { 126 return getText(Interval.of(start.getTokenIndex(), stop.getTokenIndex())); 127 } 128 129 public void consume() 130 { 131 if (LA(1) == TokenConstantDefinition.EOF) { 132 throw new IllegalStateException("cannot consume EOF"); 133 } 134 // buf always has at least tokens[p==0] in this method due to ctor 135 lastToken = tokens[p]; // track last token for LT(-1) 136 // if we're at last token and no markers, opportunity to flush buffer 137 if ( p == n-1 && numMarkers==0 ) { 138 n = 0; 139 p = -1; // p++ will leave this at 0 140 lastTokenBufferStart = lastToken; 141 } 142 p++; 143 currentTokenIndex++; 144 sync(1); 145 } 146 147 /** 148 * Make sure we have 'need' elements from current position {@link #p p}. Last valid 149 * {@code p} index is {@code tokens.length-1}. {@code p+need-1} is the tokens index 'need' elements 150 * ahead. If we need 1 element, {@code (p+1-1)==p} must be less than {@code tokens.length}. 151 */ 152 protected void sync(int want) 153 { 154 int need = (p+want-1) - n + 1; // how many more elements we need? 155 if ( need > 0 ) { 156 fill(need); 157 } 158 } 159 160 /** 161 * Add {@code n} elements to the buffer. Returns the number of tokens 162 * actually added to the buffer. If the return value is less than {@code n}, 163 * then EOF was reached before {@code n} tokens could be added. 164 */ 165 protected int fill(int n) 166 { 167 for (int i=0; i<n; i++) { 168 if (this.n > 0 && tokens[this.n-1].getType() == TokenConstantDefinition.EOF) { 169 return i; 170 } 171 Token t = tokenSource.nextToken(); 172 add(t); 173 } 174 return n; 175 } 176 177 protected void add(Token t) 178 { 179 if (n >= tokens.length) { 180 tokens.length = tokens.length * 2; 181 } 182 if (cast(WritableToken)t) { 183 (cast(WritableToken)t).setTokenIndex(getBufferStartIndex() + n); 184 } 185 tokens[n++] = t; 186 } 187 188 /** 189 * Return a marker that we can release later. 190 * 191 * <p>The specific marker value used for this class allows for some level of 192 * protection against misuse where {@code seek()} is called on a mark or 193 * {@code release()} is called in the wrong order.</p> 194 */ 195 public int mark() 196 { 197 if (numMarkers == 0) { 198 lastTokenBufferStart = lastToken; 199 } 200 int mark = -numMarkers - 1; 201 numMarkers++; 202 return mark; 203 } 204 205 public void release(int marker) 206 { 207 int expectedMark = -numMarkers; 208 if ( marker!=expectedMark ) { 209 throw new IllegalStateException("release() called with an invalid marker."); 210 } 211 212 numMarkers--; 213 if (numMarkers == 0) { // can we release buffer? 214 if (p > 0) { 215 // Copy tokens[p]..tokens[n-1] to tokens[0]..tokens[(n-1)-p], reset ptrs 216 // p is last valid token; move nothing if p==n as we have no valid char 217 // System.arraycopy(tokens, p, tokens, 0, n - p); // shift n-p tokens from p to 0 218 tokens = tokens[p..(n-p)]; 219 n = n - p; 220 p = 0; 221 } 222 lastTokenBufferStart = lastToken; 223 } 224 } 225 226 /** 227 * @uml 228 * @override 229 */ 230 public override int index() 231 { 232 return currentTokenIndex; 233 } 234 235 public void seek(int index) 236 { 237 if (index == currentTokenIndex) { 238 return; 239 } 240 241 if (index > currentTokenIndex) { 242 sync(index - currentTokenIndex); 243 index = min(index, getBufferStartIndex() + n - 1); 244 } 245 246 int bufferStartIndex = getBufferStartIndex(); 247 int i = index - bufferStartIndex; 248 if ( i < 0 ) { 249 throw new IllegalArgumentException("cannot seek to negative index " ~ 250 to!string(index)); 251 } 252 else if (i >= n) { 253 auto msg = format("seek to index outside buffer: %1$s not in %2$s..%3$s", 254 index, bufferStartIndex, bufferStartIndex + n); 255 throw new UnsupportedOperationException(msg); 256 } 257 258 p = i; 259 currentTokenIndex = index; 260 if (p == 0) { 261 lastToken = lastTokenBufferStart; 262 } 263 else { 264 lastToken = tokens[p-1]; 265 } 266 267 } 268 269 public int size() 270 { 271 throw new UnsupportedOperationException("Unbuffered stream cannot know its size"); 272 } 273 274 public string getSourceName() 275 { 276 return tokenSource.getSourceName(); 277 } 278 279 public string getText(Interval interval) 280 { 281 int bufferStartIndex = getBufferStartIndex(); 282 int bufferStopIndex = bufferStartIndex + to!int(tokens.length) - 1; 283 284 int start = interval.a; 285 int stop = interval.b; 286 if (start < bufferStartIndex || stop > bufferStopIndex) { 287 auto msg = format("interval %1$s not in token buffer window: %2$s..%3$s", 288 interval, bufferStartIndex, bufferStopIndex); 289 throw new UnsupportedOperationException(msg); 290 } 291 292 int a = start - bufferStartIndex; 293 int b = stop - bufferStartIndex; 294 295 auto buf = appender!string; 296 for (int i = a; i <= b; i++) { 297 Token t = tokens[i]; 298 buf.put(t.getText); 299 } 300 301 return buf.data; 302 } 303 304 protected int getBufferStartIndex() 305 { 306 return currentTokenIndex - p; 307 } 308 309 }