2
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
4
* Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
6
* The contents of this file are subject to the terms of either the GNU
7
* General Public License Version 2 only ("GPL") or the Common
8
* Development and Distribution License("CDDL") (collectively, the
9
* "License"). You may not use this file except in compliance with the
10
* License. You can obtain a copy of the License at
11
* http://www.netbeans.org/cddl-gplv2.html
12
* or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
13
* specific language governing permissions and limitations under the
14
* License. When distributing the software, include this License Header
15
* Notice in each file and include the License file at
16
* nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
17
* particular file as subject to the "Classpath" exception as provided
18
* by Sun in the GPL Version 2 section of the License file that
19
* accompanied this code. If applicable, add the following below the
20
* License Header, with the fields enclosed by brackets [] replaced by
21
* your own identifying information:
22
* "Portions Copyrighted [year] [name of copyright owner]"
26
* The Original Software is NetBeans. The Initial Developer of the Original
27
* Software is Sun Microsystems, Inc. Portions Copyright 1997-2007 Sun
28
* Microsystems, Inc. All Rights Reserved.
30
* If you wish your version of this file to be governed by only the CDDL
31
* or only the GPL Version 2, indicate your decision by adding
32
* "[Contributor] elects to include this software in this distribution
33
* under the [CDDL or GPL Version 2] license." If you do not indicate a
34
* single choice of license, a recipient has the option to distribute
35
* your version of this file under either the CDDL, the GPL Version 2 or
36
* to extend the choice of license to its licensees as provided above.
37
* However, if you add GPL Version 2 code and therefore, elected the GPL
38
* Version 2 license, then the option applies only if the new code is
39
* made subject to such option by the copyright holder.
42
package org.netbeans.modules.lexer.demo.antlr;
44
import antlr.LexerSharedInputState;
45
import antlr.CharStreamException;
46
import antlr.TokenStreamException;
47
import org.netbeans.api.lexer.Language;
48
import org.netbeans.api.lexer.Lexer;
49
import org.netbeans.api.lexer.LexerInput;
50
import org.netbeans.api.lexer.Token;
51
import org.netbeans.api.lexer.TokenId;
52
import org.netbeans.spi.lexer.AbstractLexer;
53
import org.netbeans.spi.lexer.antlr.AntlrToken;
54
import org.netbeans.spi.lexer.util.IntegerCache;
55
import org.netbeans.spi.lexer.util.LexerInputReader;
58
* Wrapper for antlr generated {@link antlr.CharScanner}.
59
* <BR>Please read <A href="http://lexer.netbeans.org/doc/antlr.html">
60
* to get additional information related to this source.
62
* <P>Most of the tokens
63
* returned from the scanner are just accepted and passed on
64
* but e.g. error tokens are created by assembling one or more scanner tokens into one
65
* extended error token. That's done because it's nicer to produce
66
* just one error token than multiple successive error tokens.
68
* @author Miloslav Metelka
72
final class CalcLexer extends AbstractLexer {
74
private static final CalcLanguage language = CalcLanguage.get();
76
private CalcScanner scanner;
78
private LexerInput lexerInput;
81
this.scanner = new CalcScanner((LexerSharedInputState)null);
85
* Default implementation returns null already.
87
protected Object getLexerState() {
92
public void restart(LexerInput input, Object state) {
93
super.restart(input, state);
95
this.lexerInput = input;
97
// Assign a new input state to the scanner for the given lexer inputs
98
LexerSharedInputState inputState = null;
99
if (lexerInput != null) {
100
inputState = new LexerSharedInputState(new LexerInputReader(lexerInput));
102
scanner.setInputState(inputState);
103
if (inputState != null) {
107
// state argument ignored - should always be null
110
protected final LexerInput getLexerInput() { // this method is necessary for AbstractLexer
114
protected final Language getLanguage() { // this method is necessary for AbstractLexer
119
* Fetch next token from underlying antlr scanner.
120
* <BR>The intId of the token that was found can be set
121
* into the given tokenData parameter
122
* by <CODE>TokenData.setTokenIntId()</CODE> in case there was
123
* a valid token found.
124
* <P>Token length of the fetched token can be set into tokenData
125
* by <CODE>TokenData.setTokenLength()</CODE>.
126
* If the token intId or length is not assigned in <CODE>fetchToken()</CODE>
127
* it must be assigned later during either
128
* {@link #ordinaryToken(OrdinaryTokenData)}
129
* or {@link #extendedToken(ExtendedTokenData)} depending
130
* which of these two gets called.
131
* @param tokenData mutable info about the token being fetched.
132
* @return true if a valid token was found or false
133
* if there are no more tokens on the input (in which case a call
134
* to <CODE>TokenData.setTokenIntId()</CODE> is not necessary).
136
protected boolean fetchToken(TokenData tokenData) {
138
antlr.Token antlrToken = scanner.nextToken();
139
if (antlrToken != null) {
140
int intId = antlrToken.getType();
141
if (intId == CalcScannerTokenTypes.EOF) {
144
tokenData.setTokenIntId(antlrToken.getType());
147
if (antlrToken instanceof AntlrToken) {
148
len = ((AntlrToken)antlrToken).getLength();
150
String text = antlrToken.getText();
154
tokenData.setTokenLength(len);
156
} else { // antlrToken is null
157
return false; // no more tokens from scanner
160
} catch (TokenStreamException e) {
161
/* Input that could not be recognized by antlr.
162
* According to the Calc grammar this should
163
* only occur if there are incomplete
165
* at the end of the input
166
* or a generic error caused by characters
167
* not conforming to the grammar.
169
boolean useScannerTextTokenLength = true;
171
// check for incomplete token - use the state variable
173
int state = scanner.getState();
176
incompleteIntId = CalcLanguage.ERROR_INT;
179
case CalcScannerTokenTypes.INCOMPLETE_ML_COMMENT:
180
// the following construction in Calc.g causes to get here
182
// ML_COMMENT : INCOMPLETE_ML_COMMENT { state = CalcScannerTokenTypes.INCOMPLETE_ML_COMMENT; }
183
// ( { LA(2) != '/' }? '*'
186
// "*/" { state = 0; }
189
incompleteIntId = state;
191
// The scanner would not include
192
// the last char when adding non-star character to the end of input
194
// when useScannerTextTokenLength is left to be true
195
// Therefore lexerInput-based tokenLength is used instead.
196
useScannerTextTokenLength = false;
200
throw new IllegalStateException(); // unhandled case
203
scanner.resetState();
205
tokenData.setTokenIntId(incompleteIntId);
207
int scannerTextTokenLength = scanner.getText().length();
208
int tokenLength = useScannerTextTokenLength
209
? scannerTextTokenLength
210
: tokenData.getDefaultTokenLength();
212
// Sync scanner with lexerInput if necessary
213
if (scannerTextTokenLength > tokenLength) { // Should not happen
214
throw new IllegalStateException("Internal lexer error"); // NOI18N
216
while (scannerTextTokenLength < tokenLength) {
217
scannerConsumeChar();
218
scannerTextTokenLength++;
221
// Make sure that token contains at least one char
222
tokenLength = increaseTokenLengthIfEmpty(tokenLength);
223
tokenData.setTokenLength(tokenLength);
231
private int increaseTokenLengthIfEmpty(int tokenLength) {
232
if (tokenLength == 0) { // single char unaccepted by scanner
233
scannerConsumeChar();
239
private void scannerConsumeChar() {
242
} catch (CharStreamException e) {
243
throw new IllegalStateException();
248
* Called after a token was successfully fetched
249
* by {@link #fetchToken(TokenData)} to possibly
250
* start an extended token mode
251
* by {@link OrdinaryTokenData#startExtendedToken()}
252
* <P>When extended token mode is started
253
* the {@link #extendedToken(ExtendedTokenData, boolean)}
254
* is called after each future {@link #fetchToken(TokenData) instead
255
* of <CODE>ordinaryToken()</CODE> (that would be called
256
* in non-extended mode by default).
257
* @param tokenData mutable info holding information
258
* about previously fetched token.
259
* @see OrdinaryTokenData
261
protected void ordinaryToken(OrdinaryTokenData tokenData) {
264
* Now possibly update the tokenIntId for tokens
265
* that do not have direct counterparts in the language
266
* and start extended tokens for errors
267
* and multi-line-comments.
269
int tokenIntId = tokenData.getTokenIntId();
270
switch (tokenIntId) { // check for types that start extended token
271
case CalcLanguage.ERROR_INT:
272
// All errors are attempted to be concatenated together
273
tokenData.startExtendedToken();
281
* Called in extended token mode after a token was successfully fetched
282
* by {@link #fetchToken(TokenData)} to possibly update
283
* the extended token identification or finish
284
* the extended token being put together.
286
* <P>Please note that the <CODE>extendedToken()</CODE> is not called
287
* after extended token mode gets started
288
* by <CODE>OrdinaryTokenData.startExtendedToken()</CODE>
289
* in <CODE>ordinaryToken()</CODE> until another <CODE>fetchToken()</CODE>
290
* is done. The sequence is:<pre>
292
* ordinaryToken() -> possibly startExtendedToken()
298
* extendedToken() -> possibly finishExtendedToken(true)
306
* @param tokenData mutable compound info about the token
307
* that was previously fetched and about the extended token
308
* that is being put together.
309
* @param fetchedTokenExists true if the last fetched token
310
* was valid i.e. the <CODE>fetchToken()</CODE> returned true.
311
* False if there are no more tokens to fetch from the input.
312
* <BR>If the parameter is false then this method
313
* must mandatorily finish the extended token
314
* by calling <CODE>finishExtendedToken()</CODE>.
315
* @see ExtendedTokenData
317
protected void extendedToken(ExtendedTokenData tokenData,
318
boolean fetchedTokenExists) {
320
int extendedTokenIntId = tokenData.getExtendedTokenIntId();
321
int tokenIntId = tokenData.getTokenIntId(); // fetched token id
322
switch (extendedTokenIntId) {
323
case CalcLanguage.ERROR_INT:
324
if (!fetchedTokenExists
325
|| tokenIntId != CalcLanguage.ERROR_INT
327
/* The fetched token is not the error token
328
* or there are no more tokens on the input.
329
* Finish the extended token and exclude
330
* the current token from it.
332
tokenData.finishExtendedToken(false);
336
default: // there should be no other extended tokens supported
337
throw new IllegalStateException("Unsupported extended token");
343
public String toString() {
344
String scannerText = scanner.getText();
345
return super.toString() + ", scannerText=\"" + scannerText
346
+ "\";length=" + scannerText.length();