terse.vm.Terp.java Source code

Java tutorial

Introduction

Here is the source code for terse.vm.Terp.java

Source

// --------------------------------------------------------------------------
// Copyright (c) 2012 Henry Strickland & Thomas Shanks
// 
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the "Software"),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:
// 
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
// OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.
// --------------------------------------------------------------------------
package terse.vm;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.io.Reader;
import java.util.Arrays;
import java.util.HashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import terse.vm.Cls.UsrMeth;
import terse.vm.Expr.Send;
import terse.vm.Ur.Blk;
import terse.vm.Ur.Buf;
import terse.vm.Ur.Dict;
import terse.vm.Ur.Undefined;
import terse.vm.Ur.Num;
import terse.vm.Ur.Obj;
import terse.vm.Ur.Str;
import terse.vm.Ur.Sys;
import terse.vm.Ur.Vec;
import terse.vm.Usr.Tmp;
import terse.vm.Usr.UsrCls;

public abstract class Terp extends Static {

    public static Pattern WORLD_P = Pattern.compile("^[a-z][a-z][a-z][0-9]{0,3}$", Pattern.CASE_INSENSITIVE);

    static Pattern TXTFILE_P = Pattern.compile("^[a-z][a-z0-9_]{1,31}\\.txt$");

    protected static String YAK_WEB_PAGE = "http://wiki.yak.net/1017";

    public static Pattern WHITE_PLUS = Pattern.compile("\\s+");

    public String worldName = "";
    public String worldFilename = "";
    boolean loadingWorldFile = false;
    public HashMap<String, Cls> clss = new HashMap<String, Cls>();
    public HashMap<String, Integer> allMethodNames = new HashMap<String, Integer>();

    public long tickCounter = Long.MAX_VALUE;
    public int expectingTerseException = 0;

    public Wrap wrap;
    public Cls tUr;
    public Cls tUrCls;
    public Cls tObj;
    public Cls tObjCls;
    public Cls tCls;
    public Cls tClsCls;
    public Cls tMetacls;
    public Cls tMetaclsCls;
    Cls tSuper;
    Cls tSuperCls;
    Cls tMeth;
    Cls tMethCls;
    Cls tJavaMeth;
    Cls tJavaMethCls;
    Cls tUsrMeth;
    Cls tUsrMethCls;
    Cls tSys;
    Cls tSysCls;
    Cls tNum;
    Cls tNumCls;
    Cls tBuf;
    Cls tBufCls;
    Cls tStr;
    Cls tStrCls;
    Cls tUndefined;
    Cls tUndefinedCls;
    Cls tBlk;
    Cls tBlkCls;
    Cls tVec;
    Cls tVecCls;
    Cls tDict;
    Cls tDictCls;
    Cls tExpr;
    Cls tExprCls;
    public Cls tUsr;
    public Cls tUsrCls;
    Cls tTmp;
    Cls tTmpCls;
    public Num instTrue;
    public Num instFalse;
    public Undefined instNil;
    Ur.Super instSuper;

    Exception loadWorldException;

    static boolean tolerateNullClass = false;

