001: package jsint;
002:
003: import java.io.*;
004: import java.net.URL;
005: import java.util.LinkedList;
006: import java.util.NoSuchElementException;
007:
008: /** This class represents a Scheme interpreter.
009: * @author Peter Norvig, Copyright 1998, peter@norvig.com, <a href="license.txt">license</a>
010: * subsequently modified by Jscheme project members
011: * licensed under zlib licence (see license.txt)
012: **/
013:
014: public class Scheme {
015: /** The main() arguments **/
016: public static String[] ARGS;
017:
018: public static boolean isInterruptable() {
019: return currentEvaluator().INTERRUPTABLE;
020: }
021:
022: /** Maybe interrupt this thread of execution. **/
023: public static void interruptCheck() {
024: currentEvaluator().interruptCheck();
025: }
026:
027: private static ThreadLocal currentEvaluatorStack = new InheritableThreadLocal() {
028: protected Object initialValue() {
029: return new LinkedList();
030: }
031:
032: protected Object childValue(Object parentValue) {
033: return new LinkedList((LinkedList) parentValue);
034: }
035: };
036:
037: private static LinkedList evaluatorStack() {
038: return (LinkedList) currentEvaluatorStack.get();
039: }
040:
041: public static Evaluator currentEvaluator() {
042: try {
043: return (Evaluator) evaluatorStack().getFirst();
044: } catch (NoSuchElementException e) {
045: /** Use the first evaluator. **/
046: pushEvaluator(jscheme.JS.js.getEvaluator());
047: return (Evaluator) evaluatorStack().getFirst();
048: }
049: }
050:
051: public static void pushEvaluator(Evaluator i) {
052: evaluatorStack().addFirst(i);
053:
054: }
055:
056: public static Evaluator popEvaluator() {
057: return (Evaluator) evaluatorStack().removeFirst();
058: }
059:
060: public static DynamicEnvironment getInteractionEnvironment() {
061: return currentEvaluator().interactionEnvironment;
062: }
063:
064: public static DynamicEnvironment getNullEnvironment() {
065: return Evaluator.NULL_ENVIRONMENT;
066: }
067:
068: public static DynamicEnvironment getInitialEnvironment() {
069: return currentEvaluator().INITIAL_ENVIRONMENT;
070: }
071:
072: /**
073: The following steps are performed
074: <ul>
075:
076: <li> If an "init.scm" resource or file is found, it is loaded.
077: It is expected to do all the processing, for example interpreting
078: command line arguments.
079:
080: <li> Otherwise command line arguments are processed as follows:
081:
082: <ul> <li> If the argument "-main" is seen, the next argument is
083: either "none" indicating that no main will be started, or the
084: name of a Scheme procedure or Java reflector to be invoked. All
085: of the remaining arguments are passed to it as a
086: <tt>String[]</tt>.
087:
088: <li> An argument beginning with "(" is read and evaluated as a
089: Scheme expression.
090:
091: <li> Otherwise, the argument is treated as a file, resource or URL to
092: be loaded. </ul></ul>
093:
094: <p>By putting the init.scm in a .jar file with the manifest
095: entry:
096:
097: <pre> Main-Class: jsint.Scheme </pre>
098: you get scriptable jar files that can be invoked by:
099: <pre> java -jar name.jar</pre>
100:
101: This idea was suggested by David May <a
102: href=mailto:david@davudsplace.net>david@davudsplace.net</a>.
103:
104: <p>By adding command line arguments or providing your own
105: init.scm file you can override the init.scm file in the .jar to
106: tailor your application.
107: **/
108:
109: public static void main(String[] files) {
110: ARGS = files;
111: if (!loadInit())
112: defaultMain(files);
113: }
114:
115: /** The default main() behavior. **/
116: public static void defaultMain(String[] files) {
117: String main = null;
118: String[] mainArgs = null;
119: int i = 0;
120: while (i < (files == null ? 0 : files.length)) {
121: if (files[i].startsWith("("))
122: load(new InputPort(new StringReader(files[i])));
123: else if (files[i].startsWith("-")) {
124: if (files[i].equals("-s"))
125: U.useJavaSyntax = false;
126: else if (files[i].equals("-j"))
127: U.useJavaSyntax = true;
128: else if (files[i].equals("-main")) {
129: i = i + 1;
130: main = files[i];
131: mainArgs = consumeArgs(files, i + 1);
132: break;
133: } else
134: usage(files[i]);
135: } else {
136: load(files[i]);
137: }
138: i = i + 1;
139: }
140: if (main == null)
141: runJscheme();
142: else if (!main.equals("none"))
143: // try { new jscheme.JScheme(currentEvaluator()).call(main, mainArgs); }
144: try {
145: jscheme.JS.call(main, mainArgs);
146: } catch (Throwable e) {
147: e.printStackTrace();
148: System.exit(1);
149: }
150: }
151:
152: private static void usage(String arg) {
153: E
154: .warn("Unrecognized flag: "
155: + arg
156: + "\n"
157: + "Usage: \n"
158: + " java jsint.Scheme [-s][-j] [(s-expr)] [file] ... [-main procedure arg1 ...]\n\n"
159: + "Where: \n"
160: + " -s \n"
161: + " Use normal Scheme syntax for numbers and characters.\n\n"
162: + " -j \n"
163: + " Use Javalike syntax for numbers and characters.\n\n"
164: + " (s-exp) \n"
165: + " An argument that begins with \"(\" is evaluated.\n\n"
166: + " -main procedure arg1 ...\n"
167: + " Rather than starting the normal Scheme.main, \n"
168: + " collect arg1 ... into a String[] and apply procedure to it.\n"
169: + " If procedure is none, no main is started.\n");
170: System.exit(1);
171: }
172:
173: private static String[] consumeArgs(String[] files, int i) {
174: String[] result = new String[files.length - i];
175: int j = 0;
176: while (i < files.length)
177: result[j++] = files[i++];
178: return result;
179: }
180:
181: public static void runJscheme() {
182: currentEvaluator().runJscheme();
183: }
184:
185: /** Prompt, read, eval, and write the result.
186: * Also sets up a catch for any RuntimeExceptions encountered. **/
187: public static void readEvalWriteLoop(String prompt) {
188: currentEvaluator().readEvalWriteLoop(prompt);
189: }
190:
191: public static boolean loadInit() {
192: InputPort in = open("init.scm");
193: if (in != null) {
194: load(in);
195: return true;
196: } else
197: return false;
198: }
199:
200: /** Eval all the expressions in a file. Calls load(InputPort). **/
201: public static Object load(Object fileName) {
202: String name = fileName.toString();
203: InputPort iport = open(name);
204: if (iport == null)
205: return E.warn("(load) can't open \"" + fileName + "\"");
206: else
207: return load(iport);
208: }
209:
210: /**
211: * load the current file (or class) into a new initial environment
212: * and return the resulting DynamicEnvironment.
213: */
214: public static DynamicEnvironment loadEnvironment(Object x) {
215: return currentEvaluator().loadEnvironment(x);
216: }
217:
218: public static Boolean environmentImport(Object x, Object prefix) {
219: return currentEvaluator().environmentImport(x, prefix);
220: }
221:
222: public static Boolean languageImport(Object x) {
223: return currentEvaluator().languageImport(x);
224: }
225:
226: public static InputPort open(String name) {
227: InputPort ip = null;
228: if (ip == null)
229: ip = openFile(name);
230: if (ip == null)
231: ip = openResource(name);
232: if (ip == null)
233: ip = openURL(name);
234: return ip;
235: }
236:
237: public static InputPort openURL(String url) {
238: try {
239: return new InputPort((InputStream) (new URL(url))
240: .getContent());
241: } catch (java.net.MalformedURLException e) {
242: return null;
243: } catch (IOException e) {
244: e.printStackTrace();
245: return null;
246: }
247: }
248:
249: public static InputPort openFile(String name) {
250: try {
251: return new InputPort(new FileInputStream(name));
252: } catch (java.io.IOException fnf) {
253: return null;
254: } catch (SecurityException se) {
255: return null;
256: } catch (Throwable e) {
257: e.printStackTrace(Scheme.currentEvaluator().getError());
258: return null;
259: }
260: }
261:
262: public static InputPort openResource(String name) {
263: try {
264: ClassLoader loader = Import.getClassLoader();
265: InputStream stream = (loader == null) ? ClassLoader
266: .getSystemResourceAsStream(name) : loader
267: .getResourceAsStream(name);
268: return (stream == null) ? null : new InputPort(stream);
269: } catch (Throwable e) {
270:
271: Scheme.currentEvaluator().getError().println(
272: "In openResource(" + name + "):");
273: e.printStackTrace(Scheme.currentEvaluator().getError());
274: return null;
275: }
276: }
277:
278: public static Object load(InputPort in) {
279: return currentEvaluator().load(in);
280: }
281:
282: /** evalToplevel evaluates each element of a BEGIN. This is so
283: macros can be defined and then used. Also toplevel macros can
284: expand into begin.
285: **/
286: public static Object evalToplevel(Object x, DynamicEnvironment env) {
287: return currentEvaluator().evalToplevel(x, env);
288: }
289:
290: //////////////// Evaluation ////////////////
291:
292: /** Evaluate an s-expression in the global environment. **/
293: public static Object eval(Object x) {
294: return currentEvaluator().eval(x);
295: }
296:
297: /** Evaluate an s-expression in a lexical environment. First analyze
298: * it.
299: **/
300: public static Object eval(Object x, Object env) {
301: return currentEvaluator().eval(x, env);
302: }
303:
304: /** Handle internal defines, and convert a list of exps to a single exp.
305: * Examples: (x) => (begin x), (x y) => (begin x y),
306: * ((define a 1) (+ a a)) => ((lambda (a) (begin (set! a 1) (+ a a))) #f)
307: **/
308: public static Object toBody(Object exps) {
309: Pair parts = extractDefines(Pair.EMPTY, U.toPair(exps));
310: Pair defines = ((Pair) parts.first);
311: Pair body = ((Pair) parts.rest);
312: if (U.isPair(defines)) {
313: Pair vars = Pair.EMPTY;
314: Pair sets = Pair.EMPTY;
315: Pair vals = Pair.EMPTY;
316: Pair ds = defines;
317: while (U.isPair(ds)) {
318: Pair d = ((Pair) ds.first);
319: ds = ((Pair) ds.rest);
320: vars = new Pair(U.second(d), vars);
321: sets = new Pair(U.list(Symbol.SET, d.second(), d
322: .third()), sets);
323: vals = new Pair(U.FALSE, vals);
324: }
325: Pair begin = new Pair(Symbol.BEGIN, U.append(U.list(sets
326: .reverse(), body)));
327: return new Pair(U
328: .list(Symbol.LAMBDA, vars.reverse(), begin), vals);
329: } else
330: return new Pair(Symbol.BEGIN, body);
331: }
332:
333: /** Return a Pair who's first is a list of simple defines and who's
334: rest is the remaining body.
335: **/
336: private static Pair extractDefines(Pair defines, Pair body) {
337: if (!U.isPair(body))
338: return new Pair(defines.reverse(), body);
339: else if (startsWith(body.first, Symbol.BEGIN))
340: return extractDefines(defines, (Pair) U.append(U.list(U
341: .rest(U.first(body)), body.rest)));
342: else if (startsWith(body.first, Symbol.DEFINE))
343: return extractDefines(new Pair(
344: simplifyDefine(((Pair) body.first)), defines), U
345: .toList(body.rest));
346: else
347: return new Pair(defines.reverse(), checkForDefines(body));
348: // else return new Pair(defines.reverse(), body);
349: }
350:
351: /** Check for embedded defines
352: * this checks for any improperly embedded defines
353: * and also flattens the body
354: **/
355: private static Object checkForDefines(Pair body) {
356: if (!U.isPair(body))
357: return body;
358: else if (startsWith(body.first, Symbol.BEGIN))
359: return checkForDefines((Pair) U.append(U.list(U.rest(U
360: .first(body)), body.rest)));
361: else if (startsWith(body.first, Symbol.DEFINE)) {
362: return E
363: .error("Jscheme requires all embedded defines to appear first in procedure bodies\n"
364: + "You must move "
365: + U.stringify(U.first(body)) + " up\n");
366: } else
367: return new Pair(body.first,
368: checkForDefines((Pair) body.rest));
369: }
370:
371: /** Is the first element of the list identical to the given atom? **/
372: private static boolean startsWith(Object list, Object atom) {
373: return (U.isPair(list)) && (U.first(list) == atom);
374: }
375:
376: private static Pair simplifyDefine(Pair definition) {
377: Object var = U.second(definition);
378: if (var instanceof Pair) {
379: Pair var2 = (Pair) var;
380: Object name = var2.first;
381: Object args = var2.rest;
382: Object body = U.rest(U.rest(definition));
383: return new Pair(Symbol.DEFINE, new Pair(name,
384: U
385: .list(new Pair(Symbol.LAMBDA, new Pair(
386: args, body)))));
387: } else
388: return definition;
389: }
390:
391: }
|