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     */