    /**
     * The Interpreter.
     * 
     * @throws IOException
     */
    protected Terp(boolean pleaseLoadPrelude, String worldName) throws IOException {
        if (worldName.length() > 0) {
            if (!WORLD_P.matcher(worldName).matches()) {
                toss("Bad world name <%s>", worldName);
            }
            this.worldName = worldName;
            this.worldFilename = "w_" + worldName + ".txt";
        }

        tolerateNullClass = true;
        this.tUrCls = new Cls(null/* Metacls */, this, "UrCls", null/* Cls */);
        this.tUr = new Cls(tUrCls, this, "Ur", null/* Ur has no super */);

        this.tObjCls = new Cls(null/* Metacls */, this, "ObjCls", null/* Cls */);
        this.tObj = new Cls(tObjCls, this, "Obj", tUr);

        this.tClsCls = new Cls(null/* MetaCls */, this, "ClsCls", tObjCls);
        this.tCls = new Cls(tClsCls, this, "Cls", tObj);

        this.tMetaclsCls = new Cls(null/* MetaCls */, this, "MetaclsCls", tObjCls);
        this.tMetacls = new Cls(tMetaclsCls, this, "Metacls", tCls);

        // Patch up: All *Cls's are instances of Metacls.
        this.tUrCls.cls = this.tMetacls;
        this.tObjCls.cls = this.tMetacls;
        this.tClsCls.cls = this.tMetacls;
        this.tMetaclsCls.cls = this.tMetacls;

        // Patch up: All *Cls's ultimately derive from Cls.
        this.tUrCls.supercls = tCls;
        this.tObjCls.supercls = tCls;

        tolerateNullClass = false;

        // Super is a very special class, from Ur, not from Obj.
        this.tSuperCls = new Cls(tMetacls, this, "SuperCls", tUrCls);
        this.tSuper = new Cls(tSuperCls, this, "Super", tUr);

        this.tMethCls = new Cls(tMetacls, this, "MethCls", tObjCls);
        this.tMeth = new Cls(tMethCls, this, "Meth", tObj);
        this.tJavaMethCls = new Cls(tMetacls, this, "JavMethCls", tMethCls);
        this.tJavaMeth = new Cls(tJavaMethCls, this, "JavMeth", tMeth);
        this.tUsrMethCls = new Cls(tMetacls, this, "UsrMethCls", tMethCls);
        this.tUsrMeth = new Cls(tUsrMethCls, this, "UsrMeth", tMeth);
        this.tSysCls = new Cls(tMetacls, this, "SysCls", tObjCls);
        this.tSys = new Cls(tSysCls, this, "Sys", tObj);
        this.tNumCls = new Cls(tMetacls, this, "NumCls", tObjCls);
        this.tNum = new Cls(tNumCls, this, "Num", tObj);
        this.tBufCls = new Cls(tMetacls, this, "BufCls", tObjCls);
        this.tBuf = new Cls(tBufCls, this, "Buf", tObj);
        this.tStrCls = new Cls(tMetacls, this, "StrCls", tObjCls);
        this.tStr = new Cls(tStrCls, this, "Str", tObj);

        // UsrCls is the only concrete Java subtype of Cls,
        // and it is a little bit weird.
        this.tUsrCls = new Cls(tMetacls, this, "UsrCls", tObjCls);
        this.tUsr = new Usr.UsrCls(tUsrCls, this, "Usr", tObj);
        this.tTmpCls = new Cls(tMetacls, this, "TmpCls", tUsrCls);
        this.tTmp = new Usr.UsrCls(tTmpCls, this, "Tmp", tUsr);
        // this.tUsr.myVars = strs("p", "q"); // TODO: temp hack.
        // this.tUsr.recalculateAllVarsHereAndBelow();

        this.tUndefinedCls = new Cls(tMetacls, this, "UndefinedCls", tObjCls);
        this.tUndefined = new Cls(tUndefinedCls, this, "Undefined", tObj);
        this.tBlkCls = new Cls(tMetacls, this, "BlkCls", tObjCls);
        this.tBlk = new Cls(tBlkCls, this, "Blk", tObj);
        this.tVecCls = new Cls(tMetacls, this, "VecCls", tObjCls);
        this.tVec = new Cls(tVecCls, this, "Vec", tObj);
        this.tDictCls = new Cls(tMetacls, this, "DictCls", tObjCls);
        this.tDict = new Cls(tDictCls, this, "Dict", tObj);

        this.tExprCls = new Cls(tMetacls, this, "ExprCls", tObjCls);
        this.tExpr = new Cls(tExprCls, this, "Expr", tObj);

        this.instTrue = newNum(1);
        this.instFalse = newNum(0);
        this.instNil = new Undefined(tUndefined);
        this.instSuper = new Ur.Super(this);

        // TODO: Convert all methods to "wrap" style, and get rid of all these
        // static inits.
        Str.addBuiltinMethodsForStr(this);

        tolerateNullClass = true;
        loadingWorldFile = true;
        wrap = new Wrap();
        wrap.installClasses(this);
        wrap.installMethods(this);
        loadingWorldFile = false;
        tolerateNullClass = false;

        if (pleaseLoadPrelude) {
            loadPrelude();
        }

        if (worldName.length() > 0) {
            loadWorldException = new InitialWorldReader(worldName).loadFile(worldFilename);
            if (loadWorldException != null && !(loadWorldException instanceof FileNotFoundException)) {
                say("COULD BE A PROBLEM IN InitialWorldReader(%s)loadfile(%s): %s", worldName, worldFilename,
                        loadWorldException);
            }
        }
    }

