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 }