001: /*
002: * Shell.java --
003: *
004: * Implements the start up shell for Tcl.
005: *
006: * Copyright (c) 1997 Cornell University.
007: * Copyright (c) 1997 Sun Microsystems, Inc.
008: *
009: * See the file "license.terms" for information on usage and
010: * redistribution of this file, and for a DISCLAIMER OF ALL
011: * WARRANTIES.
012: *
013: * RCS: @(#) $Id: Shell.java,v 1.16 2006/04/27 02:16:13 mdejong Exp $
014: */
015:
016: package tcl.lang;
017:
018: import java.util.*;
019: import java.io.*;
020:
021: /**
022: * The Shell class is similar to the Tclsh program: you can use it to
023: * execute a Tcl script or enter Tcl command interactively at the
024: * command prompt.
025: */
026:
027: public class Shell {
028:
029: /*
030: *----------------------------------------------------------------------
031: *
032: * main --
033: *
034: * Main program for tclsh and most other Tcl-based applications.
035: *
036: * Results:
037: * None.
038: *
039: * Side effects:
040: * This procedure initializes the Tcl world and then starts
041: * interpreting commands; almost anything could happen, depending
042: * on the script being interpreted.
043: *
044: *----------------------------------------------------------------------
045: */
046:
047: public static void main(String args[]) // Array of command-line argument strings.
048: {
049: String fileName = null;
050:
051: // Create the interpreter. This will also create the built-in
052: // Tcl commands.
053:
054: Interp interp = new Interp();
055:
056: // Make command-line arguments available in the Tcl variables "argc"
057: // and "argv". If the first argument doesn't start with a "-" then
058: // strip it off and use it as the name of a script file to process.
059: // We also set the argv0 and tcl_interactive vars here.
060:
061: if ((args.length > 0) && !(args[0].startsWith("-"))) {
062: fileName = args[0];
063: }
064:
065: TclObject argv = TclList.newInstance();
066: argv.preserve();
067: try {
068: int i = 0;
069: int argc = args.length;
070: if (fileName == null) {
071: interp.setVar("argv0", "tcl.lang.Shell",
072: TCL.GLOBAL_ONLY);
073: interp.setVar("tcl_interactive", "1", TCL.GLOBAL_ONLY);
074: } else {
075: interp.setVar("argv0", fileName, TCL.GLOBAL_ONLY);
076: interp.setVar("tcl_interactive", "0", TCL.GLOBAL_ONLY);
077: i++;
078: argc--;
079: }
080: for (; i < args.length; i++) {
081: TclList.append(interp, argv, TclString
082: .newInstance(args[i]));
083: }
084: interp.setVar("argv", argv, TCL.GLOBAL_ONLY);
085: interp.setVar("argc", java.lang.Integer.toString(argc),
086: TCL.GLOBAL_ONLY);
087: } catch (TclException e) {
088: throw new TclRuntimeError("unexpected TclException: " + e);
089: } finally {
090: argv.release();
091: }
092:
093: // Normally we would do application specific initialization here.
094: // However, that feature is not currently supported.
095:
096: // If a script file was specified then just source that file
097: // and quit.
098:
099: if (fileName != null) {
100: int exitCode = 0;
101: try {
102: interp.evalFile(fileName);
103: } catch (TclException e) {
104: int code = e.getCompletionCode();
105: if (code == TCL.RETURN) {
106: code = interp.updateReturnInfo();
107: if (code != TCL.OK) {
108: System.err
109: .println("command returned bad code: "
110: + code);
111: exitCode = 2;
112: }
113: } else if (code == TCL.ERROR) {
114: System.err.println(interp.getResult().toString());
115: exitCode = 1;
116: } else {
117: System.err.println("command returned bad code: "
118: + code);
119: exitCode = 2;
120: }
121: }
122:
123: // Note that if the above interp.evalFile() returns the main
124: // thread will exit. This may bring down the VM and stop
125: // the execution of Tcl.
126: //
127: // If the script needs to handle events, it must call
128: // vwait or do something similar.
129: //
130: // Note that the script can create AWT widgets. This will
131: // start an AWT event handling thread and keep the VM up. However,
132: // the interpreter thread (the same as the main thread) would
133: // have exited and no Tcl scripts can be executed.
134:
135: interp.dispose();
136: System.exit(exitCode);
137: }
138:
139: if (fileName == null) {
140: // We are running in interactive mode. Start the ConsoleThread
141: // that loops, grabbing stdin and passing it to the interp.
142:
143: ConsoleThread consoleThread = new ConsoleThread(interp);
144: consoleThread.setDaemon(true);
145: consoleThread.start();
146:
147: // Loop forever to handle user input events in the command line.
148: // This method will loop until "exit" is called or the interp
149: // is interrupted.
150:
151: Notifier notifier = interp.getNotifier();
152:
153: try {
154: Notifier.processTclEvents(notifier);
155: } finally {
156: interp.dispose();
157: }
158: }
159: }
160: } // end class Shell
161:
162: /*
163: *----------------------------------------------------------------------
164: *
165: * ConsoleThread --
166: *
167: * This class implements the Console Thread: it is started by
168: * tcl.lang.Shell if the user gives no initial script to evaluate, or
169: * when the -console option is specified. The console thread loops
170: * forever, reading from the standard input, executing the user input
171: * and writing the result to the standard output.
172: *
173: *----------------------------------------------------------------------
174: */
175:
176: class ConsoleThread extends Thread {
177:
178: // Interpreter associated with this console thread.
179:
180: Interp interp;
181:
182: // Collect the user input in this buffer until it forms a complete Tcl
183: // command.
184:
185: StringBuffer sbuf;
186:
187: // Used to for interactive input/output
188:
189: private Channel out;
190: private Channel err;
191:
192: // set to true to get extra debug output
193: private static final boolean debug = false;
194:
195: // used to keep track of wether or not System.in.available() works
196: private static boolean sysInAvailableWorks = false;
197:
198: static {
199: try {
200: // There is no way to tell whether System.in will block AWT
201: // threads, so we assume it does block if we can use
202: // System.in.available().
203:
204: System.in.available();
205: sysInAvailableWorks = true;
206: } catch (Exception e) {
207: // If System.in.available() causes an exception -- it's probably
208: // no supported on this platform (e.g. MS Java SDK). We assume
209: // sysInAvailableWorks is false and let the user suffer ...
210: }
211:
212: // Sun's JDK 1.2 on Windows systems is screwed up, it does not
213: // echo chars to the console unless blocking IO is used.
214: // For this reason we need to use blocking IO under Windows.
215:
216: if (Util.isWindows()) {
217: sysInAvailableWorks = false;
218: }
219:
220: if (debug) {
221: System.out.println("sysInAvailableWorks = "
222: + sysInAvailableWorks);
223: }
224:
225: }
226:
227: /*
228: *----------------------------------------------------------------------
229: *
230: * ConsoleThread --
231: *
232: * Create a ConsoleThread.
233: *
234: * Results:
235: * None.
236: *
237: * Side effects:
238: * Member fields are initialized.
239: *
240: *----------------------------------------------------------------------
241: */
242:
243: ConsoleThread(Interp i) // Initial value for interp.
244: {
245: setName("ConsoleThread");
246: interp = i;
247: sbuf = new StringBuffer(100);
248:
249: out = TclIO.getStdChannel(StdChannel.STDOUT);
250: err = TclIO.getStdChannel(StdChannel.STDERR);
251: }
252:
253: /*
254: *----------------------------------------------------------------------
255: *
256: * run --
257: *
258: * Called by the JVM to start the execution of the console thread.
259: * It loops forever to handle user inputs.
260: *
261: * Results:
262: * None.
263: *
264: * Side effects:
265: * This method never returns. During its execution, some
266: * TclObjects may be locked inside the historyObjs vector.
267: * Remember to free them at "appropriate" times!
268: *
269: *----------------------------------------------------------------------
270: */
271:
272: public synchronized void run() {
273: if (debug) {
274: System.out.println("entered ConsoleThread run() method");
275: }
276:
277: put(out, "% ");
278:
279: while (true) {
280: // Loop forever to collect user inputs in a StringBuffer.
281: // When we have a complete command, then execute it and print
282: // out the results.
283: //
284: // The loop is broken under two conditions: (1) when EOF is
285: // received inside getLine(). (2) when the "exit" command is
286: // executed in the script.
287:
288: getLine();
289:
290: final String command = sbuf.toString();
291:
292: if (debug) {
293: System.out.println("got line from console");
294: System.out.println("\"" + command + "\"");
295: }
296:
297: // When interacting with the interpreter, one must
298: // be careful to never call a Tcl method from
299: // outside of the event loop thread. If we did
300: // something like just call interp.eval() it
301: // could crash the whole process because two
302: // threads might write over each other.
303:
304: // The only safe way to interact with Tcl is
305: // to create an event and add it to the thread
306: // safe event queue.
307:
308: TclEvent event = new TclEvent() {
309: public int processEvent(int flags) {
310:
311: // See if the command is a complete Tcl command
312:
313: if (Interp.commandComplete(command)) {
314: if (debug) {
315: System.out
316: .println("line was a complete command");
317: }
318:
319: boolean eval_exception = true;
320: TclObject commandObj = TclString
321: .newInstance(command);
322:
323: try {
324: commandObj.preserve();
325: interp.recordAndEval(commandObj, 0);
326: eval_exception = false;
327: } catch (TclException e) {
328: if (debug) {
329: System.out
330: .println("eval returned exceptional condition");
331: }
332:
333: int code = e.getCompletionCode();
334: switch (code) {
335: case TCL.ERROR:
336: // This really sucks. The getMessage() call on
337: // a TclException will not always return a msg.
338: // See TclException for super() problem.
339: putLine(err, interp.getResult()
340: .toString());
341: break;
342: case TCL.BREAK:
343: putLine(err,
344: "invoked \"break\" outside of a loop");
345: break;
346: case TCL.CONTINUE:
347: putLine(err,
348: "invoked \"continue\" outside of a loop");
349: break;
350: default:
351: putLine(err,
352: "command returned bad code: "
353: + code);
354: }
355: } finally {
356: commandObj.release();
357: }
358:
359: if (!eval_exception) {
360: if (debug) {
361: System.out
362: .println("eval returned normally");
363: }
364:
365: String evalResult = interp.getResult()
366: .toString();
367:
368: if (debug) {
369: System.out.println("eval result was \""
370: + evalResult + "\"");
371: }
372:
373: if (evalResult.length() > 0) {
374: putLine(out, evalResult);
375: }
376: }
377:
378: // Empty out the incoming command buffer
379: sbuf.setLength(0);
380:
381: // See if the user set a custom shell prompt for the next command
382:
383: TclObject prompt;
384:
385: try {
386: prompt = interp.getVar("tcl_prompt1",
387: TCL.GLOBAL_ONLY);
388: } catch (TclException e) {
389: prompt = null;
390: }
391: if (prompt != null) {
392: try {
393: interp.eval(prompt.toString(),
394: TCL.EVAL_GLOBAL);
395: } catch (TclException e) {
396: put(out, "% ");
397: }
398: } else {
399: put(out, "% ");
400: }
401:
402: return 1;
403: } else { // Interp.commandComplete() returned false
404:
405: if (debug) {
406: System.out
407: .println("line was not a complete command");
408: }
409:
410: // We don't have a complete command yet. Print out a level 2
411: // prompt message and wait for further inputs.
412:
413: TclObject prompt;
414:
415: try {
416: prompt = interp.getVar("tcl_prompt2",
417: TCL.GLOBAL_ONLY);
418: } catch (TclException e) {
419: prompt = null;
420: }
421: if (prompt != null) {
422: try {
423: interp.eval(prompt.toString(),
424: TCL.EVAL_GLOBAL);
425: } catch (TclException e) {
426: put(out, "");
427: }
428: } else {
429: put(out, "");
430: }
431:
432: return 1;
433: }
434: } // end processEvent method
435: }; // end TclEvent innerclass
436:
437: // Add the event to the thread safe event queue
438: interp.getNotifier().queueEvent(event, TCL.QUEUE_TAIL);
439:
440: // Tell this thread to wait until the event has been processed.
441: event.sync();
442: }
443: }
444:
445: /*
446: *----------------------------------------------------------------------
447: *
448: * getLine --
449: *
450: * Gets a new line from System.in and put it in sbuf.
451: *
452: * Result:
453: * The new line of user input, including the trailing carriage
454: * return character.
455: *
456: * Side effects:
457: * None.
458: *
459: *----------------------------------------------------------------------
460: */
461:
462: private void getLine() {
463: // On Unix platforms, System.in.read() will block the delivery of
464: // of AWT events. We must make sure System.in.available() is larger
465: // than zero before attempting to read from System.in. Since
466: // there is no asynchronous IO in Java, we must poll the System.in
467: // every 100 milliseconds.
468:
469: int availableBytes = -1;
470:
471: if (sysInAvailableWorks) {
472: try {
473: // Wait until there are inputs from System.in. On Unix,
474: // this usually means the user has pressed the return key.
475:
476: availableBytes = 0;
477:
478: while (availableBytes == 0) {
479: availableBytes = System.in.available();
480:
481: //if (debug) {
482: // System.out.println(availableBytes +
483: // " bytes can be read from System.in");
484: //}
485:
486: Thread.sleep(100);
487: }
488: } catch (InterruptedException e) {
489: System.exit(0);
490: } catch (EOFException e) {
491: System.exit(0);
492: } catch (IOException e) {
493: e.printStackTrace();
494: System.exit(0);
495: }
496: }
497:
498: // Loop until user presses return or EOF is reached.
499: char c2 = ' ';
500: char c = ' ';
501:
502: if (debug) {
503: System.out.println("now to read from System.in");
504: System.out.println("availableBytes = " + availableBytes);
505: }
506:
507: while (availableBytes != 0) {
508: try {
509: int i = System.in.read();
510:
511: if (i == -1) {
512: if (sbuf.length() == 0) {
513: System.exit(0);
514: } else {
515: return;
516: }
517: }
518:
519: c = (char) i;
520: availableBytes--;
521:
522: if (debug) {
523: System.out.print("(" + (availableBytes + 1) + ") ");
524: System.out.print("'" + c + "', ");
525: }
526:
527: // Temporary hack until Channel drivers are complete. Convert
528: // the Windows \r\n to \n.
529:
530: if (c == '\r') {
531: if (debug) {
532: System.out.println("checking windows hack");
533: }
534:
535: i = System.in.read();
536: if (i == -1) {
537: if (sbuf.length() == 0) {
538: System.exit(0);
539: } else {
540: return;
541: }
542: }
543: c2 = (char) i;
544: if (c2 == '\n') {
545: c = c2;
546: } else {
547: sbuf.append(c);
548: c = c2;
549: }
550: }
551: } catch (IOException e) {
552: // IOException shouldn't happen when reading from
553: // System.in. The only exceptional state is the EOF event,
554: // which is indicated by a return value of -1.
555:
556: e.printStackTrace();
557: System.exit(0);
558: }
559:
560: sbuf.append(c);
561:
562: //System.out.println("appending char '" + c + "' to sbuf");
563:
564: if (c == '\n') {
565: return;
566: }
567: }
568: }
569:
570: /*
571: *----------------------------------------------------------------------
572: *
573: * putLine --
574: *
575: * Prints a string into the given channel with a trailing carriage
576: * return.
577: *
578: * Results:
579: * None.
580: *
581: * Side effects:
582: * None.
583: *
584: *----------------------------------------------------------------------
585: */
586:
587: private void putLine(Channel channel, // The Channel to print to.
588: String s) // The String to print.
589: {
590: try {
591: channel.write(interp, s);
592: channel.write(interp, "\n");
593: channel.flush(interp);
594: } catch (IOException ex) {
595: System.err.println("IOException in Shell.putLine()");
596: ex.printStackTrace(System.err);
597: } catch (TclException ex) {
598: System.err.println("TclException in Shell.putLine()");
599: ex.printStackTrace(System.err);
600: }
601: }
602:
603: /*
604: *----------------------------------------------------------------------
605: *
606: * put --
607: *
608: * Prints a string into the given channel without a trailing
609: * carriage return.
610: *
611: * Results:
612: * None.
613: *
614: * Side effects:
615: * None.
616: *
617: *----------------------------------------------------------------------
618: */
619:
620: private void put(Channel channel, // The Channel to print to.
621: String s) // The String to print.
622: {
623: try {
624: channel.write(interp, s);
625: channel.flush(interp);
626: } catch (IOException ex) {
627: System.err.println("IOException in Shell.put()");
628: ex.printStackTrace(System.err);
629: } catch (TclException ex) {
630: System.err.println("TclException in Shell.put()");
631: ex.printStackTrace(System.err);
632: }
633: }
634: } // end of class ConsoleThread
|