    final static int LOG_LEN = 128;
    String[] logArray = new String[LOG_LEN];
    int logPtr = 0;

    protected void recordLog(String msg) {
        //      double t = (System.nanoTime() % (100 * 1000000000)) / 1000000000.0;
        double t = (System.currentTimeMillis() % (100 * 1000)) / 1000.0;
        logArray[logPtr] = fmt("[%.3f] %s", t, msg);
        logPtr = (logPtr + 1) % LOG_LEN;
    }

    public String[] getLog() {
        int n = 0;
        for (int i = 0; i < LOG_LEN; i++) {
            if (logArray[i] != null) {
                ++n;
            }
        }
        String[] z = new String[n];
        int k = 0;
        for (int i = 0; i < LOG_LEN; i++) {
            int j = (i + logPtr) % LOG_LEN;
            if (logArray[j] != null) {
                z[k] = logArray[j];
                ++k;
            }
        }
        for (int i = 0; i < n / 2; i++) {
            String t = z[i];
            z[i] = z[n - i - 1];
            z[n - i - 1] = t;
        }
        return z;
    }

    public static class TooManyTicks extends Error {
    }

    public void tick() {
        --tickCounter;
        if (tickCounter < 1) {
            try {
                throw new RuntimeException("Going To Throw TooManyTicks");
            } catch (RuntimeException ex) {
                ex.printStackTrace();

                StringBuffer sb = new StringBuffer(ex.toString());
                StackTraceElement[] elems = ex.getStackTrace();
                for (StackTraceElement e : elems) {
                    sb.append("\n  * ");
                    sb.append(e.toString());
                }

                say(sb.toString());
            }
            throw new TooManyTicks();
        }

    }

    public Frame newFrame(Frame prev, Ur self, Expr.MethTop top) {
        return new Frame(prev, self, top);
    }

    public class Frame extends Obj { // Important: non-static, tied to a Terp.

        // =get Frame . prev prevFrame
        Frame prev; // for listing the stack
        // =get Frame . self receiver
        Ur self;
        // =get Frame Ur[] locals localVars
        Ur[] locals;
        // =get Frame . top currentMethodExpr
        Expr.MethTop top;
        // =get Frame int level level
        int level;

        // =cls "Sys" Frame Obj
        private Frame(Frame prev, Ur self, Expr.MethTop top) {
            super(wrap.clsFrame);
            this.prev = prev;
            this.self = self;
            this.top = top;
            this.level = (prev == null) ? 0 : prev.level + 1;
            this.locals = new Ur[top.numLocals];
            for (int i = 0; i < locals.length; i++) {
                this.locals[i] = getTerp().instNil;
            }
        }

        @Override
        public String toString() {
            StringBuffer sb = new StringBuffer("FRAME{");
            sb.append(fmt("self=<%s>; ", self));
            for (int i = 0; i < locals.length; i++) {
                sb.append(fmt("\"%d\"<%s>; ", i, locals[i]));
            }
            sb.append("}");
            return sb.toString();
        }
    }

    final public Terp getTerp() {
        return this;
    }

    final public Ur nullToNil(Ur x) {
        return (x == null) ? instNil : x;
    }

    final public Num newNum(double a) {
        return new Num(this, a);
    }

    final public Str newStr(String s) {
        return new Str(this, s);
    }

    final public Tmp newTmp() {
        return new Tmp(this);
    }

