lin
2025-08-21 57113df3a0e2be01232281fad9a5f2c060567981
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
package java_cup;
 
import java.util.Hashtable;
 
import java_cup.runtime.str_token;
import java_cup.runtime.token;
 
/** This class implements a small scanner (aka lexical analyzer or lexer) for
 *  the JavaCup specification.  This scanner reads characters from standard 
 *  input (System.in) and returns integers corresponding to the terminal 
 *  number of the next token.  Once end of input is reached the EOF token is 
 *  returned on every subsequent call.<p>
 *  Tokens currently returned include: <pre>
 *    Symbol        Constant Returned     Symbol        Constant Returned
 *    ------        -----------------     ------        -----------------
 *    "package"     PACKAGE               "import"      IMPORT 
 *    "code"        CODE                  "action"      ACTION 
 *    "parser"      PARSER                "terminal"    TERMINAL
 *    "non"         NON                   "init"        INIT 
 *    "scan"        SCAN                  "with"        WITH
 *    "start"       START                   ;           SEMI 
 *      ,           COMMA                   *           STAR 
 *      .           DOT                     :           COLON
 *      ::=         COLON_COLON_EQUALS      |           BAR
 *    identifier    ID                    {:...:}       CODE_STRING
 *    "debug"       DEBUG
 *  </pre>
 *  All symbol constants are defined in sym.java which is generated by 
 *  JavaCup from parser.cup.<p>
 * 
 *  In addition to the scanner proper (called first via init() then with
 *  next_token() to get each token) this class provides simple error and 
 *  warning routines and keeps a count of errors and warnings that is 
 *  publicly accessible.<p>
 *  
 *  This class is "static" (i.e., it has only static members and methods).
 *
 * @version last updated: 11/25/95
 * @author  Scott Hudson
 */
public class lexer {
 
  /*-----------------------------------------------------------*/
  /*--- Constructor(s) ----------------------------------------*/
  /*-----------------------------------------------------------*/
 
  /** The only constructor is private, so no instances can be created. */
  private lexer() { }
 
  /*-----------------------------------------------------------*/
  /*--- Static (Class) Variables ------------------------------*/
  /*-----------------------------------------------------------*/
 
  /** First character of lookahead. */
  protected static int next_char; 
 
  /** Second character of lookahead. */
  protected static int next_char2;
 
  /*. . . . . . . . . . . . . . . . . . . . . . . . . . . . . .*/
 
  /** EOF constant. */
  protected static final int EOF_CHAR = -1;
 
  /*. . . . . . . . . . . . . . . . . . . . . . . . . . . . . .*/
 
  /** Table of keywords.  Keywords are initially treated as identifiers.
   *  Just before they are returned we look them up in this table to see if
   *  they match one of the keywords.  The string of the name is the key here,
   *  which indexes Integer objects holding the symbol number. 
   */
  protected static Hashtable keywords = new Hashtable(23);
 
  /*. . . . . . . . . . . . . . . . . . . . . . . . . . . . . .*/
 
  /** Table of single character symbols.  For ease of implementation, we 
   *  store all unambiguous single character tokens in this table of Integer
   *  objects keyed by Integer objects with the numerical value of the 
   *  appropriate char (currently Character objects have a bug which precludes
   *  their use in tables).
   */
  protected static Hashtable char_symbols = new Hashtable(11);
 
  /*. . . . . . . . . . . . . . . . . . . . . . . . . . . . . .*/
 
  /** Current line number for use in error messages. */
  protected static int current_line = 1;
 
  /*. . . . . . . . . . . . . . . . . . . . . . . . . . . . . .*/
 
  /** Character position in current line. */
  protected static int current_position = 1;
 
  /*. . . . . . . . . . . . . . . . . . . . . . . . . . . . . .*/
 
  /** Count of total errors detected so far. */
  public static int error_count = 0;
 
  /*. . . . . . . . . . . . . . . . . . . . . . . . . . . . . .*/
 
  /** Count of warnings issued so far */
  public static int warning_count = 0;
 
  /*-----------------------------------------------------------*/
  /*--- Static Methods ----------------------------------------*/
  /*-----------------------------------------------------------*/
 
  /** Initialize the scanner.  This sets up the keywords and char_symbols
    * tables and reads the first two characters of lookahead.  
    */
  public static void init() throws java.io.IOException
    {
      /* set up the keyword table */
      keywords.put("package",  new Integer(sym.PACKAGE));
      keywords.put("import",   new Integer(sym.IMPORT));
      keywords.put("code",     new Integer(sym.CODE));
      keywords.put("action",   new Integer(sym.ACTION));
      keywords.put("parser",   new Integer(sym.PARSER));
      keywords.put("terminal", new Integer(sym.TERMINAL));
      keywords.put("non",      new Integer(sym.NON));
      keywords.put("init",     new Integer(sym.INIT));
      keywords.put("scan",     new Integer(sym.SCAN));
      keywords.put("with",     new Integer(sym.WITH));
      keywords.put("start",    new Integer(sym.START));
      keywords.put("debug",    new Integer(sym.DEBUG));
 
      /* set up the table of single character symbols */
      char_symbols.put(new Integer(';'), new Integer(sym.SEMI));
      char_symbols.put(new Integer(','), new Integer(sym.COMMA));
      char_symbols.put(new Integer('*'), new Integer(sym.STAR));
      char_symbols.put(new Integer('.'), new Integer(sym.DOT));
      char_symbols.put(new Integer('|'), new Integer(sym.BAR));
 
      /* read two characters of lookahead */
      next_char = System.in.read();
      if (next_char == EOF_CHAR) 
    next_char2 = EOF_CHAR;
      else
    next_char2 = System.in.read();
    }
 
