001: /*
002: * ShellScriptFormatter.java
003: *
004: * Copyright (C) 1998-2002 Peter Graves
005: * $Id: ShellScriptFormatter.java,v 1.1.1.1 2002/09/24 16:09:11 piso Exp $
006: *
007: * This program is free software; you can redistribute it and/or
008: * modify it under the terms of the GNU General Public License
009: * as published by the Free Software Foundation; either version 2
010: * of the License, or (at your option) any later version.
011: *
012: * This program is distributed in the hope that it will be useful,
013: * but WITHOUT ANY WARRANTY; without even the implied warranty of
014: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
015: * GNU General Public License for more details.
016: *
017: * You should have received a copy of the GNU General Public License
018: * along with this program; if not, write to the Free Software
019: * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
020: */
021:
022: package org.armedbear.j;
023:
024: public final class ShellScriptFormatter extends Formatter {
025: private static final int STATE_BACKQUOTE = STATE_LAST + 1;
026: private static final int STATE_EXPANSION = STATE_LAST + 2;
027: private static final int STATE_HERE_DOCUMENT = STATE_LAST + 3;
028: private static final int STATE_ECHO = STATE_LAST + 4;
029:
030: private static final int SHELLSCRIPT_FORMAT_TEXT = 0;
031: private static final int SHELLSCRIPT_FORMAT_COMMENT = 2;
032:
033: private static final int SHELLSCRIPT_FORMAT_STRING = 4;
034: private static final int SHELLSCRIPT_FORMAT_KEYWORD = 5;
035: private static final int SHELLSCRIPT_FORMAT_FUNCTION = 6;
036: private static final int SHELLSCRIPT_FORMAT_OPERATOR = 7;
037: private static final int SHELLSCRIPT_FORMAT_BRACE = 8;
038: private static final int SHELLSCRIPT_FORMAT_NUMBER = 9;
039:
040: private static StringSet keywords;
041:
042: private FastStringBuffer sb = new FastStringBuffer();
043: private int tokStart;
044: private String token;
045:
046: private String endOfText;
047:
048: public ShellScriptFormatter(Buffer buffer) {
049: this .buffer = buffer;
050: }
051:
052: private void endToken(int state) {
053: if (sb.length() > 0) {
054: int format = -1;
055: switch (state) {
056: case STATE_NEUTRAL:
057: break;
058: case STATE_QUOTE:
059: case STATE_SINGLEQUOTE:
060: case STATE_BACKQUOTE:
061: case STATE_HERE_DOCUMENT:
062: case STATE_ECHO:
063: format = SHELLSCRIPT_FORMAT_STRING;
064: break;
065: case STATE_IDENTIFIER:
066: break;
067: case STATE_COMMENT:
068: format = SHELLSCRIPT_FORMAT_COMMENT;
069: break;
070: case STATE_OPERATOR:
071: format = SHELLSCRIPT_FORMAT_OPERATOR;
072: break;
073: case STATE_BRACE:
074: format = SHELLSCRIPT_FORMAT_BRACE;
075: break;
076: case STATE_NUMBER:
077: case STATE_HEXNUMBER:
078: format = SHELLSCRIPT_FORMAT_NUMBER;
079: break;
080: }
081: token = sb.toString();
082: addSegment(token, format);
083: tokStart += token.length();
084: sb.setLength(0);
085: }
086: }
087:
088: private void parseLine(String text, int state) {
089: if (Editor.tabsAreVisible())
090: text = Utilities
091: .makeTabsVisible(text, buffer.getTabWidth());
092: else
093: text = Utilities.detab(text, buffer.getTabWidth());
094: clearSegmentList();
095: int braceCount = 0;
096: sb.setLength(0);
097: int i = 0;
098: tokStart = 0;
099: if (state == STATE_HERE_DOCUMENT) {
100: if (text.startsWith(endOfText))
101: state = STATE_NEUTRAL;
102: else {
103: sb.append(text);
104: endToken(state);
105: return;
106: }
107: }
108: int limit = text.length();
109: char c;
110: // Skip whitespace at start of line.
111: while (i < limit) {
112: c = text.charAt(i);
113: if (Character.isWhitespace(c)) {
114: sb.append(c);
115: ++i;
116: } else {
117: endToken(state);
118: break;
119: }
120: }
121: while (i < limit) {
122: c = text.charAt(i);
123: if (state == STATE_QUOTE) {
124: if (c == '"') {
125: sb.append(c);
126: endToken(state);
127: state = STATE_NEUTRAL;
128: } else {
129: sb.append(c);
130: if (c == '\\' && i < limit - 1) {
131: // Escape char.
132: sb.append(text.charAt(++i));
133: }
134: }
135: ++i;
136: continue;
137: }
138: if (state == STATE_SINGLEQUOTE) {
139: if (c == '\'') {
140: sb.append(c);
141: endToken(state);
142: state = STATE_NEUTRAL;
143: } else {
144: sb.append(c);
145: }
146: ++i;
147: continue;
148: }
149: if (state == STATE_BACKQUOTE) {
150: if (c == '`') {
151: sb.append(c);
152: endToken(state);
153: state = STATE_NEUTRAL;
154: } else {
155: sb.append(c);
156: }
157: ++i;
158: continue;
159: }
160: // Reaching here, we're not in a quoted string.
161: if (c == '"') {
162: endToken(state);
163: sb.append(c);
164: state = STATE_QUOTE;
165: ++i;
166: continue;
167: }
168: if (c == '\'') {
169: endToken(state);
170: sb.append(c);
171: state = STATE_SINGLEQUOTE;
172: ++i;
173: continue;
174: }
175: if (c == '`') {
176: endToken(state);
177: sb.append(c);
178: state = STATE_BACKQUOTE;
179: ++i;
180: continue;
181: }
182: if (state == STATE_ECHO) {
183: if (c == '\\' && i < limit - 1) {
184: // Escape.
185: sb.append(c);
186: ++i;
187: sb.append(text.charAt(i));
188: ++i;
189: continue;
190: }
191: // Look for terminating ';'.
192: if (c == ';') {
193: endToken(state);
194: sb.append(c);
195: state = STATE_NEUTRAL;
196: } else
197: sb.append(c);
198: ++i;
199: continue;
200: }
201: if (state == STATE_EXPANSION) {
202: if (c == '{') {
203: ++braceCount;
204: sb.append(c);
205: ++i;
206: continue;
207: }
208: if (c == '}') {
209: --braceCount;
210: if (braceCount == 0) {
211: sb.append(c);
212: endToken(state);
213: state = STATE_NEUTRAL;
214: ++i;
215: continue;
216: } else {
217: sb.append(c);
218: ++i;
219: continue;
220: }
221: }
222: if (braceCount == 0) {
223: if (!buffer.mode.isIdentifierPart(c)) {
224: endToken(state);
225: sb.append(c);
226: state = STATE_NEUTRAL;
227: ++i;
228: continue;
229: }
230: }
231: sb.append(c);
232: ++i;
233: continue;
234: }
235: if (c == '$') {
236: endToken(state);
237: sb.append(c);
238: state = STATE_EXPANSION;
239: ++i;
240: continue;
241: }
242: if (c == '#') {
243: endToken(state);
244: state = STATE_COMMENT;
245: sb.append(text.substring(i));
246: endToken(state);
247: return;
248: }
249: if (state == STATE_IDENTIFIER) {
250: if (buffer.mode.isIdentifierPart(c))
251: sb.append(c);
252: else {
253: endToken(state);
254: if (token.equals("echo"))
255: state = STATE_ECHO;
256: else
257: state = STATE_NEUTRAL;
258: sb.append(c);
259: }
260: ++i;
261: continue;
262: }
263: if (state == STATE_NUMBER) {
264: if (Character.isDigit(c))
265: sb.append(c);
266: else {
267: endToken(state);
268: sb.append(c);
269: if (buffer.mode.isIdentifierStart(c))
270: state = STATE_IDENTIFIER;
271: else
272: state = STATE_NEUTRAL;
273: }
274: ++i;
275: continue;
276: }
277: if (state == STATE_NEUTRAL) {
278: if (buffer.mode.isIdentifierStart(c)) {
279: endToken(state);
280: sb.append(c);
281: state = STATE_IDENTIFIER;
282: } else if (Character.isDigit(c)) {
283: endToken(state);
284: sb.append(c);
285: state = STATE_NUMBER;
286: } else
287: // Still neutral...
288: sb.append(c);
289: }
290: ++i;
291: }
292: endToken(state);
293: }
294:
295: public LineSegmentList formatLine(Line line) {
296: if (line == null) {
297: clearSegmentList();
298: addSegment("", SHELLSCRIPT_FORMAT_TEXT);
299: return segmentList;
300: }
301: parseLine(line.getText(), line.flags());
302: for (int i = 0; i < segmentList.size(); i++) {
303: LineSegment segment = segmentList.getSegment(i);
304: if (segment.getFormat() > 0)
305: continue;
306: String s = segment.getText();
307: if (isKeyword(s))
308: segment.setFormat(SHELLSCRIPT_FORMAT_KEYWORD);
309: else
310: segment.setFormat(SHELLSCRIPT_FORMAT_TEXT);
311: }
312: return segmentList;
313: }
314:
315: public boolean parseBuffer() {
316: int state = STATE_NEUTRAL;
317: Line line = buffer.getFirstLine();
318: boolean changed = false;
319: char quoteChar = '\0';
320: while (line != null) {
321: int oldflags = line.flags();
322: if (state == STATE_HERE_DOCUMENT) {
323: if (line.getText().equals(endOfText))
324: state = STATE_NEUTRAL;
325: }
326: if (state != oldflags) {
327: line.setFlags(state);
328: changed = true;
329: }
330: if (state == STATE_HERE_DOCUMENT) {
331: line = line.next();
332: continue;
333: }
334: final int limit = line.length();
335: for (int i = 0; i < limit; i++) {
336: char c = line.charAt(i);
337: if (c == '\\' && i < limit - 1) {
338: // Escape.
339: ++i;
340: continue;
341: }
342: if (state == STATE_QUOTE) {
343: if (c == '"')
344: state = STATE_NEUTRAL;
345: continue;
346: }
347: if (state == STATE_SINGLEQUOTE) {
348: if (c == '\'')
349: state = STATE_NEUTRAL;
350: continue;
351: }
352: // Not in comment or quoted string.
353: if (c == '<' && i < limit - 2) {
354: if (line.charAt(i + 1) == '<') {
355: endOfText = line.substring(i + 2).trim();
356: if (endOfText.startsWith("-"))
357: endOfText = endOfText.substring(1);
358: int length = endOfText.length();
359: if (length > 2) {
360: if (endOfText.charAt(0) == '"'
361: && endOfText.charAt(length - 1) == '"') {
362: // Removed enclosing double quotes.
363: endOfText = endOfText.substring(1,
364: length - 1);
365: } else if (endOfText.charAt(0) == '\''
366: && endOfText.charAt(length - 1) == '\'') {
367: // Removed enclosing single quotes.
368: endOfText = endOfText.substring(1,
369: length - 1);
370: }
371: }
372: if (endOfText.length() > 0) {
373: // Make sure "<<" is not shift operator.
374: if (Character.isLetter(endOfText.charAt(0))) {
375: state = STATE_HERE_DOCUMENT;
376: break;
377: }
378: }
379: }
380: continue;
381: }
382: if (c == '#') {
383: // BUG!! Could be inside ${ ... }
384: // Single-line comment beginning.
385: // Ignore rest of line.
386: break;
387: }
388: if (c == '"')
389: state = STATE_QUOTE;
390: else if (c == '\'')
391: state = STATE_SINGLEQUOTE;
392: }
393: line = line.next();
394: }
395: buffer.setNeedsParsing(false);
396: return changed;
397: }
398:
399: public FormatTable getFormatTable() {
400: if (formatTable == null) {
401: formatTable = new FormatTable("ShellScriptMode");
402: formatTable.addEntryFromPrefs(SHELLSCRIPT_FORMAT_TEXT,
403: "text");
404: formatTable.addEntryFromPrefs(SHELLSCRIPT_FORMAT_COMMENT,
405: "comment");
406: formatTable.addEntryFromPrefs(SHELLSCRIPT_FORMAT_STRING,
407: "string");
408: formatTable.addEntryFromPrefs(SHELLSCRIPT_FORMAT_KEYWORD,
409: "keyword");
410: formatTable.addEntryFromPrefs(SHELLSCRIPT_FORMAT_FUNCTION,
411: "function");
412: formatTable.addEntryFromPrefs(SHELLSCRIPT_FORMAT_OPERATOR,
413: "operator");
414: formatTable.addEntryFromPrefs(SHELLSCRIPT_FORMAT_BRACE,
415: "brace");
416: formatTable.addEntryFromPrefs(SHELLSCRIPT_FORMAT_NUMBER,
417: "number");
418: }
419: return formatTable;
420: }
421: }
|