    final public Dict newDict(Ur[] arr) {
        Dict z = new Dict(this);
        for (int i = 0; i < arr.length; i++) {
            if (arr[i] instanceof Vec) {
                Vec v = (Vec) arr[i];
                if (v.vec.size() == 2) {
                    z.dict.put(v.vec.get(0), v.vec.get(1));
                } else {
                    toss("To initialize assoc in Dict, expected Vec of length 2, but got <%s#%d#%s>; inside <%s>",
                            v.cls, v.vec.size(), v, arrayToString(arr));
                }
            } else {
                toss("To initialize assoc in Dict, expected Vec of length 2, but got <%s#%s>; inside <%s>",
                        arr[i].cls, arr[i], arrayToString(arr));
            }
        }
        return z;
    }

    final public Vec newVec(Ur[] a) {
        Vec z = new Vec(this);
        for (int i = 0; i < a.length; ++i) {
            z.vec.add(a[i]);
        }
        return z;
    }

    final public Vec newVec(int[] a) {
        Vec z = new Vec(this);
        for (int i = 0; i < a.length; ++i) {
            z.vec.add(newNum(a[i]));
        }
        return z;
    }

    final public Vec mkStrVec(String... strs) {
        Ur[] arr = new Ur[strs.length];
        for (int i = 0; i < strs.length; i++) {
            arr[i] = newStr(strs[i]);
        }
        return newVec(arr);
    }

    final public Vec mkSingletonStrVecVec(String... strs) {
        Ur[] arr = new Ur[strs.length];
        for (int i = 0; i < strs.length; i++) {
            arr[i] = mkStrVec(strs[i]);
        }
        return newVec(arr);
    }

    public Dict handleUrl(String url, HashMap<String, String> query) {
        say("runUrl: %s", url);
        query = (query == null) ? new HashMap<String, String>() : query;
        Ur[] queryArr = new Ur[query.size()];
        int i = 0;
        for (String k : query.keySet()) {
            String v = query.get(k);
            if (k == null)
                k = "HOW_DID_WE_GET_A_NULL_KEY";
            if (v == null)
                v = "HOW_DID_WE_GET_A_NULL_VALUE";
            Ur queryKey = newStr(k);
            Ur queryValue = newStr(v.replaceAll("\r\n", "\n"));
            queryArr[i] = new Vec(this, urs(queryKey, queryValue));
            ++i;
        }
        Dict qDict = newDict(queryArr);
        assert url.startsWith("/");
        if (url.equals("/")) {
            url = "/Top";
        }

        // To get app name, skip the initial '/', and split on dots.
        String[] word = url.substring(1).split("[.]");
        assert word.length > 0;
        String appName = word[0];

        Dict result = null;
        try {
            Cls cls = getTerp().clss.get(appName.toLowerCase());
            if (cls == null) {
                toss("Rendering class does not exist: <%s>", appName);
            }

            String urlRepr = newStr(url).repr();
            String qDictRepr = qDict.repr(); // Inefficient. TODO.
            Ur result_ur = instNil;
            int id = 0;

            Obj inst = null;
            try {
                id = Integer.parseInt(word[1]);
                if (cls instanceof Usr.UsrCls) {
                    inst = ((Usr.UsrCls) cls).cache.find(id);
                }
            } catch (Exception _) {
                // pass.
            }
            long before = tickCounter;
            long nanosBefore = System.nanoTime();
            if (inst != null) {
                result_ur = inst.eval(fmt("self handle: (%s) query: (%s)", urlRepr, qDictRepr));
            } else if (Send.understands(cls, "handle:query:")) {
                say("CLS <%s> understands handle:query: so sending to class.", cls);
                // First try sending to the class.
                result_ur = cls.eval(fmt("self handle: (%s) query: (%s)", urlRepr, qDictRepr));
            } else {
                Ur instance = cls.eval("self new");
                Usr usrInst = instance.asUsr();
                // TODO: LRU & mention() conflict with Cls.insts map.
                id = usrInst == null ? 0 : usrInst.omention(); // LRU Cache

                // Next try creating new instance, and send to it.
                result_ur = instance.asObj()
                        .eval(fmt("self handle: (%s) query: (%s)", newStr(url).repr(), qDict.repr()));
            }
            result = result_ur.asDict();
            if (result == null) {
                toss("Sending <handle:query:> to instance of <%s> did not return a Dict: <%s>", appName, result_ur);
            }
            result.dict.put(newStr("id"), newStr(Integer.toString(id)));
            long after = tickCounter;
            long nanosAfter = System.nanoTime();
            result.dict.put(newStr("ticks"), newNum(before - after));
            result.dict.put(newStr("nanos"), newNum(nanosAfter - nanosBefore));
            say("<handle:query:> used %d ticks and %.3f secs.", before - after,
                    (double) (nanosAfter - nanosBefore) / 1000000000.0);

        } catch (Exception ex) {
            ex.printStackTrace();
            StringBuffer sb = new StringBuffer(ex.toString());
            StackTraceElement[] elems = ex.getStackTrace();
            for (StackTraceElement e : elems) {
                sb.append("\n  * ");
                sb.append(e.toString());
            }
            Ur[] dict_arr = urs(new Vec(this, urs(newStr("type"), newStr("text"))),
                    new Vec(this, urs(newStr("title"), newStr(ex.toString()))),
                    new Vec(this, urs(newStr("value"), newStr(sb.toString()))));
            result = newDict(dict_arr);
        } catch (TooManyTicks err) {
            err.printStackTrace();
            String s = fmt("TOO_MANY_TICKS_IN_handleUrl <%s> qdict <%s>", url, qDict);
            Ur[] dict_arr = urs(new Vec(this, urs(newStr("type"), newStr("text"))),
                    new Vec(this, urs(newStr("title"), newStr(err.toString()))),
                    new Vec(this, urs(newStr("value"), newStr(s))));
            result = newDict(dict_arr);
        } catch (Error err) {
            err.printStackTrace();
            Ur[] dict_arr = urs(new Vec(this, urs(newStr("type"), newStr("text"))),
                    new Vec(this, urs(newStr("title"), newStr(err.toString()))),
                    new Vec(this, urs(newStr("value"), newStr(err.toString()))));
            result = newDict(dict_arr);
        }
        return result;
    }