  /*. . . . . . . . . . . . . . . . . . . . . . . . . . . . . .*/
 
  /** Advance the scanner one character in the input stream.  This moves
   * next_char2 to next_char and then reads a new next_char2.  
   */
  protected static void advance() throws java.io.IOException
    {
      int old_char;
 
      old_char = next_char;
      next_char = next_char2;
      if (next_char == EOF_CHAR)
    next_char2 = EOF_CHAR;
      else
    next_char2 = System.in.read();
 
      /* count this */
      current_position++;
      if (old_char == '\n')
    {
      current_line++;
      current_position = 1;
    }
    }
 
  /*. . . . . . . . . . . . . . . . . . . . . . . . . . . . . .*/
 
  /** Emit an error message.  The message will be marked with both the 
   *  current line number and the position in the line.  Error messages
   *  are printed on standard error (System.err).
   * @param message the message to print.
   */
  public static void emit_error(String message)
    {
      System.err.println("Error at " + current_line + "(" + current_position +
             "): " + message);
      error_count++;
    }
 
  /*. . . . . . . . . . . . . . . . . . . . . . . . . . . . . .*/
 
  /** Emit a warning message.  The message will be marked with both the 
   *  current line number and the position in the line.  Messages are 
   *  printed on standard error (System.err).
   * @param message the message to print.
   */
  public static void emit_warn(String message)
    {
      System.err.println("Warning at " + current_line + "(" + current_position +
             "): " + message);
      warning_count++;
    }
 
  /*. . . . . . . . . . . . . . . . . . . . . . . . . . . . . .*/
 
  /** Determine if a character is ok to start an id. 
   * @param ch the character in question.
   */
  protected static boolean id_start_char(int ch)
    {
      return (ch >= 'a' &&  ch <= 'z') || (ch >= 'A' && ch <= 'Z') || 
         (ch == '_');
 
      // later need to deal with non-8-bit chars here
    }
 
  /*. . . . . . . . . . . . . . . . . . . . . . . . . . . . . .*/
 
  /** Determine if a character is ok for the middle of an id.
   * @param ch the character in question. 
   */
  protected static boolean id_char(int ch)
    {
      return id_start_char(ch) || (ch >= '0' && ch <= '9');
    }
 
  /*. . . . . . . . . . . . . . . . . . . . . . . . . . . . . .*/
 
  /** Try to look up a single character symbol, returns -1 for not found. 
   * @param ch the character in question.
   */
  protected static int find_single_char(int ch)
    {
      Integer result;
 
      result = (Integer)char_symbols.get(new Integer((char)ch));
      if (result == null) 
    return -1;
      else
    return result.intValue();
    }
 
  /*. . . . . . . . . . . . . . . . . . . . . . . . . . . . . .*/
 
  /** Handle swallowing up a comment.  Both old style C and new style C++
   *  comments are handled.
   */
  protected static void swallow_comment() throws java.io.IOException
    {
      /* next_char == '/' at this point */
 
      /* is it a traditional comment */
      if (next_char2 == '*')
    {
      /* swallow the opener */
      advance(); advance();
 
      /* swallow the comment until end of comment or EOF */
      for (;;)
        {
          /* if its EOF we have an error */
          if (next_char == EOF_CHAR)
        {
          emit_error("Specification file ends inside a comment");
          return;
        }
 
          /* if we can see the closer we are done */
          if (next_char == '*' && next_char2 == '/')
        {
          advance();
          advance();
          return;
        }
 
          /* otherwise swallow char and move on */
          advance();
        }
    }
 
      /* is its a new style comment */
      if (next_char2 == '/')
    {
      /* swallow the opener */
      advance(); advance();
 
      /* swallow to '\n', '\f', or EOF */ 
      while (next_char != '\n' && next_char != '\f' && next_char!=EOF_CHAR)
        advance();
 
      return;
 
    }
 
      /* shouldn't get here, but... if we get here we have an error */
      emit_error("Malformed comment in specification -- ignored");
      advance();
    }
 
  /*. . . . . . . . . . . . . . . . . . . . . . . . . . . . . .*/
 
