/* Copyright 2004-2010 David N. Welton, DedaSys LLC Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package org.hecl; import java.io.InputStream; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.EOFException; import java.io.InputStreamReader; import java.io.IOException; import java.io.PrintStream; import java.util.Enumeration; import java.util.Hashtable; import java.util.Stack; import java.util.Vector; //#if j2se import java.util.LinkedList; import java.util.List; //import jline.ArgumentCompletor; //import jline.ConsoleReader; //import jline.NullCompletor; //import jline.SimpleCompletor; //#endif /** *. * * @param clazz The class to remove the command for. */ public void removeClassCmd(Class clazz) { addClassCmd(clazz,null);} /** * Add a new class command to anInterpis the Hecl interpreter, the class responsible for * knowing what variables and commands are available. * * @author David N. Welton * @version 1.0 */ public class Interp extends Thread/*implements Runnable*/ { /** * Package name prefix of the module classes. */ public static final String MODULE_CLASS_PACKAGE = "org.hecl"; /** * Flags for the event loop. */ public static final int DONT_WAIT = 1; public static final int IDLE_EVENTS = 2; public static final int TIMER_EVENTS = 4; public static final int ALL_EVENTS = ~DONT_WAIT; /** * Some string constants used to generate names for internal events. */ public static final String ASYNCPREFIX = "async"; public static final String IDLEPREFIX = "idle"; public static final String TIMERPREFIX = "timer"; /** * The prompt for thereadEvalPrintloop. */ public final String PROMPT = "hecl> "; /** * The prompt for continued lines in thereadEvalPrintloop. */ public final String PROMPT2 = "hecl+ "; /** * AThingto indicate global reference. */ static final Thing GLOBALREFTHING = new Thing(""); /* Used to cache command lookups. */ public long cacheversion = 0; /* Do we have the Java reflection stuff? */ private static boolean javacmdpresent = false; /** * ThecommandsHashtableprovides the * mapping from the strings containing command names to the code * implementing the commands. */ protected Hashtable commands = new Hashtable(); /** * TheauxdataHashtableis a place to * store extra information about the state of the program. * */ protected Hashtable auxdata = new Hashtable(); protected Stack stack = new Stack(); protected Stack error = new Stack(); protected Vector timers = new Vector(); protected Vector asyncs = new Vector(); protected Vector idle = new Vector(); protected Hashtable waittokens = new Hashtable(); protected long idlegeneration = 0; protected boolean running = true; protected long maxblocktime = 0; // block time in milliseconds protected Vector ci = new Vector(); protected Hashtable classcmdcache = new Hashtable(); /** *procclassis used by CLDC 1.0 to store the 'Proc' * class, since you can't just do Proc.class in that version of * J2ME. * */ //#if cldc == 1.0 static Class procclass; //#endif /** *eolis the end-of-line character or characters. * */ public static final char eol[] = System.getProperty("line.separator").toCharArray(); //#if files || j2se /** *fileseparatoris the file separator, such as "/". * */ public static final String fileseparator = System.getProperty("file.separator"); public Thing currentFile = new Thing(""); //#endif /** * Creates a newInterpinstance, initializing command and * variable hashtables, a stack, and an error stack. * * @exception HeclException if an error occurs */ public Interp() throws HeclException { try { //#if cldc == 1.0 procclass = Class.forName("Proc"); //#endif /* Check and see if the "java" extension is present */ Class.forName("org.hecl.java.JavaCmd"); javacmdpresent = true; } catch (ClassNotFoundException e) { /* Ignore it - nothing changes. */ } // Set up stack frame for globals. stack.push(new Hashtable()); initInterp(); start(); } //#ifdef j2se protected String[] hashKeysToArray(Hashtable h) { return hashKeysToArray(h, ""); } protected String[] hashKeysToArray(Hashtable h, String prefix) { Vectorcmds = new Vector (); for (Enumeration e = h.keys(); e.hasMoreElements();) { cmds.add(prefix + (String)e.nextElement()); } String[] scmds = new String[cmds.size()]; cmds.copyInto(scmds); return scmds; } //#endif // /** // * The commandLinemethod implements a // * Read/Eval/Print Loop. // * // * @param in Input stream to read input from. // * @param out Output stream to print results to. // * @param err Output stream for error messages. // * // * This function never returns. // */ // public void readEvalPrint(InputStream in, PrintStream out, PrintStream err) { // String prompt = PROMPT; // StringBuffer sb = new StringBuffer(); // List completors = null; // ConsoleReader reader = null; // int oldsz = 0; // int newsz = 0; // while(true) { // byte outbytes[] = null; // // String line = null; // // Hashtable vars = getVarhash(-1); // newsz = commands.size() + vars.size(); // // /* If the number of commands or variables has increased, // * reindex them in the command line completor. Currently, // * it uses commands for the first completion, and variable // * names (with leading $) for subsequent completions. */ // if (newsz > oldsz) { // completors = new LinkedList(); // completors.add( // new SimpleCompletor(hashKeysToArray(commands))); // completors.add( // new SimpleCompletor(hashKeysToArray(vars, "$"))); // // completors.add(new NullCompletor()); // // try { // reader = new ConsoleReader(); // reader.addCompletor(new ArgumentCompletor(completors)); // } catch (IOException e) { // System.err.println(e); // return; // } // // oldsz = newsz; // } // // try { // line = reader.readLine(prompt); // } catch (IOException e) { // err.println(e); // break; // } // // if(line == null) // break; // if(sb.length() > 0) // sb.append('\n'); // sb.append(line); // try { // if(sb.length() <= 0) // continue; // // Thing res = evalAsyncAndWait(new Thing(sb.toString())); // if (res != null) { // String s = res.toString(); // if(s.length() > 0) { // // It seems that DataOutputStream.println(String) // // is broken and returns OutOfmemory when the // // string is to long, so we convert the string // // into bytes and write out the pure bytes // // directly. // outbytes = s.getBytes(); // } // } // sb.delete(0,sb.length()); // prompt = PROMPT; // } // catch(HeclException he) { // if (he.code.equals("PARSE_ERROR")) { // // Change prompt and get more input // prompt = PROMPT2; // } else { // sb.delete(0,sb.length()); // he.printStackTrace(); // outbytes = he.getMessage().getBytes(); // prompt = PROMPT; // } // } // if(outbytes != null) { // // result output // try { // out.write(outbytes); // out.println(); // } // catch(IOException ioex) { // err.println(ioex.getMessage()); // break; // } // outbytes = null; // } // } // } /** * Add a new class command to anInterp. * * @param clazz The Java class the command should operate on. * @param cmd The command to add. When this paramter isnull, * an existing command is removed. */ public void addClassCmd(Class clazz,ClassCommand cmd) { // clear cache first, even when deleting a cmd this.classcmdcache.clear(); int l = this.ci.size(); for(int i=0; iInterp Interp. *
* The current implementation does not support any subclassing and selects * the first class commandclazzis assignable to. * * @param clazz The Java class to look up the class command for. * @return AClassCommandInfodecsribing the class command, * ornull if no command was found. */ ClassCommandInfo findClassCmd(Class clazz) { ClassCommandInfo found = (ClassCommandInfo)this.classcmdcache.get(clazz); if(found == null) { // No entry in cache, so we loop over all class commands and try // to detect the most specific one. int l = this.ci.size(); for(int i=0; iunsets a variable in the current stack * frame. * * @param varname aeval: "+in.toString()); return CodeThing.get(this, in).run(this); } public HeclTask evalIdle(Thing idleThing) { return addTask(idle,new HeclTask(idleThing,idlegeneration,IDLEPREFIX),-1); } public HeclTask evalAsync(Thing asyncThing) { return addTask(asyncs, new HeclTask(asyncThing,0,ASYNCPREFIX),-1); } public Thing evalAsyncAndWait(Thing in) throws HeclException { HeclTask t = evalAsync(in); t.setErrorPrint(false); boolean done = false; while(!t.isDone()) { try { synchronized(t) { t.wait(); } } catch(Exception e) { // ignore e.printStackTrace(); } } try { Exception e = t.getError(); if(e != null) throw e; return t.getResult(); } catch (HeclException he) { throw he; } catch(Exception e) { throw new HeclException(e.getMessage()); } } /** * This version of evaltakes a 'level' argument that * tells Hecl what level to run the code at. Level 0 means * global, negative numbers indicate relative levels down from the * current stackframe, and positive numbers mean absolute stack * frames counting up from 0. * * @param in aThingvalue * @param level anintvalue * @return aThingvalue * @exception HeclException if an error occurs */ public Thing eval(Thing in, int level) throws HeclException { Thing result = null; Vector savedstack = new Vector(); int stacklen = stack.size(); int i = 0; int end = 0; HeclException save_exception = null; if (level >= 0) { end = level; } else { end = (stacklen - 1 + level); } /* Save the old stack frames... */ for (i = stacklen - 1; i > end; i--) { savedstack.addElement(stackDecr()); } try { result = eval(in); } catch (HeclException he) { /* If this is an upeval situation, we need to catch the * exception and then throw it *after* the old stack frame * has been restored. */ save_exception = he; } /* ... and then restore them after evaluating the code. */ for (i = savedstack.size() - 1; i >= 0; i--) { stackPush((Hashtable)savedstack.elementAt(i)); } if (save_exception != null) { throw save_exception; } return result; } public boolean hasIdleTasks() { return this.idle.size() == 0; } public synchronized HeclTask getEvent(String name) { int n = timers.size(); Vector v = new Vector(); HeclTask t = null; for(int i=0; its) break; } //System.err.println("Adding timer, time="+ts); return addTask(timers,t,i); } } public void cancelTimer(String name) { cancelTask(timers,name); } public void cancelIdle(String name) { cancelTask(idle,name); } public void cancelAsync(String name) { cancelTask(asyncs,name); } public synchronized void cancelIdle(HeclTask idletask) { idle.removeElement(idletask); } public synchronized boolean doOneEvent(int flags) { if((flags & ALL_EVENTS) == 0) flags = ALL_EVENTS; // The core of this procedure is an infinite loop, even though // we only service one event. The reason for this is that we // may be processing events that don't do anything inside of Hecl. int count = 0; while(true) { // First check for async events... HeclTask t = nextTask(asyncs,-1); if(t != null) { return executeTask(t); } long now = System.currentTimeMillis(); if((flags & TIMER_EVENTS) != 0) { t = nextTask(timers,now); if(t != null) { return executeTask(t); } } // Determine maxblocktime if ((flags & DONT_WAIT) != 0) { maxblocktime = 0; } else { maxblocktime = 1000; synchronized(this) { if(timers.size() > 0) { t = (HeclTask)timers.elementAt(0); maxblocktime = t.getGeneration() - now; } } } // this may reduce maxblocktime! if((flags & IDLE_EVENTS) != 0) { serviceIdleTask(); } if(count > 0 || maxblocktime <= 0) break; yield(); // give other thread a chance synchronized(this) { try { this.wait(maxblocktime); } catch (InterruptedException e) { // it doesn't matter } } //System.err.println("interp wait done, next loop iteration"); ++count; } //System.err.println("<--doOneEvent:false"); return false; } public void waitForToken(String tokenname) throws HeclException { boolean exists = false; WaitToken token = null; synchronized(this) { exists = waittokens.containsKey(tokenname); if(exists) throw new HeclException("Wait token '"+tokenname+"' already exists."); token = new WaitToken(); waittokens.put(tokenname,token); } // Endless loop, some event in the future should kick us off this loop boolean b = true; while(b) { // Carefully read/modify status information synchronized(this) { b = token.waiting; } if(b) { // Service one event doOneEvent(Interp.ALL_EVENTS); } } } public void notifyToken(String tokenname) throws HeclException { synchronized(this) { WaitToken token = (WaitToken)waittokens.get(tokenname); if(token == null) throw new HeclException("No wait token '"+tokenname+"'."); token.waiting = false; waittokens.remove(tokenname); } } /** * The terminatemethod terminates the Hecl * interpreter thread in a graceful manner. The thread will * eventually finish its run-method. * */ public void terminate() { running = false; synchronized(this) { notify(); } } @Override public void run() { // System.err.println("interp running..."); long now = System.currentTimeMillis(); while(running) { doOneEvent(ALL_EVENTS); } // System.err.println("interp stopped!"); } /** * TheinitCommandsmethod initializes all the built in * commands. These are commands available in all versions of Hecl. J2SE * commands are initialized in Standard.java, and J2ME commands in * Micro.java. * * @exception HeclException if an error occurs */ private void initInterp() throws HeclException { /* Do not use the 'Facade' style commands as an example if you * just have to add a simple command or two. The pattern * works best when you need to add several commands with * related functionality. */ // System.err.println("-->initinterp"); // System.err.println("loading interp cmds..."); /* Commands that manipulate interp data structures - * variables, procs, commands, and so forth. */ InterpCmds.load(this); // System.err.println("loading math cmds..."); /* Math and logic commands. */ MathCmds.load(this); // System.err.println("loading list cmds..."); /* List related commands. */ ListCmds.load(this); // System.err.println("loading control cmds..."); /* Control commands. */ ControlCmds.load(this); // System.err.println("loading string cmds..."); /* String commands. */ StringCmds.load(this); // System.err.println("loading hash cmds..."); /* Hash table commands. */ HashCmds.load(this); commands.put("puts", new PutsCmd()); commands.put("sort", new SortCmd()); //#if cldc == 1.0 addClassCmd(procclass, new AnonProc()); //#else addClassCmd(Proc.class, new AnonProc()); //#endif // System.err.println("<--initinterp"); } /** * ThecmdRenamemethod renames a command, or throws * an error if the original command didn't exist. * * @param oldname aStringvalue * @param newname aStringvalue * @exception HeclException if an error occurs */ public synchronized void cmdRename(String oldname, String newname) throws HeclException { cmdAlias(oldname, newname); commands.remove(oldname); } public synchronized void cmdAlias(String oldname, String newname) throws HeclException { Command tmp = (Command)commands.get(oldname); if (tmp == null) { throw new HeclException("Command " + oldname + " does not exist"); } commands.put(newname, tmp); } /** * ThestackIncrmethod creates a new stack frame. Used in * the Proc class. * */ public synchronized void stackIncr() { stackPush(new Hashtable()); } /** *stackDecrpops the stack frame, returning it so that * commands like upeval can save it. If it's not saved, it's gone. * */ public synchronized Hashtable stackDecr() { return (Hashtable) stack.pop(); } /** *stackDecrpushes a new variable hashtable * (probably saved via upeval) onto the stack frame. * */ public synchronized void stackPush(Hashtable vars) { cacheversion++; stack.push(vars); } /** *getVarhashfetches the variable Hashtable at the * given level, where -1 means to just get the hashtable on top of * the stack. * * @param level anintvalue * @return aHashtablevalue */ private Hashtable getVarhash(int level) { return level < 0 ? (Hashtable)stack.peek() : (Hashtable)stack.elementAt(level); } /** *getVarreturns the value of a variable given its name. * * @param varname aThingvalue * * @return aThingvalue * @exception HeclException if an error occurs */ public Thing getVar(Thing varname) throws HeclException { return getVar(varname.toString(), -1); } /** *getVarreturns the value of a variable given its name. * * @param varname aStringvalue * @return aThingvalue * @exception HeclException if an error occurs */ public Thing getVar(String varname) throws HeclException { return getVar(varname, -1); } /** *getVarreturns the value of a variable given its name and * level. * * @param varname aStringvalue * @param level anintvalue * @return aThingvalue * @exception HeclException if an error occurs */ public synchronized Thing getVar(String varname, int level) throws HeclException { Hashtable lookup = getVarhash(level); //System.out.println("getvar: " + varname + " " + level + " " + lookup); Thing res = (Thing) lookup.get(varname); //#ifdef old // if (res == null) { // throw new HeclException("Variable " + varname + " does not exist"); // } // return res; //#else if(res == GLOBALREFTHING) { // ref to a global var Hashtable globalhash = getVarhash(0); res = (Thing)globalhash.get(varname); if(res == GLOBALREFTHING) { // should not happen, but just in case... System.err.println("Unexpected GLOBALREFTHING in globalhash"); res = null; } //#ifdef emptyglobals // else if (res == null) { // // Return a fake empty value for a non-set global variable for // // the sake of modifying commands. // // !!!! THIS IS STRANGE !!! // System.err.println("FAKE EMPTY VALUE for global var"); // res = new Thing(""); // globalhash.put(varname,res); // } //#endif } if (res == null) { throw new HeclException("Variable " + varname + " does not exist"); } //System.err.println("<unSetVar Thingvalue * @throws org.hecl.HeclException */ public void unSetVar(Thing varname) throws HeclException { unSetVar(varname.toString(),-1); } public synchronized void unSetVar(String varname) throws HeclException { unSetVar(varname,-1); } public synchronized void unSetVar(String varname,int level) throws HeclException { Hashtable lookup = getVarhash(level); // Bump the cache number so that SubstThing.get refetches the // variable. Thing value = (Thing)lookup.get(varname); if (value != null) { cacheversion++; lookup.remove(varname); if (value.global) { Hashtable globalhash = getVarhash(0); value = (Thing)globalhash.get(varname); if (value != null) { globalhash.remove(varname); } } } else { throw new HeclException("Variable " + varname + " does not exist"); } } /** *existsVarreturnstrueif the given * variable exists in the current variable stack frame,false* if it does not. * * @param varname aThingvalue * @return abooleanvalue * @throws org.hecl.HeclException */ public boolean existsVar(Thing varname) throws HeclException { return existsVar(varname.toString()); } /** *existsVarreturnstrueif the given * variable exists in the current variable stack frame,false* if it does not. * * @param varname aStringvalue * @return abooleanvalue */ public boolean existsVar(String varname) { return existsVar(varname, -1); } /** *existsVarreturnstrueif the given * variable exists in the variable stack frame given bylevel, *falseif it does not. * * @param varname aStringvalue * @param level anintvalue * @return abooleanvalue */ public synchronized boolean existsVar(String varname, int level) { Hashtable lookup = getVarhash(level); return lookup.containsKey(varname); } /** *addErroradds a Thing as an error message. * * @param err aThingvalue */ public void addError(Thing err) { error.push(err); } /** *clearErrorclears the error stack. * */ public void clearError() { error = new Stack(); } /** *checkArgCountchecks to see whether the command * actually has the required number of arguments. The first element of the * parameter arrayargvis not counted as argument! * * @param argv AThing[]parameter array. * @param minargs The minimal number of arguments or -1 if no check is required. * @param maxargs The maximal number of arguments or -1 if no check is required. * @exception HeclException if an error occurs */ public static void checkArgCount(Thing[] argv,int minargs,int maxargs) throws HeclException { int n = argv.length-1; // Ignore command name if(minargs >= 0 && n < minargs) { throw new HeclException("Too few arguments, at least " + minargs + " arguments required."); } if(maxargs >= 0 && n > maxargs) { throw new HeclException("Bad argument count, max. " + maxargs +" arguments allowed."); } } /** *nextTaskextracts first element from given vector. * This function operates in a synchronized manner on the argument *v. * * @param v AVectorof tasks. * @param until A value to compare the result of *getGenerationof theHeclTask. If *untilis less than 0 orgetGeneration* is less thanuntilfor the first element of *v, the first task in the vector is returned, null * otherwise. */ protected synchronized HeclTask nextTask(Vector v,long until) { HeclTask t = null; if(v.size() > 0) { t = (HeclTask)v.elementAt(0); //System.err.println("now="+ts+", fire="+t.getGeneration()); if(until < 0 || t.getGeneration() <= until) { v.removeElementAt(0); } else { t = null; } } return t; } /** * Service at most one idle task of the idle task queue. * * @return abooleanindicatign that an idle task has been * serviced (=true) or not (=false). */ protected boolean serviceIdleTask() { // The code below is trickier than it may look, for the following // reasons: // // 1. New handlers can get added to the list while the current // one is being processed. If new ones get added, we don't // want to process them during this pass through the list (want // to check for other work to do first). This is implemented // using the generation number in the handler: new handlers // will have a different generation than any of the ones currently // on the list. // 2. The handler can call doOneEvent, so we have to remove // the handler from the list before calling it. Otherwise an // infinite loop could result. // 3. cancelIdleCall can be called to remove an element from // the list while a handler is executing, so the list could // change structure during the call. long oldgeneration; synchronized(this) { if(idle.size()== 0) { idlegeneration = 0; return false; } oldgeneration = idlegeneration; ++idlegeneration; } HeclTask t = nextTask(idle,oldgeneration); if(t != null) t.execute(this); if(idle.size() > 0) maxblocktime = 0; return true; } /** * Service at most one idle task of the idle task queue. * * @param v AVectorof tasks to add the task to. * @param task TheHeclTaskto add. * @param pos The int position ofvwhere to * addtaskbeing in the range from 0 to *v.size(). -1 indicates to addtaskto the * end ofv. * * @return AStringbeing the name of the inserted task. */ private HeclTask addTask(Vector v,HeclTask task,int pos) { synchronized (v) { if(pos < 0) v.addElement(task); else v.insertElementAt(task,pos); // notify(); return task; } } /** * Cancel a task of the specified name in the specified vector. * The functions performs nothing when no task of the specified name is * an element of the vector. * * @param v A vector ofHeclTasks. * @param name AStringspecifying the name of the * task to be removed fromv. */ private synchronized void cancelTask(Vector v,String name) { int n = v.size(); for(int i = 0; itask. * * @return Always the boolean value trueto indicate * that a task has been serviced. */ private boolean executeTask(HeclTask task) { try { task.execute(this); } catch(Exception e) { // Nothing to do. It is expected that each task handles // its exceptions locally but we use this block is to ensure // that the queue continues to operate. } return true; } /** * ThebackgroundErrormethod can be called to * dispatch a bgerror command. * * @param errorMessage aStringvalue */ public void backgroundError(String errorMessage) { try { Vector v = new Vector(); v.addElement(new Thing("bgerror")); v.addElement(new Thing(errorMessage)); eval(ListThing.create(v),0); } catch(Exception e) { System.err.println("Hecl severe bg error: " + errorMessage + "\n" + e.toString()); e.printStackTrace(); } } protected static class WaitToken { public volatile boolean waiting = true; } static String readLine(InputStreamReader is) { StringBuffer b = new StringBuffer(); int ch = -1; try { while ((ch = is.read()) != -1) { if(ch == '\r') continue; if(ch == '\n') break; b.append((char)ch); } } catch(IOException iox) { } if(b.length() > 0 || ch != -1) return b.toString(); return null; } }
Ads help maintain this website.