    static Pattern INITIAL_WHITE = Pattern.compile("^\\s");

    public abstract class WorldReader {
        protected int lineNum;
        protected String world;

        public WorldReader(String world) {
            this.world = world;
        }

        abstract public void doCls(String[] words, String more);

        abstract public void doVars(String[] words, String more);

        abstract public void doMeth(String[] words, String more);

        abstract public void doEquals(String[] words, String more);

        abstract public void doInst(String[] words, String more);

        public Exception loadFile(String initFilename) {
            try {
                say("LOADING FILE <%s> for world <%s>", initFilename, worldName);
                FileInputStream fis = openFileRead(initFilename);
                loadReader(new InputStreamReader(fis));
                discoverAllMethodNames();
            } catch (IOException ex) {
                ex.printStackTrace();
                say("ERROR Loading <%s> at line %d:  %s", initFilename, lineNum, ex);
                return ex;
            } catch (RuntimeException ex) {
                ex.printStackTrace();
                say("ERROR Loading <%s> at line %d:  %s", initFilename, lineNum, ex);
                return ex;
            }
            return null;
        }

        public void loadReader(InputStreamReader isr) throws IOException {
            BufferedReader br = new BufferedReader(isr);
            loadingWorldFile = true;
            lineNum = 0;
            try {
                String line = br.readLine();
                ++lineNum;
                while (true) {
                    // Break at EOF.
                    if (line == null)
                        break;
                    // Skip blank lines.
                    String trimmed = line.trim();
                    if (trimmed.length() == 0) {
                        line = br.readLine();
                        ++lineNum;
                        continue;
                    }
                    // Skip comments.
                    if (line.charAt(0) == '#') {
                        line = br.readLine();
                        ++lineNum;
                        continue;
                    }

                    char c = line.charAt(0);
                    if (c != '(' && c != ')') {
                        if (line.charAt(0) < 'a' || line.charAt(0) > 'z') {
                            toss("loadInitFile: Should have started with some lowercase letter a-z: <%s>", line);
                        }
                    }

                    //say("COMMAND: %s", line);

                    // Store the command line in words[] and command.
                    String[] words = line.trim().split("\\s+");
                    if (words.length < 1) {
                        line = br.readLine();
                        ++lineNum;
                        continue;
                    }
                    // Recognize new timestamped format with "(".
                    if (words[0].equals("(") && words.length > 2) {
                        // Skip two words: the "("or ")", and a timestamp that
                        // follows
                        // it.
                        // words = Arrays.copyOfRange(words, 2, words.length);
                        String[] new_words = new String[words.length - 2];
                        System.arraycopy(words, 2, new_words, 0, words.length - 2);
                        words = new_words;
                    }
                    // Recognize new timestamped format ending with ")" line.
                    if (words[0].equals(")")) {
                        line = br.readLine();
                        ++lineNum;
                        continue;
                    }

                    String command = words[0];

                    // Now slurp any lines beginning with white space into more.
                    StringBuffer sb = new StringBuffer();
                    line = br.readLine();
                    ++lineNum;
                    while (line != null && (INITIAL_WHITE.matcher(line).lookingAt() || line.length() == 0)) {
                        sb.append(line.length() > 0 ? line.substring(1) : "");
                        sb.append("\n");
                        line = br.readLine();
                        ++lineNum;
                    }
                    // Keep line for next time through big loop.
                    String more = sb.toString();
                    //say("MORE: len=%s", more.length());

                    try {
                        int ca0 = command.charAt(0);
                        //say("Char At 0 == %d; cmd=%s; words=%s", ca0, command, mkStrVec(words));
                        if (command.charAt(0) == ')') {
                            // NOP.
                        } else if (command.equals("meth")) {
                            doMeth(words, more);
                        } else if (command.equals("vars")) {
                            doVars(words, more);
                        } else if (command.equals("instvars")) { // Archaic spelling.
                            doVars(words, more);
                        } else if (command.equals("inst")) {
                            doInst(words, more);
                        } else if (command.equals("cls")) {
                            doCls(words, more);
                        } else if (command.equals("class")) { // Archaic spelling.
                            doCls(words, more);
                        } else if (command.equals("equals")) {
                            doEquals(words, more);
                        } else if (command.equals("stop")) {
                            break; // For testing partial files.
                        } else {
                            toss("loadInitFile: Unknown command: <%s>", mkStrVec(words));
                        }
                    } catch (RuntimeException ex) {
                        ex.printStackTrace();
                        toss("Loading at line %d:  %s", lineNum, ex);
                    }
                }
            } finally {
                loadingWorldFile = false;
            }
        }
    }