  /** Swallow up a code string.  Code strings begin with "{:" and include
      all characters up to the first occurrence of ":}" (there is no way to 
      include ":}" inside a code string).  The routine returns an str_token
      object suitable for return by the scanner.
   */
  protected static token do_code_string() throws java.io.IOException
    {
      StringBuffer result = new StringBuffer();
 
      /* at this point we have lookahead of "{:" -- swallow that */
      advance(); advance();
 
      /* save chars until we see ":}" */
      while (!(next_char == ':' && next_char2 == '}'))
    {
      /* if we have run off the end issue a message and break out of loop */
      if (next_char == EOF_CHAR)
        {
          emit_error("Specification file ends inside a code string");
          break;
        }
 
      /* otherwise record the char and move on */
      result.append(new Character((char)next_char));
      advance();
    }
 
      /* advance past the closer and build a return token */
      advance(); advance();
      return new str_token(sym.CODE_STRING, result.toString());
    }
 
  /*. . . . . . . . . . . . . . . . . . . . . . . . . . . . . .*/
 
  /** Process an identifier.  Identifiers begin with a letter, underscore,
   *  or dollar sign, which is followed by zero or more letters, numbers,
   *  underscores or dollar signs.  This routine returns an str_token suitable
   *  for return by the scanner.
   */
  protected static token do_id() throws java.io.IOException
    {
      StringBuffer result = new StringBuffer();
      String       result_str;
      Integer      keyword_num;
      char         buffer[] = new char[1];
 
      /* next_char holds first character of id */
      buffer[0] = (char)next_char;
      result.append(buffer,0,1);
      advance();
 
      /* collect up characters while they fit in id */ 
      while(id_char(next_char))
    {
          buffer[0] = (char)next_char;
      result.append(buffer,0,1);
      advance();
    }
 
      /* extract a string and try to look it up as a keyword */
      result_str = result.toString();
      keyword_num = (Integer)keywords.get(result_str);
 
      /* if we found something, return that keyword */
      if (keyword_num != null)
    return new token(keyword_num.intValue());
 
      /* otherwise build and return an id token with an attached string */
      return new str_token(sym.ID, result_str);
    }
 
  /*. . . . . . . . . . . . . . . . . . . . . . . . . . . . . .*/
 
  /** Return one token.  This is the main external interface to the scanner.
   *  It consumes sufficient characters to determine the next input token
   *  and returns it.  To help with debugging, this routine actually calls
   *  real_next_token() which does the work.  If you need to debug the 
   *  parser, this can be changed to call debug_next_token() which prints
   *  a debugging message before returning the token.
   */
  public static token next_token() throws java.io.IOException
    {
      return real_next_token();
    }
 
  /*. . . . . . . . . . . . . . . . . . . . . . . . . . . . . .*/
 
  /** Debugging version of next_token().  This routine calls the real scanning
   *  routine, prints a message on System.out indicating what the token is,
   *  then returns it.
   */
  public static token debug_next_token() throws java.io.IOException
    {
      token result = real_next_token();
      System.out.println("# next_token() => " + result.sym);
      return result;
    }
 
  /*. . . . . . . . . . . . . . . . . . . . . . . . . . . . . .*/
 
  /** The actual routine to return one token.  This is normally called from
   *  next_token(), but for debugging purposes can be called indirectly from
   *  debug_next_token(). 
   */
  protected static token real_next_token() throws java.io.IOException
    {
      int sym_num;
 
      for (;;)
    {
      /* look for white space */
      if (next_char == ' ' || next_char == '\t' || next_char == '\n' ||
          next_char == '\f' ||  next_char == '\r')
        {
          /* advance past it and try the next character */
          advance();
          continue;
        }
 
      /* look for a single character symbol */
      sym_num = find_single_char(next_char);
      if (sym_num != -1)
        {
          /* found one -- advance past it and return a token for it */
          advance();
          return new token(sym_num);
        }
 
      /* look for : or ::= */
      if (next_char == ':')
        {
          /* if we don't have a second ':' return COLON */
          if (next_char2 != ':') 
        {
          advance();
          return new token(sym.COLON);
        }
 
          /* move forward and look for the '=' */
          advance();
          if (next_char2 == '=') 
        {
          advance(); advance();
          return new token(sym.COLON_COLON_EQUALS);
        }
          else
        {
          /* return just the colon (already consumed) */
          return new token(sym.COLON);
        }
        }
 
      /* look for a comment */
      if (next_char == '/' && (next_char2 == '*' || next_char2 == '/'))
        {
          /* swallow then continue the scan */
          swallow_comment();
          continue;
        }
 
      /* look for start of code string */
      if (next_char == '{' && next_char2 == ':')
        return do_code_string();
 
      /* look for an id or keyword */
      if (id_start_char(next_char)) return do_id();
 
      /* look for EOF */
      if (next_char == EOF_CHAR) return new token(sym.EOF);
 
      /* if we get here, we have an unrecognized character */
      emit_warn("Unrecognized character '" + 
        new Character((char)next_char) + "'(" + next_char + 
        ") -- ignored");
 
      /* advance past it */
      advance();
    }
    }
 
  /*-----------------------------------------------------------*/
};