001 002 package spirograph; 003 004 import bsh.Interpreter; 005 import bsh.EvalError; 006 007 import java.util.Hashtable; 008 import java.util.StringTokenizer; 009 010 /** Implements and accelerator that uses bean shell to interpret student code. 011 * See <a href="http://www.beanshell.org/">www.beanshell.org</a> 012 * for bean shell details. 013 * 014 * <p>Copyright © 1998 Massachusetts Institute of Technology.<br /> 015 * Copyright © 2003 Franklin W. Olin College of Engineering.</p> 016 */ 017 public class BshAccel implements Accelerator { 018 019 private Interpreter i; 020 021 /** Create a new Accelerator that uses the bean shell (bsh) 022 * interpreter to evaluate code, rather than the native Java 023 * compiler. 024 * @param fieldCode The student code defining the fields (variables) used in the body code. 025 * @param bodyCode The student code to evaluate 026 */ 027 public BshAccel(String fieldCode, String bodyCode) 028 { 029 String code, mangledCode; 030 031 // bsh has some strange ideas about closure semantics. In 032 // particular, a variable on the left hand side of an assignment 033 // always assigns to a variable in local scope, even if it means 034 // creating a new variable in local scope that shadows one in the 035 // enclosing scope. Thus, using normal Java-style syntax, 036 // variables in enclosing scopes are read-only. 037 // 038 // The escape is that "super.foo" will explicitly refer to the 039 // variable named foo in the enclosing scope. We don't want to 040 // expose this to the students, because it is different from real 041 // Java syntax. Therefore, we first parse the "fields" text for a 042 // list of variables, and prefix all uses of those variables in 043 // the body of the code with the string "super.". 044 // 045 // If the body declares a variable that would shadow the field, 046 // uses of that will also be prefixed, so the student won't see 047 // the usual shadowing semantics. This shouldn't be a problem. 048 049 mangledCode = ScopeMangle(fieldCode, bodyCode); 050 051 code = "wrapper()" + 052 "{" + 053 fieldCode + 054 055 "foo(double pos, double vel, double otherPos, " + 056 "double otherVel, double maxPos)" + 057 "{" + 058 mangledCode + 059 "return 0;" + // Ensure some value is returned 060 "}" + 061 062 "return this;" + // wacky bsh return-closure syntax 063 "}"; 064 065 i = new Interpreter(); 066 try 067 { 068 i.eval(code); 069 i.eval("wrobj = wrapper();"); 070 } 071 catch (EvalError e) 072 { 073 System.out.println("Error in student code: " + e); 074 } 075 076 } 077 078 // method act documented in Accelerator 079 080 public double act(double pos, double vel, 081 double otherPos, double otherVel, double maxPos) 082 { 083 double ret = 0.0; 084 085 try 086 { 087 i.eval("retval = (double) wrobj.foo( " + 088 pos + ", " + 089 vel + ", " + 090 otherPos + ", " + 091 otherVel + ", " + 092 maxPos + ") "); 093 i.eval("if (retval == void) { retval = 0; }"); 094 ret = ((Double)i.getVariable("retval")).doubleValue(); 095 } 096 catch (EvalError e) 097 { 098 System.out.println("Eval error (internal): " + e); 099 } 100 101 return ret; 102 } 103 104 String ScopeMangle(String fields, String body) 105 { 106 StringTokenizer st; 107 Hashtable fieldNames; 108 boolean haveType = false; 109 boolean inQuote = false; 110 boolean inDoubQuote = false; 111 boolean escapeNext = false; 112 String token, newBody; 113 114 // The grammar of fields we recognize is: 115 // "type foo[,bar]*;" 116 st = new StringTokenizer(fields, " \t\n\r,;=\"\\'", true); 117 fieldNames = new Hashtable(); 118 119 // Collect field names. 120 haveType = false; 121 while (st.hasMoreTokens()) 122 { 123 token = st.nextToken(); 124 125 if (escapeNext) { 126 escapeNext = false; 127 continue; 128 } 129 130 if (token.equals("\"")) { 131 if (!inDoubQuote) { 132 inDoubQuote = true; 133 continue; 134 } else { 135 inDoubQuote = false; 136 continue; 137 } 138 } 139 140 if (token.equals("'")) { 141 if (!inQuote) { 142 inQuote = true; 143 continue; 144 } else { 145 inQuote = false; 146 continue; 147 } 148 } 149 150 if (token.equals("\\")) { 151 escapeNext = true; 152 continue; 153 } 154 155 156 if (inQuote || inDoubQuote) { 157 continue; 158 } 159 160 if (token.equals(" ") || 161 token.equals("\t") || 162 token.equals("\n") || 163 token.equals("\r") || 164 token.equals("{") || 165 token.equals("}") || 166 token.equals("[") || 167 token.equals("]") || 168 token.equals(",") || 169 token.equals("=")) { 170 ; // do nothing 171 } else if (Character.isDigit(token.charAt(0))) { 172 ; // do nothing 173 } else if (token.equals(";")) { 174 haveType = false; 175 } else if(haveType == false) { 176 haveType = true; 177 } else if (!SpiroUtils.isReservedWord(token)) { // A field name 178 fieldNames.put(token, "field"); 179 } 180 } 181 182 // Mangle the body. 183 st = new StringTokenizer(body, 184 " \t\n\r`~!@#%^&*()-=+[]{}\\|;:'\"<>,./?", 185 true); 186 187 newBody = ""; 188 while (st.hasMoreTokens()) 189 { 190 token = st.nextToken(); 191 192 // Don't bother figuring out what kind of a thing the 193 // token is. If it's in our list of fields, it's fair 194 // game. 195 196 if (fieldNames.containsKey(token)) 197 token = "super." + token; 198 199 newBody += token; 200 } 201 202 return newBody; 203 } 204 } 205 206 /* 207 * $Log: BshAccel.java,v $ 208 * Revision 1.8 2003/01/14 19:41:35 gus 209 * Fixed typo and javadoc error 210 * 211 * Revision 1.7 2003/01/13 20:02:48 gus 212 * Remove useless string field that makes the code harder to read. 213 * 214 * Revision 1.6 2003/01/13 19:59:25 gus 215 * Remove entirely unneccessary argument from BshAccel constructor 216 * 217 * Revision 1.5 2003/01/10 22:19:18 gus 218 * Complete and update the javadocs 219 * 220 * Revision 1.4 2003/01/10 17:57:49 gus 221 * Handle quoted string literals and charcter literals 222 * Also don't use _ and $ in the delimiters for the body; they are valid chars in identifiers 223 * 224 */