    public class InitialWorldReader extends WorldReader {

        public InitialWorldReader(String world) {
            super(world);
        }

        @Override
        public void doCls(String[] words, String more) {
            String className = words[1];
            String superName = words[2];
            Cls clsObj = clss.get(className.toLowerCase());
            Cls supObj = clss.get(superName.toLowerCase());
            if (more.trim().length() > 0) {
                toss("Was not expecting more: <%s>");
            }
            if (supObj == null) {
                toss("loadInitFile: Cannot define subclass <%s> of <%s>: class <%s> does not exist.", className,
                        superName, superName);
            }

            if (clsObj == null && supObj != null) {
                // We can create the class.
                supObj.defineSubclass(className);
            } else {
                toss("loadInitFile: Cannot define subclass <%s> of <%s>", className, superName);
            }
        }

        @Override
        public void doVars(String[] words, String more) {
            String className = words[1];
            Cls cls = clss.get(className.toLowerCase());
            if (cls == null) {
                cls = defineOrphan(className);
            }
            cls.defVars_(more);
        }

        @Override
        public void doMeth(String[] words, String more) {
            String className = words[1];
            String methodName = words[2];

            Cls cls = clss.get(className.toLowerCase());
            if (cls == null) {
                cls = defineOrphan(className);
            }
            UsrMeth um = new UsrMeth(cls, methodName, "", "", more, null);
            cls.meths.put(methodName.toLowerCase(), um);

        }

