001: package jsint;
002:
003: import java.io.*;
004: import java.util.HashMap;
005:
006: /** This class represents a Scheme interpreter.
007: * @author Peter Norvig, Copyright 1998, peter@norvig.com, <a href="license.txt">license</a>
008: * subsequently modified by Jscheme project members
009: * licensed under zlib licence (see license.txt)
010: **/
011:
012: public class Evaluator implements java.io.Serializable {
013: /** Should REP Loop exit? **/
014: private boolean exit = false;
015:
016: public boolean setExit(boolean exit) {
017: return this .exit = exit;
018: }
019:
020: private transient InputPort input = new InputPort(System.in);
021:
022: public void setInput(InputPort ip) {
023: this .input = ip;
024: }
025:
026: public InputPort getInput() {
027: return this .input;
028: }
029:
030: private transient PrintWriter output = new PrintWriter(System.out,
031: true);
032:
033: public PrintWriter getOutput() {
034: return this .output;
035: }
036:
037: public void setOutput(PrintWriter w) {
038: this .output = w;
039: }
040:
041: private transient PrintWriter error = new PrintWriter(System.err,
042: true);
043:
044: public PrintWriter getError() {
045: return this .error;
046: }
047:
048: public void setError(PrintWriter w) {
049: this .error = w;
050: }
051:
052: /** Is execution interruptable. **/
053: public boolean INTERRUPTABLE = false;
054:
055: /** Interrupt execution on thread <tt>t</tt>. **/
056: public void interrupt(Thread t) {
057: INTERRUPTABLE = true;
058: t.interrupt();
059: }
060:
061: /** Maybe interrupt this thread of execution. **/
062: public void interruptCheck() {
063: if (INTERRUPTABLE && Thread.currentThread().interrupted()) {
064: INTERRUPTABLE = false;
065: throw new JschemeThrowable("Execution was interrupted.");
066: }
067: }
068:
069: // The default environment for doing work.
070: // We have to define and initialize this before the static block
071: // that loads the primitives.
072: public DynamicEnvironment interactionEnvironment = null;
073: // = new DynamicEnvironment();
074:
075: // The R5RS "null-environment".
076: public static DynamicEnvironment NULL_ENVIRONMENT = new DynamicEnvironment();
077:
078: // The environment at system startup. Any loaded environments are
079: // created based off of this.
080: public DynamicEnvironment INITIAL_ENVIRONMENT = null;
081:
082: public DynamicEnvironment getInteractionEnvironment() {
083: return interactionEnvironment;
084: }
085:
086: public static DynamicEnvironment getNullEnvironment() {
087: return NULL_ENVIRONMENT;
088: }
089:
090: public DynamicEnvironment getInitialEnvironment() {
091: return INITIAL_ENVIRONMENT;
092: }
093:
094: private static HashMap environmentCache = new HashMap();
095: private static DynamicEnvironment BASE_ENVIRONMENT = null;
096:
097: static {
098: NULL_ENVIRONMENT.lockDown();
099: }
100:
101: public Evaluator() {
102: Scheme.pushEvaluator(this );
103: try {
104: if (BASE_ENVIRONMENT == null) {
105: interactionEnvironment = new DynamicEnvironment();
106: jsint.Primitive.loadPrimitives();
107: BASE_ENVIRONMENT = new DynamicEnvironment(
108: this .interactionEnvironment);
109: BASE_ENVIRONMENT.lockDown();
110: } else {
111: interactionEnvironment = new DynamicEnvironment(
112: BASE_ENVIRONMENT);
113: }
114: INITIAL_ENVIRONMENT = new DynamicEnvironment(
115: interactionEnvironment);
116: INITIAL_ENVIRONMENT.lockDown();
117: } finally {
118: Scheme.popEvaluator();
119: }
120: }
121:
122: public Evaluator(DynamicEnvironment env) {
123: interactionEnvironment = new DynamicEnvironment(env);
124: INITIAL_ENVIRONMENT = new DynamicEnvironment(
125: interactionEnvironment);
126: INITIAL_ENVIRONMENT.lockDown();
127: }
128:
129: public void runJscheme() {
130: if (exit)
131: return;
132: showVersion();
133: readEvalWriteLoop("> ");
134: }
135:
136: /**
137: * If true, results of REPL evaluations are named (e.g. $3) for
138: * future reference.
139: */
140: private boolean nameResults = true;
141:
142: public void enableNamedResults(boolean enabled) {
143: nameResults = enabled;
144: }
145:
146: /** Prompt, read, eval, and write the result.
147: * Also sets up a catch for any RuntimeExceptions encountered.
148: * Returns true on EOF, false if (exit) was evaluated. **/
149: public boolean readEvalWriteLoop(String prompt) {
150: int count = 0;
151: while (!exit) {
152: try {
153: output.print(prompt);
154: output.flush();
155: Object x = input.read();
156: if (x == InputPort.EOF)
157: return true; // EOF
158: Object result = eval(x);
159: if (nameResults) {
160: String name = "$" + (++count);
161: output.print(name + " = ");
162: interactionEnvironment.setValue(
163: Symbol.intern(name), result);
164: }
165: U.write(result, output, true);
166: output.write("\n");
167: output.println();
168: output.flush();
169: } catch (Throwable e) {
170: e.printStackTrace(error);
171: error.flush();
172: }
173: }
174: return false; // (exit) called.
175: }
176:
177: /**
178: * load the current object (file or class) into a new Evaluator
179: * and return the resulting Evaluator's DynamicEnvironment.
180: */
181: public DynamicEnvironment loadEnvironment(Object x) {
182:
183: Object cacheKey = x;
184: Object cached = environmentCache.get(cacheKey);
185:
186: // for filenames, use the CanonicalFile name as the hashkey, for
187: // others use the object itself
188: try {
189: if (x instanceof String) {
190: cacheKey = (new java.io.File((String) x))
191: .getCanonicalFile().toString().intern();
192: cached = environmentCache.get(cacheKey);
193: }
194: } catch (Exception e) {
195: }
196:
197: if (cached != null)
198: return (DynamicEnvironment) cached;
199: else {
200: Evaluator evaluator = new Evaluator();
201: DynamicEnvironment env = evaluator.interactionEnvironment;
202: Scheme.pushEvaluator(evaluator);
203: evaluator.load(x);
204: env = evaluator.interactionEnvironment; // the evaluator might change its interactionEnvironment
205: Scheme.popEvaluator();
206: env.lockDown();
207: environmentCache.put(cacheKey, env);
208: return (env);
209: }
210: }
211:
212: public Boolean environmentImport(Object x, Object prefix) {
213: return environmentImport(x, prefix, false, null);
214: }
215:
216: public Boolean environmentImport(Object x, Object prefix,
217: boolean macrosFlag, jsint.Symbol[] procnames) {
218: synchronized (interactionEnvironment) {
219: DynamicEnvironment env = loadEnvironment(x);
220: if (prefix instanceof String)
221: if (macrosFlag && (((String) prefix).length() > 0)) {
222: E
223: .error("(environment-import): macros cannot have a prefix"
224: + prefix);
225: return Boolean.FALSE;
226: } else {
227: interactionEnvironment.importBindings(env,
228: (String) prefix, macrosFlag, procnames);
229: return Boolean.TRUE;
230: }
231: else if ((prefix == U.MISSING)
232: || ((prefix instanceof Boolean) && ((Boolean) prefix) == Boolean.FALSE)) {
233: interactionEnvironment.importBindings(env, null,
234: macrosFlag, procnames);
235: return Boolean.TRUE;
236: } else {
237: E
238: .error("(environment-import): prefix is not string or #f: "
239: + prefix);
240: return Boolean.FALSE;
241: }
242: }
243: }
244:
245: public Boolean languageImport(Object x) {
246: synchronized (interactionEnvironment) {
247: DynamicEnvironment env = loadEnvironment(x);
248: interactionEnvironment.importBindings(env, null, true);
249: return Boolean.TRUE;
250: }
251: }
252:
253: /** Eval all the expressions in a file. Calls load(InputPort). **/
254: public Object load(Object fileName) {
255: String name = fileName.toString();
256: InputPort iport = Scheme.open(name);
257: if (iport == null)
258: return E.warn("(load) can't open \"" + fileName + "\"");
259: else
260: return load(iport);
261: }
262:
263: /** Eval all the expressions coming from an InputPort, putting them
264: in the interactionEnvironment. The interactionEnvironment might
265: have been rebound to a module environment, but we don't care
266: about that. Returns TRUE on EOF, FALSE if (exit) was evaluated.
267: **/
268: public Object load(InputPort in) {
269: while (!exit) {
270: try {
271: Object x = in.read();
272: if (x == InputPort.EOF)
273: return U.TRUE;
274: else
275: evalToplevel(x, interactionEnvironment);
276: } catch (Exception e) {
277: E.warn("Error during load (lineno "
278: + in.getLineNumber() + "): ", e);
279: e.printStackTrace(error);
280: }
281: }
282: return U.FALSE; // (exit)
283: }
284:
285: /** evalToplevel evaluates each element of a BEGIN. This is so
286: macros can be defined and then used. Also toplevel macros can
287: expand into begin.
288: **/
289: public Object evalToplevel(Object x, DynamicEnvironment env) {
290: if (U.isPair(x)) {
291: Object mx = Macro.expand((Pair) x);
292: if (x != mx)
293: return evalToplevel(mx, env);
294: else if (U.first(x) == Symbol.BEGIN) {
295: Object xs = U.rest(x);
296: Object result = null;
297: while (U.isPair(xs)) {
298: result = eval(U.first(xs), env);
299: xs = U.rest(xs);
300: }
301: return result;
302: } else
303: return eval(x, env);
304: } else
305: return eval(x, env);
306: }
307:
308: //////////////// Evaluation ////////////////
309:
310: /** Evaluate an s-expression in the global environment. **/
311: public Object eval(Object x) {
312: return eval(x, interactionEnvironment);
313: }
314:
315: /** Evaluate an s-expression in a lexical environment. First analyze
316: * it.
317: **/
318: public Object eval(Object x, Object env) {
319: DynamicEnvironment dynamicEnv = ((env == U.MISSING) ? interactionEnvironment
320: : (DynamicEnvironment) env);
321: Object analyzedCode = analyze(x, dynamicEnv,
322: LexicalEnvironment.NULLENV);
323: return execute(analyzedCode, LexicalEnvironment.NULLENV);
324: }
325:
326: /** Analyze (or preprocess or precompile) an expression into "code".
327: * The code returned is either a Symbol, a LocalVariable, a Closure, or
328: * an array whose first element is either one of (quote if or begin set!)
329: * or is code evaluating to a procedure. **/
330: public Object analyze(Object x, DynamicEnvironment dynamicEnv,
331: LexicalEnvironment lexenv) {
332: if (x instanceof Symbol) {
333: LocalVariable localvar = lexenv.lookup((Symbol) x); // Convert symbol to variable
334: if (localvar == null)
335: return dynamicEnv.intern((Symbol) x);
336: else
337: return localvar;
338: } else if (!U.isPair(x))
339: return new Object[] { Symbol.QUOTE, x }; // Convert 42 to #(quote 42)
340: else {
341: Object f = U.first(x), xm;
342: int len = ((Pair) x).length();
343: if (Symbol.LAMBDA == f && len >= 3) { // Handle (lambda ...)
344: Object args = U.second(x);
345: LexicalEnvironment lexenv2 = new LexicalEnvironment(
346: args, null, lexenv);
347: return new Closure(args, analyze(Scheme.toBody(U.rest(U
348: .rest(x))), dynamicEnv, lexenv2), lexenv);
349: } else if (Symbol.MACRO == f && len >= 3) { // Handle (macro ...)
350: Object args = U.second(x);
351: LexicalEnvironment lexenv2 = new LexicalEnvironment(
352: args, null, lexenv);
353: return new Macro(args, analyze(Scheme.toBody(U.rest(U
354: .rest(x))), dynamicEnv, lexenv2), lexenv);
355: } else if (2 == len && Symbol.BEGIN == f)
356: return analyze(U.second(x), dynamicEnv, lexenv); // Convert (begin x) to x
357: else if (x != (xm = Macro.expand((Pair) x)))
358: return analyze(xm, dynamicEnv, lexenv); // Analyze macroexpansion
359: else if (Symbol.OR == f && len == 1)
360: return new Object[] { Symbol.QUOTE, U.FALSE };// (or) => '#f
361: else if (Symbol.IF == f && len == 3) // (if a b) => (if a b #f)
362: return analyze(U.append(U.list(x, U.list(U.FALSE))),
363: dynamicEnv, lexenv);
364: else if (Symbol.QUOTE == f && len == 2)
365: return new Object[] { Symbol.QUOTE, U.second(x) };
366: else {
367: // Complain if there is was syntax error.
368: checkLength(f, len, x);
369: // Compile into a special form, or an application.
370: Object[] xv = U.listToVector(x); // Convert list to vector
371: if (!isSpecial(f))
372: xv[0] = analyze(xv[0], dynamicEnv, lexenv);
373: for (int i = 1; i < xv.length; i++) {
374: xv[i] = analyze(xv[i], dynamicEnv, lexenv); // Convert each element
375: }
376: return xv;
377: }
378: }
379: }
380:
381: /** Evaluate analyzed code in a lexical environment. Don't pass this
382: * a raw s-expression; you need to analyze the s-expression
383: * first.
384: **/
385: public Object execute(Object x, LexicalEnvironment lexenv) {
386: // The purpose of the while loop is to allow tail recursion.
387: // The idea is that in a tail recursive position, we do "x = ..."
388: // and loop, rather than doing "return execute(...)".
389: do {
390: if (!(x instanceof Object[])) {
391: if (x instanceof DynamicVariable)
392: return ((DynamicVariable) x).getDynamicValue();
393: else if (x instanceof LocalVariable)
394: return lexenv.get((LocalVariable) x);
395: else
396: return ((Closure) x).copy(lexenv);
397: } else {
398: Object[] xv = (Object[]) x;
399: Object f = xv[0];
400: if (f == Symbol.QUOTE)
401: return xv[1];
402: else if (f == Symbol.IF)
403: x = (U.to_bool(execute(xv[1], lexenv))) ? xv[2]
404: : xv[3];
405: else if (f == Symbol.BEGIN)
406: x = executeButLast(xv, lexenv);
407: else if (f == Symbol.OR) {
408: int xvlm1 = xv.length - 1;
409: for (int i = 1; i < xvlm1; i++) {
410: Object result = execute(xv[i], lexenv);
411: if (U.toBool(result) != U.FALSE)
412: return result;
413: }
414: // Replace x with the final one
415: x = xv[xvlm1];
416: } else if (f == Symbol.SET
417: && xv[1] instanceof DynamicVariable)
418: return ((DynamicVariable) xv[1])
419: .setDynamicValue(execute(xv[2], lexenv));
420: else if (f == Symbol.SET
421: && xv[1] instanceof LocalVariable)
422: return lexenv.set((LocalVariable) xv[1], execute(
423: xv[2], lexenv));
424: else { // Function application.
425: if (INTERRUPTABLE)
426: interruptCheck();
427: try {
428: f = executef(f, lexenv);
429: if (f instanceof Closure) {
430: Closure c = (Closure) f;
431: x = c.body;
432: lexenv = new LexicalEnvironment(c.parms, c
433: .makeArgArray(xv, this , lexenv),
434: c.lexenv);
435: } else if (f instanceof Generic) {
436: Generic g = ((Generic) f);
437: Object[] args = g.makeArgArray(xv, this ,
438: lexenv);
439: Closure c = g.findMethod(args);
440: x = c.body;
441: lexenv = new LexicalEnvironment(c.parms,
442: args, c.lexenv);
443: } else { // OTHER PROCEDURE CALL
444: Procedure p = U.toProc(f);
445: return p.apply(p.makeArgArray(xv, this ,
446: lexenv));
447: }
448: // Continuations, and (messageless) JschemeThrowables pass through
449: } catch (ContinuationException ce) {
450: throw ce;
451: } catch (RuntimeException e) {
452: if ((e instanceof JschemeThrowable)
453: && (e.getMessage() == null))
454: throw e;
455: else
456: throw new BacktraceException(e, xv, lexenv);
457: }
458: }
459: }
460: } while (true);
461: }
462:
463: /** Specialized execute() where x is the function of an application. **/
464: private Object executef(Object x, LexicalEnvironment lexenv) {
465: if (x instanceof DynamicVariable)
466: return ((DynamicVariable) x).getDynamicValue();
467: else if (x instanceof LocalVariable)
468: return lexenv.get((LocalVariable) x);
469: else if (x instanceof Closure)
470: return ((Closure) x).copy(lexenv);
471: else
472: return execute(x, lexenv);
473: }
474:
475: private static boolean isSpecial(Object f) {
476: return f == Symbol.SET || f == Symbol.IF || f == Symbol.BEGIN
477: || f == Symbol.OR || f == Symbol.QUOTE;
478: }
479:
480: private static void checkLength(Object f, int len, Object x) {
481: if ((f == Symbol.LAMBDA && len < 3)
482: || (f == Symbol.MACRO && len < 3)
483: || (f == Symbol.SET && len != 3)
484: || (f == Symbol.IF && len != 4)
485: || (f == Symbol.BEGIN && len <= 2)
486: || (f == Symbol.OR && len < 2)
487: || (f == Symbol.QUOTE && len != 2))
488: E.warn("wrong number of arguments for syntax:", x);
489: }
490:
491: /** Evaluate **/
492: private Object executeButLast(Object[] xv, LexicalEnvironment lexenv) {
493: for (int i = 1; i < xv.length - 1; i++) {
494: execute(xv[i], lexenv);
495: }
496: return xv[xv.length - 1];
497: }
498:
499: private void showVersion() {
500: error.println(getVersion());
501: }
502:
503: private String getVersion() {
504: String defaultmsg = "Jscheme http://jscheme.sourceforge.net";
505: String name = "jsint/version.txt";
506: ClassLoader loader = Import.getClassLoader();
507: InputStream stream = (loader == null) ? ClassLoader
508: .getSystemResourceAsStream(name) : loader
509: .getResourceAsStream(name);
510: if (stream == null)
511: return defaultmsg;
512: else {
513: BufferedReader r = new BufferedReader(
514: new InputStreamReader(stream));
515: try {
516: return r.readLine();
517: } catch (IOException e) {
518: return defaultmsg;
519: }
520: }
521: }
522: }
|