001: /*
002: * ScanCmd.java
003: *
004: * Copyright (c) 1997 Sun Microsystems, Inc.
005: *
006: * See the file "license.terms" for information on usage and
007: * redistribution of this file, and for a DISCLAIMER OF ALL
008: * WARRANTIES.
009: *
010: * RCS: @(#) $Id: ScanCmd.java,v 1.5 2006/05/22 21:23:35 mdejong Exp $
011: *
012: */
013:
014: package tcl.lang;
015:
016: import java.util.*;
017:
018: /**
019: * This class implements the built-in "scan" command in Tcl.
020: *
021: */
022:
023: class ScanCmd implements Command {
024: /**
025: * This procedure is invoked to process the "scan" Tcl command.
026: * See the user documentation for details on what it does.
027: *
028: * Each iteration of the cmdProc compares the scanArr's current index to
029: * the frmtArr's index. If the chars are equal then the indicies are
030: * incremented. If a '%' is found in the frmtArr, the formatSpecifier
031: * is parced from the frmtArr, the corresponding value is extracted from
032: * the scanArr, and that value is set in the Tcl Interp.
033: *
034: * If the chars are not equal, or the conversion fails, the boolean
035: * scanArrDone is set to true, indicating the scanArr is not to be
036: * parced and no new values are to be set. However the frmtArr is still
037: * parced because of the priority of error messages. In the C version
038: * of Tcl, bad format specifiers throw errors before incorrect argument
039: * input or other scan errors. Thus we need to parce the entire frmtArr
040: * to verify correct formating. This is dumb and inefficient but it is
041: * consistent w/ the current C-version of Tcl.
042: */
043:
044: public void cmdProc(Interp interp, TclObject argv[])
045: throws TclException {
046:
047: if (argv.length < 3) {
048: throw new TclNumArgsException(interp, 1, argv,
049: "string format ?varName varName ...?");
050: }
051: ;
052:
053: StrtoulResult strul; // Return value for parcing the scanArr when
054: // extracting integers/longs
055: StrtodResult strd; // Return value for parcing the scanArr when
056: // extracting doubles
057: char[] scanArr; // Array containing parce info
058: char[] frmtArr; // Array containing info on how to
059: // parse the scanArr
060: int scanIndex; // Index into the scan array
061: int frmtIndex; // Index into the frmt array
062: int tempIndex; // Temporary index holder
063: int argIndex; // Index into the current arg
064: int width; // Stores the user specified result width
065: int base; // Base of the integer being converted
066: int numUnMatched; // Number of fields actually set.
067: int numMatched; // Number of fields actually matched.
068: int i; // Generic variable
069: char ch; // Generic variable
070: boolean cont; // Used in loops to indicate when to stop
071: boolean scanOK; // Set to false if strtoul/strtod fails
072: boolean scanArrDone; // Set to false if strtoul/strtod fails
073: boolean widthFlag; // True is width is specified
074: boolean discardFlag; // If a "%*" is in the formatString dont
075: // write output to arg
076:
077: scanArr = argv[1].toString().toCharArray();
078: frmtArr = argv[2].toString().toCharArray();
079: width = base = numMatched = numUnMatched = 0;
080: scanIndex = frmtIndex = 0;
081: scanOK = true;
082: scanArrDone = false;
083: argIndex = 3;
084:
085: // Skip all (if any) of the white space before getting to a char
086:
087: frmtIndex = skipWhiteSpace(frmtArr, frmtIndex);
088:
089: // Search through the frmtArr. If the next char is a '%' parse the
090: // next chars and determine the type (if any) of the format specifier.
091: // If the scanArr has been fully searched, do nothing but incerment
092: // "numUnMatched". The reason to continue the frmtArr search is for
093: // consistency in output. Previously scan format errors were reported
094: // before arg input mismatch, so this maintains the same level of error
095: // checking.
096:
097: while (frmtIndex < frmtArr.length) {
098: discardFlag = widthFlag = false;
099: cont = true;
100:
101: // Parce the format array and read in the correct value from the
102: // scan array. When the correct value is retrieved, set the
103: // variable (from argv) in the interp.
104:
105: if (frmtArr[frmtIndex] == '%') {
106:
107: frmtIndex++;
108: checkOverFlow(interp, frmtArr, frmtIndex);
109:
110: // Two '%'s in a row, do nothing...
111:
112: if (frmtArr[frmtIndex] == '%') {
113: frmtIndex++;
114: scanIndex++;
115: continue;
116: }
117:
118: // Check for a discard field flag
119:
120: if (frmtArr[frmtIndex] == '*') {
121: discardFlag = true;
122: frmtIndex++;
123: checkOverFlow(interp, frmtArr, frmtIndex);
124: }
125:
126: // Check for a width field and accept the 'h', 'l', 'L'
127: // characters, but do nothing with them.
128: //
129: // Note: The order of the width specifier and the other
130: // chars is unordered, so we need to iterate until all
131: // of the specifiers are identified.
132:
133: while (cont) {
134: cont = false;
135:
136: switch (frmtArr[frmtIndex]) {
137: case 'h':
138: case 'l':
139: case 'L': {
140: // Just ignore these values
141:
142: frmtIndex++;
143: cont = true;
144: break;
145: }
146: default: {
147: if (Character.isDigit(frmtArr[frmtIndex])) {
148: strul = interp.strtoulResult;
149: Util.strtoul(new String(frmtArr),
150: frmtIndex, base, strul);
151: width = (int) strul.value;
152: frmtIndex = strul.index;
153: widthFlag = true;
154: cont = true;
155: strul = null;
156: }
157: }
158: }
159: checkOverFlow(interp, frmtArr, frmtIndex);
160: }
161:
162: // On all conversion specifiers except 'c', move the
163: // scanIndex to the next non-whitespace.
164:
165: ch = frmtArr[frmtIndex];
166: if ((ch != 'c') && (ch != '[') && !scanArrDone) {
167: scanIndex = skipWhiteSpace(scanArr, scanIndex);
168: }
169: if (scanIndex >= scanArr.length) {
170: scanArrDone = true;
171: }
172:
173: if ((scanIndex < scanArr.length) && (ch != 'c')
174: && (ch != '[')) {
175: // The width+scanIndex might be greater than
176: // the scanArr so we need to re-adjust when this
177: // happens.
178:
179: if (widthFlag
180: && (width + scanIndex > scanArr.length)) {
181: width = scanArr.length - scanIndex;
182: }
183: }
184:
185: if (scanIndex >= scanArr.length) {
186: scanArrDone = true;
187: }
188:
189: // Foreach iteration we want strul and strd to be
190: // null since we error check on this case.
191:
192: strul = null;
193: strd = null;
194:
195: switch (ch) {
196: case 'd':
197: case 'o':
198: case 'x': {
199:
200: if (!scanArrDone) {
201:
202: if (ch == 'd') {
203: base = 10;
204: } else if (ch == 'o') {
205: base = 8;
206: } else {
207: base = 16;
208: }
209:
210: // If the widthFlag is set then convert only
211: // "width" characters to an ascii representation,
212: // else read in until the end of the integer. The
213: // scanIndex is moved to the point where we stop
214: // reading in.
215:
216: strul = interp.strtoulResult;
217: if (widthFlag) {
218: Util.strtoul(new String(scanArr, 0, width
219: + scanIndex), scanIndex, base,
220: strul);
221: } else {
222: Util.strtoul(new String(scanArr),
223: scanIndex, base, strul);
224: }
225: if (strul.errno != 0) {
226: scanOK = false;
227: break;
228: }
229: scanIndex = strul.index;
230:
231: if (!discardFlag) {
232: i = (int) strul.value;
233: testAndSetVar(interp, argv, argIndex++,
234: TclInteger.newInstance(i));
235: }
236: }
237: break;
238: }
239: case 'c': {
240: if (widthFlag) {
241: errorCharFieldWidth(interp);
242: }
243: if (!discardFlag && !scanArrDone) {
244: testAndSetVar(
245: interp,
246: argv,
247: argIndex++,
248: TclInteger
249: .newInstance(scanArr[scanIndex++]));
250: }
251: break;
252: }
253: case 's': {
254: if (!scanArrDone) {
255: // If the widthFlag is set then read only "width"
256: // characters into the string, else read in until
257: // the first whitespace or endArr is found. The
258: // scanIndex is moved to the point where we stop
259: // reading in.
260:
261: tempIndex = scanIndex;
262: if (!widthFlag) {
263: width = scanArr.length;
264: }
265: for (i = 0; (scanIndex < scanArr.length)
266: && (i < width); i++) {
267: ch = scanArr[scanIndex];
268: if ((ch == ' ') || (ch == '\n')
269: || (ch == '\r') || (ch == '\t')
270: || (ch == '\f')) {
271: break;
272: }
273: scanIndex++;
274: }
275:
276: if (!discardFlag) {
277: String str = new String(scanArr, tempIndex,
278: scanIndex - tempIndex);
279: testAndSetVar(interp, argv, argIndex++,
280: TclString.newInstance(str));
281: }
282: }
283: break;
284: }
285: case 'e':
286: case 'f':
287: case 'g': {
288: if (!scanArrDone) {
289: // If the wisthFlag is set then read only "width"
290: // characters into the string, else read in until
291: // the first whitespace or endArr is found. The
292: // scanIndex is moved to the point where we stop
293: // reading in.
294:
295: if (widthFlag) {
296: strd = interp.strtodResult;
297: Util.strtod(new String(scanArr, 0, width
298: + scanIndex), scanIndex, -1, strd);
299: } else {
300: strd = interp.strtodResult;
301: Util.strtod(new String(scanArr), scanIndex,
302: -1, strd);
303: }
304: if (strd.errno != 0) {
305: scanOK = false;
306: break;
307: }
308: scanIndex = strd.index;
309:
310: if (!discardFlag) {
311: double d = strd.value;
312: testAndSetVar(interp, argv, argIndex++,
313: TclDouble.newInstance(d));
314: }
315: }
316: break;
317: }
318: case '[': {
319: boolean charMatchFound = false;
320: boolean charNotMatch = false;
321: char[] tempArr;
322: int startIndex;
323: int endIndex;
324: String unmatched = "unmatched [ in format string";
325:
326: if ((++frmtIndex) >= frmtArr.length) {
327: throw new TclException(interp, unmatched);
328: }
329:
330: if (frmtArr[frmtIndex] == '^') {
331: charNotMatch = true;
332: frmtIndex += 2;
333: } else {
334: frmtIndex++;
335: }
336: tempIndex = frmtIndex - 1;
337:
338: if (frmtIndex >= frmtArr.length) {
339: throw new TclException(interp, unmatched);
340: }
341:
342: // Extract the list of chars for matching.
343:
344: while (frmtArr[frmtIndex] != ']') {
345: if ((++frmtIndex) >= frmtArr.length) {
346: throw new TclException(interp, unmatched);
347: }
348: }
349: tempArr = new String(frmtArr, tempIndex, frmtIndex
350: - tempIndex).toCharArray();
351:
352: startIndex = scanIndex;
353: if (charNotMatch) {
354: // Format specifier contained a '^' so interate
355: // until one of the chars in tempArr is found.
356:
357: while (scanOK && !charMatchFound) {
358: if (scanIndex >= scanArr.length) {
359: scanOK = false;
360: break;
361: }
362: for (i = 0; i < tempArr.length; i++) {
363: if (tempArr[i] == scanArr[scanIndex]) {
364: charMatchFound = true;
365: break;
366: }
367: }
368: if (widthFlag
369: && ((scanIndex - startIndex) >= width)) {
370: break;
371: }
372: if (!charMatchFound) {
373: scanIndex++;
374: }
375: }
376: } else {
377: // Iterate until the char in the scanArr is not
378: // in the tempArr.
379:
380: charMatchFound = true;
381: while (scanOK && charMatchFound) {
382: if (scanIndex >= scanArr.length) {
383: scanOK = false;
384: break;
385: }
386: charMatchFound = false;
387: for (i = 0; i < tempArr.length; i++) {
388: if (tempArr[i] == scanArr[scanIndex]) {
389: charMatchFound = true;
390: break;
391: }
392: }
393: if (widthFlag
394: && (scanIndex - startIndex) >= width) {
395: break;
396: }
397: if (charMatchFound) {
398: scanIndex++;
399: }
400:
401: }
402: }
403:
404: // Indicates nothing was found.
405:
406: endIndex = scanIndex - startIndex;
407: if (endIndex <= 0) {
408: scanOK = false;
409: break;
410: }
411:
412: if (!discardFlag) {
413: String str = new String(scanArr, startIndex,
414: endIndex);
415: testAndSetVar(interp, argv, argIndex++,
416: TclString.newInstance(str));
417: }
418: break;
419: }
420: default: {
421: errorBadField(interp, ch);
422: }
423: }
424:
425: // As long as the scan was successful (scanOK), the format
426: // specifier did not contain a '*' (discardFlag), and
427: // we are not at the end of the scanArr (scanArrDone);
428: // increment the num of vars set in the interp. Otherwise
429: // increment the number of valid format specifiers.
430:
431: if (scanOK && !discardFlag && !scanArrDone) {
432: numMatched++;
433: } else if ((scanArrDone || !scanOK) && !discardFlag) {
434: numUnMatched++;
435: }
436: frmtIndex++;
437:
438: } else if (scanIndex < scanArr.length
439: && scanArr[scanIndex] == frmtArr[frmtIndex]) {
440: // No '%' was found, but the characters matched
441:
442: scanIndex++;
443: frmtIndex++;
444:
445: } else {
446: // No '%' found and the characters int frmtArr & scanArr
447: // did not match.
448:
449: frmtIndex++;
450:
451: }
452:
453: }
454:
455: // The numMatched is the return value: a count of the num of vars set.
456: // While the numUnMatched is the number of formatSpecifiers that
457: // passed the parsing stage, but did not match anything in the scanArr.
458:
459: if ((numMatched + numUnMatched) != (argv.length - 3)) {
460: errorDiffVars(interp);
461: }
462: interp.setResult(numMatched);
463:
464: }
465:
466: /**
467: * Given an array and an index into it, move the index forward
468: * until a non-whitespace char is found.
469: *
470: * @param arr - the array to search
471: * @param index - where to begin the search
472: * @return The index value where the whitespace ends.
473: */
474:
475: private int skipWhiteSpace(char[] arr, int index) {
476: boolean cont;
477: do {
478: if (index >= arr.length) {
479: return index;
480: }
481: cont = false;
482: switch (arr[index]) {
483: case '\t':
484: case '\n':
485: case '\r':
486: case '\f':
487: case ' ': {
488: cont = true;
489: index++;
490: }
491: }
492: } while (cont);
493:
494: return index;
495: }
496:
497: /**
498: * Called whenever the cmdProc wants to set an interp value.
499: * This method <ol>
500: * <li> verifies that there exisits a varName from the argv array,
501: * <li> that the variable either dosent exisit or is of type scalar
502: * <li> set the variable in interp if (1) and (2) are OK
503: * </ol>
504: *
505: * @param interp - the Tcl interpreter
506: * @param argv - the argument array
507: * @param argIndex - the current index into the argv array
508: * @param tobj - the TclObject that the varName equals
509: *
510: */
511:
512: private static void testAndSetVar(Interp interp, TclObject[] argv,
513: int argIndex, TclObject tobj) throws TclException {
514: if (argIndex < argv.length) {
515: try {
516: interp.setVar(argv[argIndex].toString(), tobj, 0);
517: } catch (TclException e) {
518: throw new TclException(interp,
519: "couldn't set variable \""
520: + argv[argIndex].toString() + "\"");
521: }
522: } else {
523: errorDiffVars(interp);
524: }
525: }
526:
527: /**
528: * Called whenever the frmtIndex in the cmdProc is changed. It verifies
529: * the the array index is still within the bounds of the array. If no
530: * throw error.
531: * @param interp - The TclInterp which called the cmdProc method .
532: * @param arr - The array to be checked.
533: * @param index - The new value for the array index.
534: */
535:
536: private static final void checkOverFlow(Interp interp, char[] arr,
537: int index) throws TclException {
538: if ((index >= arr.length) || (index < 0)) {
539: throw new TclException(interp,
540: "\"%n$\" argument index out of range");
541: }
542: }
543:
544: /**
545: * Called whenever the number of varName args do not match the number
546: * of found and valid formatSpecifiers (matched and unmatched).
547: *
548: * @param interp - The TclInterp which called the cmdProc method .
549: */
550:
551: private static final void errorDiffVars(Interp interp)
552: throws TclException {
553:
554: throw new TclException(interp,
555: "different numbers of variable names and field specifiers");
556: }
557:
558: /**
559: * Called whenever the current char in the frmtArr is erroneous
560: *
561: * @param interp - The TclInterp which called the cmdProc method .
562: * @param fieldSpecifier - The erroneous character
563: */
564:
565: private static final void errorBadField(Interp interp,
566: char fieldSpecifier) throws TclException {
567: throw new TclException(interp,
568: "bad scan conversion character \"" + fieldSpecifier
569: + "\"");
570: }
571:
572: /**
573: * Called whenever the a width field is used in a char ('c') format
574: * specifier
575: *
576: * @param interp - The TclInterp which called the cmdProc method .
577: */
578:
579: private static final void errorCharFieldWidth(Interp interp)
580: throws TclException {
581: throw new TclException(interp,
582: "field width may not be specified in %c conversion");
583: }
584: }
|