        @Override
        public void doEquals(String[] words, String more) {
            String expected = "";
            for (int i = 1; i < words.length; i++) {
                expected += words[i] + " ";
            }
            Ur p1 = newTmp().eval(expected);
            Ur p2 = newTmp().eval(more);
            if (!p1.equals(p2)) {
                toss("loadInitFile: eq check failed: line %d: <%s> --> <%s> but <%s> --> <%s>", lineNum, expected,
                        p1, more, p2);
            }
        }

        @Override
        public void doInst(String[] words, String more) {
            String className = words[1];
            String instName = words[2];
            Matcher match_nww = Usr.NAME_WITH_WORLD.matcher(instName);
            if (match_nww.lookingAt()) {
                // Already has the world on it.
            } else {
                instName += "_" + world;
            }

            UsrCls usrCls = (UsrCls) clss.get(className.toLowerCase());
            if (usrCls == null) {
                usrCls = defineOrphan(className);
            }
            usrCls.savedInstsUnrealized.put(instName, more);
        }

    }

    void appendWorldFile(String firstLine, String[] rest) throws IOException {
        if (loadingWorldFile) {
            return; // Don't cause infinite loop!
        }
        if (worldFilename.length() == 0) {
            return; // For unit tests.
        }
        long timestamp = System.currentTimeMillis() / 1000;
        FileOutputStream fos = openFileAppend(worldFilename);
        PrintStream ps = new PrintStream(fos);
        ps.println(fmt("( %d %s", timestamp, firstLine));
        if (rest != null) {
            for (String s : rest) {
                ps.println(" " + s); // Stuff 1 space before each line.
            }
        }
        ps.println(fmt(") %d", timestamp));
        ps.flush();
        fos.flush();
        fos.close();
    }

    public UsrCls defineOrphan(String className) {
        UsrCls orphan = (UsrCls) clss.get("orphan");
        say("COULD BE A PROBLEM:  Defining Orphaned Class <%s>", className);
        return (UsrCls) orphan.defineSubclass(className);
    }

    public Ur toss(String s, Object... objects) {
        if (expectingTerseException > 0) {
            throw new TerseExpectedException(s, objects);
        } else {
            throw new TerseException(s, objects);
        }
    }

    public Ur retoss(String s, Object... objects) {
        if (expectingTerseException > 0) {
            throw new TerseExpectedRetossException(s, objects);
        } else {
            throw new TerseRetossException(s, objects);
        }
    }

    public Ur tossNotUnderstood(Cls c, String msg) {
        return toss("Message <%s> not understood by class <%s>", msg, c.cname);
    }

    public void checkTxtFileNameSyntax(String filename) {
        if (!TXTFILE_P.matcher(filename).matches()) {
            toss("Bad filename, should match <%s> : <%s>", TXTFILE_P, filename);
        }
    }

    public Num boolObj(boolean x) {
        return x ? instTrue : instFalse;
    }

    public abstract String say(String s, Object... objects);

    public abstract void loadPrelude() throws IOException;

    public abstract FileInputStream openFileRead(String filename) throws FileNotFoundException;

    public abstract FileOutputStream openFileWrite(String filename) throws FileNotFoundException;

    public abstract FileOutputStream openFileAppend(String filename) throws FileNotFoundException;

    public abstract File getFilesDir();

    public abstract boolean deleteFile(String filename);

    public abstract Vec listOfWebFiles();

    public abstract void pushToWeb(String filename, String content);

    public abstract String pullFromWeb(String filename);

    // DRAW ABSTRACTIONS
    public interface IInk {
        void setColor(int rgbDecimal);

        void setFont(String name);

        void setFontSize(int scaledPoints);

        void setThickness(int pixels);
    }

    public interface ICanv {
        void drawLine(IInk k, float x1, float y1, float x2, float y2);

        void drawRect(IInk k, float x1, float y1, float x2, float y2);

        void drawText(IInk k, float x, float y);
    }

    public static class PosixTerp extends Terp {

        protected PosixTerp(boolean loadPrelude, String imageName) throws IOException {
            super(loadPrelude, imageName);
        }

        @Override
        public String say(String s, Object... objects) {
            String msg = fmt(s, objects);
            System.err.println(msg);
            recordLog(msg);
            return msg;
        }

        @Override
        public void loadPrelude() throws IOException {
            new InitialWorldReader("pre0").loadFile("prelude.txt");
        }

