Shell.java 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637
  1. package kawa;
  2. import gnu.mapping.*;
  3. import gnu.expr.*;
  4. import java.io.*;
  5. import gnu.lists.*;
  6. import gnu.bytecode.ArrayClassLoader;
  7. import gnu.bytecode.ZipLoader;
  8. import gnu.kawa.format.AbstractFormat;
  9. import gnu.kawa.io.BinaryInPort;
  10. import gnu.kawa.io.FilePath;
  11. import gnu.kawa.io.InPort;
  12. import gnu.kawa.io.NBufferedInputStream;
  13. import gnu.kawa.io.OutPort;
  14. import gnu.kawa.io.Path;
  15. import gnu.kawa.io.TtyInPort;
  16. import gnu.kawa.util.ExitCalled;
  17. import gnu.kawa.util.Signals;
  18. import gnu.text.Lexer;
  19. import gnu.text.SourceMessages;
  20. import gnu.text.SyntaxException;
  21. import java.net.URL;
  22. /** Utility functions (static methods) for kawa.repl.
  23. * It also contains methods for file loading (source and compiled).
  24. */
  25. public class Shell
  26. {
  27. /* #ifdef JAVA2 */
  28. public static ThreadLocal currentLoadPath = new ThreadLocal();
  29. /* #else */
  30. // public static Location currentLoadPath =
  31. // Location.make(null, "load-path");
  32. /* #endif */
  33. private static Class[] noClasses = { };
  34. private static Class[] boolClasses = { Boolean.TYPE };
  35. private static Class[] lispPushClasses = { OutPort.class, Character.TYPE, Boolean.TYPE };
  36. private static Class[] xmlPrinterClasses = {Consumer.class, Object.class };
  37. private static Class[] httpPrinterClasses = {OutPort.class };
  38. private static Object consumerArg = "(consumer)";
  39. /** A table of names of known output formats.
  40. * For each entry, the first Object is the format name.
  41. * The next entries are a class name, the name of a static method in that
  42. * class, and the parameter types (as a Class[] suitable for getMethod).
  43. * The remain values are arguments (passed to invoke), except that if an
  44. * argument is the spacial value consumerArg, it is replaced by the
  45. * destination Consumer. */
  46. static Object[][] formats = {
  47. { "scheme", "gnu.kawa.functions.DisplayFormat",
  48. "getSchemeFormat", boolClasses,
  49. Boolean.FALSE },
  50. { "readable-scheme", "gnu.kawa.functions.DisplayFormat",
  51. "getSchemeFormat", boolClasses,
  52. Boolean.TRUE },
  53. { "elisp", "gnu.kawa.functions.DisplayFormat",
  54. "getEmacsLispFormat", boolClasses,
  55. Boolean.FALSE },
  56. { "readable-elisp", "gnu.kawa.functions.DisplayFormat",
  57. "getEmacsLispFormat", boolClasses,
  58. Boolean.TRUE },
  59. { "clisp", "gnu.kawa.functions.DisplayFormat",
  60. "getCommonLispFormat", boolClasses,
  61. Boolean.FALSE },
  62. { "readable-clisp", "gnu.kawa.functions.DisplayFormat",
  63. "getCommonLispFormat", boolClasses,
  64. Boolean.TRUE },
  65. { "commonlisp", "gnu.kawa.functions.DisplayFormat",
  66. "getCommonLispFormat", boolClasses,
  67. Boolean.FALSE },
  68. { "readable-commonlisp", "gnu.kawa.functions.DisplayFormat",
  69. "getCommonLispFormat", boolClasses,
  70. Boolean.TRUE },
  71. { "xml", "gnu.xml.XMLPrinter",
  72. "make", xmlPrinterClasses,
  73. consumerArg, null },
  74. { "html", "gnu.xml.XMLPrinter",
  75. "make", xmlPrinterClasses,
  76. consumerArg, "html" },
  77. { "xhtml", "gnu.xml.XMLPrinter",
  78. "make", xmlPrinterClasses,
  79. consumerArg, "xhtml" },
  80. { "cgi", "gnu.kawa.xml.HttpPrinter",
  81. "make", httpPrinterClasses,
  82. consumerArg },
  83. { "ignore", "gnu.lists.VoidConsumer",
  84. "make", new Class[] { Consumer.class },
  85. consumerArg },
  86. { null }
  87. };
  88. public static String defaultFormatName;
  89. public static Object[] defaultFormatInfo;
  90. public static java.lang.reflect.Method defaultFormatMethod;
  91. /** Specify the default output format.
  92. * @param name The name of the format, as an entry in the formats table.
  93. */
  94. public static void setDefaultFormat(String name)
  95. {
  96. name = name.intern();
  97. defaultFormatName = name;
  98. for (int i = 0; ; i++)
  99. {
  100. Object[] info = formats[i];
  101. Object iname = info[0];
  102. if (iname == null)
  103. {
  104. System.err.println ("kawa: unknown output format '"+name+"'");
  105. System.exit (-1);
  106. }
  107. else if (iname == name)
  108. {
  109. defaultFormatInfo = info;
  110. try
  111. {
  112. Class formatClass = Class.forName((String) info[1]);
  113. defaultFormatMethod
  114. = formatClass.getMethod((String) info[2], (Class[]) info[3]);
  115. }
  116. catch (Throwable ex)
  117. {
  118. System.err.println("kawa: caught "+ex+" while looking for format '"+name+"'");
  119. System.exit (-1);
  120. }
  121. break;
  122. }
  123. }
  124. if (! defaultFormatInfo[1].equals("gnu.lists.VoidConsumer"))
  125. ModuleBody.setMainPrintValues(true);
  126. }
  127. /** Return a Consumer that formats using the appropriate format.
  128. * The format is chosen depending on specified defaults.
  129. * @param out The output where formatted output is sent to.
  130. */
  131. public static Consumer getOutputConsumer(OutPort out)
  132. {
  133. Object[] info = defaultFormatInfo;
  134. if (out == null)
  135. return VoidConsumer.getInstance();
  136. else if (info == null)
  137. return Language.getDefaultLanguage().getOutputConsumer(out);
  138. try
  139. {
  140. Object args[] = new Object[info.length - 4];
  141. System.arraycopy(info, 4, args, 0, args.length);
  142. boolean useConsumer = args[0] == consumerArg;
  143. for (int i = args.length; --i >= 0; ) {
  144. if (args[i] == consumerArg)
  145. args[i] = out;
  146. }
  147. Object format = defaultFormatMethod.invoke(null, args);
  148. if (format instanceof AbstractFormat)
  149. return ((AbstractFormat) format).makeConsumer(out);
  150. else
  151. return (Consumer) format;
  152. }
  153. catch (Exception ex)
  154. {
  155. throw new RuntimeException("cannot get output-format '"
  156. + defaultFormatName + "' - caught " + ex);
  157. }
  158. }
  159. public static boolean run (Language language, Environment env)
  160. {
  161. InPort inp = InPort.inDefault ();
  162. SourceMessages messages = new SourceMessages();
  163. OutPort perr;
  164. if (inp instanceof TtyInPort) // Interactive?
  165. {
  166. ((TtyInPort)inp).setPrompter(defaultPrompter);
  167. perr = OutPort.errDefault();
  168. }
  169. else
  170. perr = null; // Non-interactive.
  171. Throwable ex = run(language, env, inp, OutPort.outDefault(),
  172. perr, messages);
  173. if (ex == null)
  174. return true;
  175. printError(ex, messages, OutPort.errDefault());
  176. return false;
  177. }
  178. public static Throwable run (Language language, Environment env,
  179. InPort inp, OutPort pout, OutPort perr,
  180. SourceMessages messages)
  181. {
  182. Consumer out = getOutputConsumer(pout);
  183. return run(language, env, inp, out, perr, null, messages);
  184. }
  185. public static boolean run (Language language, Environment env,
  186. InPort inp, Consumer out, OutPort perr,
  187. URL url)
  188. {
  189. SourceMessages messages = new SourceMessages();
  190. Throwable ex = run(language, env, inp, out, perr, url, messages);
  191. if (ex != null)
  192. printError(ex, messages, perr);
  193. return ex == null;
  194. }
  195. public static Throwable run (Language language, Environment env,
  196. InPort inp, Consumer out, OutPort perr,
  197. java.net.URL url, SourceMessages messages)
  198. {
  199. Language saveLanguage = Language.setSaveCurrent(language);
  200. Lexer lexer = language.getLexer(inp, messages);
  201. boolean interactive = inp instanceof TtyInPort;
  202. lexer.setInteractive(interactive);
  203. CallContext ctx = CallContext.getInstance();
  204. Consumer saveConsumer = null;
  205. if (out != null)
  206. {
  207. saveConsumer = ctx.consumer;
  208. ctx.consumer = out;
  209. }
  210. try
  211. {
  212. Thread thread = Thread.currentThread();
  213. ClassLoader parentLoader = thread.getContextClassLoader();
  214. // Create a "session" ClassLoader. Use this for remembering classes
  215. // created in one command (Compilation) through further command,
  216. // while still allowing the classes to be replaced and collected.
  217. if (!(parentLoader instanceof ArrayClassLoader))
  218. thread.setContextClassLoader(new ArrayClassLoader(parentLoader));
  219. }
  220. catch (SecurityException ex)
  221. {
  222. // Nothing - we'll just lose some minor functionality.
  223. }
  224. java.lang.reflect.Method parserMethod = getJLineParserMethod(inp);
  225. Environment saveEnv = Environment.setSaveCurrent(env);
  226. try
  227. {
  228. SigIntHandler sigIntHandler = null;
  229. if (interactive) {
  230. sigIntHandler = new SigIntHandler();
  231. ((TtyInPort) inp).sigIntHandler = sigIntHandler;
  232. }
  233. for (;;)
  234. {
  235. Object oldIntHandler = null;
  236. int opts = Language.PARSE_FOR_EVAL|Language.PARSE_ONE_LINE|Language.PARSE_INTERACTIVE_MODULE;
  237. try
  238. {
  239. Compilation comp;
  240. if (interactive)
  241. oldIntHandler =
  242. Signals.register("INT",
  243. ((TtyInPort) inp).sigIntHandler);
  244. if (parserMethod != null) {
  245. try {
  246. comp = (Compilation) parserMethod.invoke(null, language, lexer);
  247. } catch (java.lang.reflect.InvocationTargetException ex) {
  248. throw ex.getTargetException();
  249. }
  250. } else {
  251. for (;;) {
  252. try {
  253. comp = language.parse(lexer, opts, null);
  254. break;
  255. } catch (TtyInPort.MoreInputNeeded ex) {
  256. }
  257. }
  258. }
  259. boolean sawError;
  260. if (interactive) {
  261. sawError = messages.checkErrors(perr, Compilation.maxErrors());
  262. perr.flush();
  263. }
  264. else if (messages.seenErrors())
  265. throw new SyntaxException(messages);
  266. else
  267. sawError = false;
  268. if (comp == null) // ??? end-of-file
  269. break;
  270. if (sawError) {
  271. comp.lexical.pop(comp.mainLambda);
  272. continue;
  273. }
  274. if (! ModuleExp.evalModule(env, ctx, comp, url, perr))
  275. throw new SyntaxException(messages);
  276. if (out instanceof Writer)
  277. ((Writer) out).flush();
  278. if (inp.eofSeen())
  279. break;
  280. }
  281. catch (ThreadDeath e)
  282. {
  283. if (! interactive)
  284. throw e;
  285. else if (sigIntHandler == null || sigIntHandler.trace == null)
  286. e.printStackTrace(perr);
  287. else
  288. sigIntHandler.trace.printStackTrace(perr);
  289. Thread.interrupted();
  290. }
  291. catch (Error e)
  292. {
  293. throw e;
  294. }
  295. catch (Throwable e)
  296. {
  297. if (! interactive)
  298. return e;
  299. printError(e, messages, perr);
  300. }
  301. finally
  302. {
  303. if (oldIntHandler != null)
  304. Signals.unregister("INT", oldIntHandler);
  305. }
  306. }
  307. }
  308. finally
  309. {
  310. Environment.restoreCurrent(saveEnv);
  311. if (out != null)
  312. ctx.consumer = saveConsumer;
  313. Language.restoreCurrent(saveLanguage);
  314. }
  315. return null;
  316. }
  317. static java.lang.reflect.Method getJLineParserMethod(InPort in) {
  318. Class cls = in.getClass();
  319. try {
  320. if (cls.getName().equals("gnu.kawa.io.JLineInPort")) {
  321. cls = Class.forName("gnu.kawa.io.JLineInPort$KawaParsedLine");
  322. return cls.getDeclaredMethod("parse",
  323. Language.class, Lexer.class);
  324. }
  325. } catch (Throwable ex) {
  326. }
  327. return null;
  328. }
  329. public static void printError (Throwable ex, SourceMessages messages,
  330. OutPort perr)
  331. {
  332. if (ex instanceof WrongArguments)
  333. {
  334. WrongArguments e = (WrongArguments) ex;
  335. messages.printAll(perr, Compilation.maxErrors());
  336. if (e.usage != null)
  337. perr.println("usage: "+e.usage);
  338. e.printStackTrace(perr);
  339. }
  340. /*
  341. else if (ex instanceof java.io.IOException)
  342. {
  343. messages.printAll(perr, Compilation.maxErrors());
  344. String msg = new SourceError(inp, 'e', "").toString();
  345. msg = msg.substring(0, msg.length() - 2);
  346. perr.println(msg + " (or later): caught IOException");
  347. ex.printStackTrace(perr);
  348. }
  349. */
  350. else
  351. {
  352. SyntaxException se;
  353. if (ex instanceof SyntaxException
  354. && (se = (SyntaxException) ex).getMessages() == messages)
  355. {
  356. se.printAll(perr, Compilation.maxErrors());
  357. se.clear();
  358. }
  359. else
  360. {
  361. messages.printAll(perr, Compilation.maxErrors());
  362. ex.printStackTrace(perr);
  363. }
  364. }
  365. perr.flush();
  366. }
  367. public final static CompiledModule checkCompiledZip (InputStream fs, Path path, Environment env, Language language)
  368. throws IOException
  369. {
  370. try
  371. {
  372. fs.mark(5);
  373. boolean isZip = (fs.read() == 'P' && fs.read() == 'K'
  374. && fs.read() == '\003' && fs.read() == '\004');
  375. fs.reset();
  376. if (! isZip)
  377. return null;
  378. }
  379. catch (IOException ex)
  380. {
  381. return null;
  382. }
  383. fs.close ();
  384. Environment orig_env = Environment.getCurrent();
  385. String name = path.toString();
  386. try
  387. {
  388. if (env != orig_env)
  389. Environment.setCurrent(env);
  390. File zfile = path.toFile();
  391. if (zfile == null)
  392. throw new RuntimeException ("load: "+name+" - not a file path");
  393. if (!zfile.exists ())
  394. throw new RuntimeException ("load: "+name+" - not found");
  395. if (!zfile.canRead ())
  396. throw new RuntimeException ("load: "+name+" - not readable");
  397. ZipLoader loader = new ZipLoader (name);
  398. Class clas = loader.loadAllClasses();
  399. return CompiledModule.make(clas, language);
  400. }
  401. catch (java.io.IOException ex)
  402. {
  403. throw new WrappedException ("load: "+name+" - "+ex.toString (), ex);
  404. }
  405. finally
  406. {
  407. if (env != orig_env)
  408. Environment.setCurrent(orig_env);
  409. }
  410. }
  411. static InPort openFile(InputStream fs, Path path) throws IOException {
  412. Object conv = OutPort.charEncoding.get(null);
  413. InPort src;
  414. if (conv == null || conv == Boolean.TRUE)
  415. return BinaryInPort.openHeuristicFile(fs, path);
  416. else
  417. return InPort.openFile(fs, path, conv);
  418. }
  419. /** Run a named source file, compiled .zip, or class.
  420. * We try in order if {@code fname} names a compiled zip file,
  421. * or names some other file (in which case it is assumed to be source),
  422. * or is the name of a class in the classpath.
  423. * @param lineByLine Should we read and evaluate a source file line-by-line
  424. * (i.e. read and evaluate each line before reading the next one),
  425. * or should be read and compile the whole file as a module before
  426. * running it? Only used when parsing a source file.
  427. * @param skipLines If reading a source file, the number of initial
  428. * lines to skip before beginning parsing.
  429. * @return True on success, false on failure.
  430. */
  431. public static boolean runFileOrClass (String fname,
  432. boolean lineByLine, int skipLines)
  433. {
  434. Language language = Language.getDefaultLanguage();
  435. try
  436. {
  437. Path path;
  438. InputStream fs;
  439. if (fname.equals("-"))
  440. {
  441. path = Path.valueOf("/dev/stdin");
  442. fs = System.in;
  443. }
  444. else
  445. {
  446. path = Path.valueOf(fname);
  447. fs = path.openInputStream();
  448. }
  449. try
  450. {
  451. Environment env = Environment.getCurrent();
  452. return runFile(fs, path, env, lineByLine, skipLines);
  453. }
  454. catch (Error e)
  455. {
  456. throw e;
  457. }
  458. catch (Throwable e)
  459. {
  460. e.printStackTrace(System.err);
  461. return false;
  462. }
  463. }
  464. catch (Error e)
  465. {
  466. throw e;
  467. }
  468. catch (Throwable e)
  469. {
  470. Class clas;
  471. try
  472. {
  473. clas = Class.forName(fname);
  474. }
  475. catch (Exception ex)
  476. {
  477. System.err.println("Cannot read file "+e.getMessage());
  478. return false;
  479. }
  480. try
  481. {
  482. runClass(clas, Environment.getCurrent());
  483. return true;
  484. }
  485. catch (Error ex)
  486. {
  487. throw ex;
  488. }
  489. catch (Throwable ex)
  490. {
  491. //ExitCalled.check(e);
  492. ex.printStackTrace();
  493. return false;
  494. }
  495. }
  496. }
  497. public static void runClass(Class clas, Environment env) throws Throwable {
  498. CompiledModule cmodule = CompiledModule.make(clas, Language.getDefaultLanguage());
  499. cmodule.evalModule(env, OutPort.outDefault());
  500. }
  501. public static final boolean runFile(InputStream fs, Path path,
  502. Environment env,
  503. boolean lineByLine, int skipLines)
  504. throws Throwable {
  505. if (! (fs instanceof BufferedInputStream)
  506. && ! (fs instanceof NBufferedInputStream))
  507. fs = new NBufferedInputStream(fs);
  508. Language language = Language.getDefaultLanguage();
  509. Path savePath = (Path) currentLoadPath.get();
  510. try {
  511. currentLoadPath.set(path);
  512. CompiledModule cmodule = checkCompiledZip(fs, path, env, language);
  513. if (cmodule == null) {
  514. InPort src = openFile(fs, path);
  515. while (--skipLines >= 0)
  516. src.skipRestOfLine();
  517. try {
  518. SourceMessages messages = new SourceMessages();
  519. URL url = path.toURL();
  520. OutPort perr = OutPort.errDefault();
  521. if (lineByLine) {
  522. boolean print = ModuleBody.getMainPrintValues();
  523. Consumer out
  524. = (print ? getOutputConsumer(OutPort.outDefault())
  525. : new VoidConsumer());
  526. Throwable ex
  527. = run(language, env, src, out, perr, url, messages);
  528. if (ex instanceof SyntaxException
  529. && ((SyntaxException) ex).getMessages() == messages) {
  530. messages.printAll(perr, Compilation.maxErrors());
  531. perr.flush();
  532. return false;
  533. }
  534. if (ex != null)
  535. throw ex;
  536. } else {
  537. cmodule = compileSource(src, env, url, language,
  538. messages, perr);
  539. if (cmodule == null)
  540. return false;
  541. }
  542. } finally {
  543. src.close();
  544. }
  545. }
  546. if (cmodule != null)
  547. cmodule.evalModule(env, OutPort.outDefault());
  548. } finally {
  549. currentLoadPath.set(savePath);
  550. }
  551. return true;
  552. }
  553. /** Parse and compile a module from the given port.
  554. * Return null on error, which gets reported to {@code messages}.
  555. */
  556. static CompiledModule compileSource(InPort port, Environment env, URL url,
  557. Language language,
  558. SourceMessages messages, OutPort perr)
  559. throws SyntaxException, IOException {
  560. ModuleManager manager = ModuleManager.getInstance();
  561. ModuleInfo minfo = manager.findWithSourcePath(port.getName());
  562. Lexer lexer = language.getLexer(port, messages);
  563. try {
  564. Compilation comp
  565. = language.parse(lexer, Language.PARSE_IMMEDIATE, minfo);
  566. CallContext ctx = CallContext.getInstance();
  567. //ctx.values = Values.noArgs;
  568. Object inst = ModuleExp.evalModule1(env, comp, url, null);
  569. messages.printAll(perr, Compilation.maxErrors());
  570. perr.flush();
  571. if (inst == null || messages.seenErrors())
  572. return null;
  573. return new CompiledModule(comp.getModule(), inst, language);
  574. }
  575. catch (Error ex) {
  576. throw ex;
  577. }
  578. catch (Throwable ex) {
  579. if (! (ex instanceof SyntaxException)
  580. || ((SyntaxException) ex).getMessages() != messages) {
  581. lexer.error('e', "unexpected exception while compiling: "+ex);
  582. messages.printAll(perr, Compilation.maxErrors());
  583. ex.printStackTrace(perr);
  584. }
  585. else
  586. messages.printAll(perr, Compilation.maxErrors());
  587. return null;
  588. }
  589. }
  590. public static final Procedure1 defaultPrompter = new Prompter();
  591. static class Prompter extends Procedure1 {
  592. public Object apply1 (Object arg) {
  593. return ((TtyInPort) arg).defaultPrompt();
  594. }
  595. }
  596. static class SigIntHandler implements Runnable {
  597. public Thread thread;
  598. public Error trace;
  599. public SigIntHandler(Thread thread) { this.thread = thread; }
  600. public SigIntHandler() { this.thread = Thread.currentThread(); }
  601. public void run() {
  602. Error ex = new Error("user interrupt of "+thread);
  603. ex.setStackTrace(thread.getStackTrace());
  604. this.trace = ex;
  605. thread.stop();
  606. }
  607. };
  608. }