001: // Copyright (c) 1999, 2004 Per M.A. Bothner.
002: // This is free software; for terms and warranty disclaimer see ./COPYING.
003:
004: package gnu.text;
005:
006: import java.io.*;
007:
008: /**
009: * Framework for implementing lexical scanners and parsers.
010: * @author Per Bothner
011: */
012:
013: public class Lexer extends Reader {
014: protected LineBufferedReader port;
015: protected boolean interactive;
016:
017: public Lexer(LineBufferedReader port) {
018: this .port = port;
019: }
020:
021: public Lexer(LineBufferedReader port, SourceMessages messages) {
022: this .port = port;
023: this .messages = messages;
024: }
025:
026: /** Enter a nested expression.
027: * This is used in interactive mode to control whether to continue
028: * past end of line, depending on whether the expression is incomplete.
029: * @param promptChar Used in prompt string to indicate type of nesting.
030: * @return The previous value of promptChar, to be passed to popNesting.
031: */
032: public char pushNesting(char promptChar) {
033: nesting++;
034: LineBufferedReader port = getPort();
035: char save = port.readState;
036: port.readState = promptChar;
037: return save;
038: }
039:
040: /** Exit a nested expression, reversing pushNesting
041: * @param save Saved values return by prior pushNesting
042: */
043: public void popNesting(char save) {
044: LineBufferedReader port = getPort();
045: port.readState = save;
046: nesting--;
047: }
048:
049: protected int nesting;
050:
051: public final LineBufferedReader getPort() {
052: return port;
053: }
054:
055: public void close() throws java.io.IOException {
056: port.close();
057: }
058:
059: public int read() throws java.io.IOException {
060: return port.read();
061: }
062:
063: public int read(char[] buf, int offset, int length)
064: throws java.io.IOException {
065: return port.read(buf, offset, length);
066: }
067:
068: public void unread(int ch) throws java.io.IOException {
069: if (ch >= 0)
070: port.unread();
071: }
072:
073: public int peek() throws java.io.IOException {
074: return port.peek();
075: }
076:
077: public void skip() throws java.io.IOException {
078: port.skip();
079: }
080:
081: protected void unread() throws java.io.IOException {
082: port.unread();
083: }
084:
085: protected void unread_quick() throws java.io.IOException {
086: port.unread_quick();
087: }
088:
089: /**
090: * Check if the next character matches a given character.
091: * @param ch The character to match against.
092: * @return if the character read matches
093: * On a match, the position is advanced following that character.
094: */
095: public boolean checkNext(char ch) throws java.io.IOException {
096: int r = port.read();
097: if (r == ch)
098: return true;
099: if (r >= 0)
100: port.unread_quick();
101: return false;
102: }
103:
104: protected void skip_quick() throws java.io.IOException {
105: port.skip_quick();
106: }
107:
108: SourceMessages messages = null;
109:
110: public SourceMessages getMessages() {
111: return messages;
112: }
113:
114: public void setMessages(SourceMessages messages) {
115: this .messages = messages;
116: }
117:
118: /** Returns true if any error were seen. Prints and clears the errors.
119: * @param out where to write the error message to
120: * @param max maximum number of messages to print (can be 0) */
121: public boolean checkErrors(PrintWriter out, int max) {
122: return messages != null && messages.checkErrors(out, max);
123: }
124:
125: public SourceError getErrors() {
126: return messages == null ? null : messages.getErrors();
127: }
128:
129: public boolean seenErrors() {
130: return messages != null && messages.seenErrors();
131: }
132:
133: public void clearErrors() {
134: if (messages != null)
135: messages.clearErrors();
136: }
137:
138: public void error(char severity, String filename, int line,
139: int column, String message) {
140: if (messages == null)
141: messages = new SourceMessages();
142: messages.error(severity, filename, line, column, message);
143: }
144:
145: public void error(char severity, String message) {
146: int line = port.getLineNumber();
147: int column = port.getColumnNumber();
148: error(severity, port.getName(), line + 1,
149: column >= 0 ? column + 1 : 0, message);
150: }
151:
152: public void error(String message) {
153: error('e', message);
154: }
155:
156: public void fatal(String message) throws SyntaxException {
157: error('f', message);
158: throw new SyntaxException(messages);
159: }
160:
161: public void eofError(String msg) throws SyntaxException {
162: fatal(msg);
163: }
164:
165: public void eofError(String message, int startLine, int startColumn)
166: throws SyntaxException {
167: error('f', port.getName(), startLine, startColumn, message);
168: throw new SyntaxException(messages);
169: }
170:
171: /** Read an optional signed integer.
172: * If there is no integer in the input stream, return 1.
173: * For excessively large exponents, return Integer.MIN_VALUE
174: * or Integer.MAX_VALUE.
175: */
176: public int readOptionalExponent() throws java.io.IOException {
177: int sign = read();
178: boolean overflow = false;
179: int c;
180: if (sign == '+' || sign == '-')
181: c = read();
182: else {
183: c = sign;
184: sign = 0;
185: }
186: int value;
187: if (c < 0 || (value = Character.digit((char) c, 10)) < 0) {
188: if (sign != 0)
189: error("exponent sign not followed by digit");
190: value = 1;
191: } else {
192: int max = (Integer.MAX_VALUE - 9) / 10;
193: for (;;) {
194: c = read();
195: int d = Character.digit((char) c, 10);
196: if (d < 0)
197: break;
198: if (value > max)
199: overflow = true;
200: value = 10 * value + d;
201: }
202: }
203: if (c >= 0)
204: unread(c);
205: if (sign == '-')
206: value = -value;
207: if (overflow)
208: return sign == '-' ? Integer.MIN_VALUE : Integer.MAX_VALUE;
209: return value;
210: }
211:
212: /** Read digits, up to the first non-digit or the buffer limit
213: * @return the digits seen as a non-negative long, or -1 on overflow
214: */
215: public static long readDigitsInBuffer(LineBufferedReader port,
216: int radix) {
217: long ival = 0;
218: boolean overflow = false;
219: long max_val = Long.MAX_VALUE / radix;
220: int i = port.pos;
221: if (i >= port.limit)
222: return 0;
223: for (;;) {
224: char c = port.buffer[i];
225: int dval = Character.digit(c, radix);
226: if (dval < 0)
227: break;
228: if (ival > max_val)
229: overflow = true;
230: else
231: ival = ival * radix + dval;
232: if (ival < 0)
233: overflow = true;
234: if (++i >= port.limit)
235: break;
236: }
237: port.pos = i;
238: return overflow ? -1 : ival;
239: }
240:
241: public String getName() {
242: return port.getName();
243: }
244:
245: public int getLineNumber() {
246: return port.getLineNumber();
247: }
248:
249: public int getColumnNumber() {
250: return port.getColumnNumber();
251: }
252:
253: public boolean isInteractive() {
254: return interactive;
255: }
256:
257: public void setInteractive(boolean v) {
258: interactive = v;
259: }
260:
261: /** For building tokens of various kinds. */
262: public char[] tokenBuffer = new char[100];
263:
264: /** The number of chars of tokenBuffer that are used. */
265: public int tokenBufferLength = 0;
266:
267: /** Append one character to tokenBuffer, resizing it if need be. */
268: public void tokenBufferAppend(int ch) {
269: if (ch >= 0x10000) {
270: tokenBufferAppend(((ch - 0x10000) >> 10) + 0xD800);
271: ch = (ch & 0x3FF) + 0xDC00;
272: // fall through to append low surrogate.
273: }
274: int len = tokenBufferLength;
275: char[] buffer = tokenBuffer;
276: if (len == tokenBuffer.length) {
277: tokenBuffer = new char[2 * len];
278: System.arraycopy(buffer, 0, tokenBuffer, 0, len);
279: buffer = tokenBuffer;
280: }
281: buffer[len] = (char) ch;
282: tokenBufferLength = len + 1;
283: }
284:
285: private int saveTokenBufferLength = -1;
286:
287: /** Start tentative parsing. Must be followed by a reset. */
288: public void mark() throws java.io.IOException {
289: if (saveTokenBufferLength >= 0)
290: throw new Error(
291: "internal error: recursive call to mark not allowed");
292: port.mark(Integer.MAX_VALUE);
293: saveTokenBufferLength = tokenBufferLength;
294: }
295:
296: /** Stop tentative parsing. Return to position where we called mark. */
297: public void reset() throws java.io.IOException {
298: if (saveTokenBufferLength < 0)
299: throw new Error(
300: "internal error: reset called without prior mark");
301: port.reset();
302: saveTokenBufferLength = -1;
303: }
304: }
|