        @Override
        public FileInputStream openFileRead(String filename) throws FileNotFoundException {
            return new FileInputStream(filename);
        }

        @Override
        public FileOutputStream openFileWrite(String filename) throws FileNotFoundException {
            return new FileOutputStream(filename, false);
        }

        @Override
        public FileOutputStream openFileAppend(String filename) throws FileNotFoundException {
            return new FileOutputStream(filename, true);
        }

        @Override
        public File getFilesDir() {
            return new File(".");
        }

        @Override
        public void pushToWeb(String filename, String content) {
            // import org.apache.http.impl.client.DefaultHttpClient;
            toss("Not Implemented in PosixTerp: pushToWeb");
        }

        @Override
        public String pullFromWeb(String filename) {
            // import org.apache.http.impl.client.DefaultHttpClient;
            toss("Not Implemented in PosixTerp: pullFromWeb");
            return null;
        }

        @Override
        public Vec listOfWebFiles() {
            toss("Not Implemented in PosixTerp: listOfWebFiles");
            return null;
        }

        @Override
        public boolean deleteFile(String filename) {
            File f = new File(filename);
            return f.delete();
            // TODO: some regexp.
        }
    }

    public interface Factory {
        Terp createTerp(boolean loadPrelude, String imageName) throws IOException;
    }

    void discoverAllMethodNames() {
        allMethodNames.clear();
        for (String clsName : clss.keySet()) {
            Cls c = clss.get(clsName);
            for (String methName : c.meths.keySet()) {
                int numColons = 0;
                for (int i = 0; i < methName.length(); i++) {
                    if (methName.charAt(i) == ':')
                        ++numColons;
                }
                allMethodNames.put(methName, numColons);
            }
        }
    }

    private class TerseBaseException extends RuntimeException {
        private static final long serialVersionUID = 1L;
        protected String msg;
        protected Object[] args;
        protected boolean retoss;

        public TerseBaseException(boolean retoss, String format, Object... args) {
            this.retoss = retoss;
            this.msg = "?????";
            this.args = args;

            String[] argstrs = new String[args.length];
            for (int i = 0; i < args.length; i++) {
                try {
                    argstrs[i] = fmt("<%s:%s>", args[i].getClass().getName(), args[i].toString());
                } catch (RuntimeException _) {
                    argstrs[i] = "???";
                }
            }
            this.msg = fmt(format, (Object[]) /*argstrs*/ args);
        }

        @Override
        public String toString() {
            return fmt(msg, args);
        }
    }

    private class TerseException extends TerseBaseException {
        private static final long serialVersionUID = 1L;

        public TerseException(String format, Object[] args) {
            super(false, format, args);
            say("TerseException(%s)<<%s>>", retoss ? "retoss" : "", msg);
            if (retoss) {
                breakHere("retoss");
            } else {
                breakHere("toss");
            }
        }
    }

    private class TerseRetossException extends TerseBaseException {
        private static final long serialVersionUID = 1L;

        public TerseRetossException(String format, Object[] args) {
            super(true, format, args);
            say("TerseException(%s)<<%s>>", retoss ? "retoss" : "", msg);
            if (retoss) {
                breakHere("retoss");
            } else {
                breakHere("toss");
            }
        }
    }

    private class TerseExpectedException extends TerseBaseException {
        private static final long serialVersionUID = 1L;

        public TerseExpectedException(String format, Object[] args) {
            super(false, format, args);
            say("TerseEXPECTEDException(%s)<<%s>>", retoss ? "retoss" : "", msg);
            if (retoss) {
                breakHere("expected retoss");
            } else {
                breakHere("expected toss");
            }
        }
    }

    private class TerseExpectedRetossException extends TerseBaseException {
        private static final long serialVersionUID = 1L;

        public TerseExpectedRetossException(String format, Object[] args) {
            super(true, format, args);
            say("TerseEXPECTEDException(%s)<<%s>>", retoss ? "retoss" : "", msg);
            if (retoss) {
                breakHere("expected retoss");
            } else {
                breakHere("expected toss");
            }
        }
    }
}