/interpreter/tags/at2-build060407/src/edu/vub/at/eval/Evaluator.java
Java | 532 lines | 292 code | 57 blank | 183 comment | 62 complexity | d01d2df2ccf0d4525a38709e3d667276 MD5 | raw file
1/** 2 * AmbientTalk/2 Project 3 * Evaluator.java created on 27-sep-2006 at 15:53:39 4 * (c) Programming Technology Lab, 2006 - 2007 5 * Authors: Tom Van Cutsem & Stijn Mostinckx 6 * 7 * Permission is hereby granted, free of charge, to any person 8 * obtaining a copy of this software and associated documentation 9 * files (the "Software"), to deal in the Software without 10 * restriction, including without limitation the rights to use, 11 * copy, modify, merge, publish, distribute, sublicense, and/or 12 * sell copies of the Software, and to permit persons to whom the 13 * Software is furnished to do so, subject to the following 14 * conditions: 15 * 16 * The above copyright notice and this permission notice shall be 17 * included in all copies or substantial portions of the Software. 18 * 19 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 20 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 21 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 22 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 23 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 24 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 25 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 26 * OTHER DEALINGS IN THE SOFTWARE. 27 */ 28package edu.vub.at.eval; 29 30import edu.vub.at.exceptions.InterpreterException; 31import edu.vub.at.exceptions.XAmbienttalk; 32import edu.vub.at.exceptions.XArityMismatch; 33import edu.vub.at.exceptions.XIllegalParameter; 34import edu.vub.at.objects.ATContext; 35import edu.vub.at.objects.ATObject; 36import edu.vub.at.objects.ATTable; 37import edu.vub.at.objects.grammar.ATAssignVariable; 38import edu.vub.at.objects.grammar.ATSymbol; 39import edu.vub.at.objects.mirrors.OBJMirrorRoot; 40import edu.vub.at.objects.natives.NATException; 41import edu.vub.at.objects.natives.NATObject; 42import edu.vub.at.objects.natives.NATTable; 43import edu.vub.at.objects.natives.NATText; 44import edu.vub.at.objects.natives.OBJLexicalRoot; 45import edu.vub.at.objects.natives.grammar.AGSplice; 46import edu.vub.at.objects.natives.grammar.AGSymbol; 47import edu.vub.at.objects.symbiosis.JavaObject; 48import edu.vub.at.objects.symbiosis.JavaPackage; 49import edu.vub.at.objects.symbiosis.XJavaException; 50import edu.vub.util.Matcher; 51import edu.vub.util.Pattern; 52 53import java.io.File; 54import java.io.FileInputStream; 55import java.io.IOException; 56import java.io.InputStream; 57import java.util.LinkedList; 58 59/** 60 * The Evaluator class serves as a repository for auxiliary evaluation methods. 61 * 62 * @author tvc 63 */ 64public final class Evaluator { 65 66 // important symbols 67 68 public static final AGSymbol _ANON_MTH_NAM_ = AGSymbol.jAlloc("nativelambda"); 69 public static final NATTable _ANON_MTH_ARGS_ = NATTable.of(new AGSplice(AGSymbol.jAlloc("args"))); 70 public static final AGSymbol _LAMBDA_ = AGSymbol.alloc(NATText.atValue("lambda")); 71 public static final AGSymbol _APPLY_ = AGSymbol.alloc(NATText.atValue("apply")); 72 public static final AGSymbol _INIT_ = AGSymbol.alloc(NATText.atValue("init")); 73 public static final AGSymbol _CURNS_SYM_ = AGSymbol.jAlloc("~"); 74 75 /** 76 * A thread-local variable is used to assign a unique global scope to 77 * each separate actor. Each actor that invokes the getGlobalLexicalScope 78 * method receives its own separate copy of the global scope 79 */ 80 private static final ThreadLocal _GLOBAL_SCOPE_ = new ThreadLocal() { 81 protected synchronized Object initialValue() { 82 return createGlobalLexicalScope(); 83 } 84 }; 85 86 /** 87 * A thread-local variable is used to assign a unique lobby namespace to 88 * each separate actor. Each actor that invokes the getLobby() 89 * method receives its own separate copy of the lobby namespace 90 */ 91 private static final ThreadLocal _LOBBY_NAMESPACE_ = new ThreadLocal() { 92 protected synchronized Object initialValue() { 93 return createLobbyNamespace(); 94 } 95 }; 96 97 /** 98 * A thread-local variable is used to assign a unique jlobby root to 99 * each separate actor. The jlobby root is the root JavaPackage from 100 * which other Java packages can be loaded. Each actor that invokes the getJLobbyRoot() 101 * method receives its own separate copy of the jlobby root 102 */ 103 private static final ThreadLocal _JLOBBY_ROOT_ = new ThreadLocal() { 104 protected synchronized Object initialValue() { 105 return createJLobbyRoot(); 106 } 107 }; 108 109 /** 110 * A thread-local variable is used to assign a unique mirror root to 111 * each separate actor. The mirror root encapsulates the default semantics 112 * for AmbientTalk objects and is the parent of most interecessive custom mirrors 113 * defined by AmbientTalk programmers themselves. 114 */ 115 private static final ThreadLocal _MIRROR_ROOT_ = new ThreadLocal() { 116 protected synchronized Object initialValue() { 117 return createMirrorRoot(); 118 } 119 }; 120 121 122 /** 123 * Auxiliary function used to print the elements of a table using various separators. 124 */ 125 public final static NATText printElements(ATObject[] els,String start, String sep, String stop) throws InterpreterException { 126 if (els.length == 0) 127 return NATText.atValue(String.valueOf(start+stop)); 128 129 StringBuffer buff = new StringBuffer(start); 130 for (int i = 0; i < els.length - 1; i++) { 131 buff.append(els[i].meta_print().asNativeText().javaValue + sep); 132 } 133 buff.append(els[els.length-1].meta_print().asNativeText().javaValue + stop); 134 return NATText.atValue(buff.toString()); 135 } 136 137 /** 138 * Auxiliary function used to print the elements of a table using various separators. 139 */ 140 public final static NATText printElements(NATTable tab,String start, String sep, String stop) throws InterpreterException { 141 return printElements(tab.elements_, start, sep, stop); 142 } 143 144 public static final NATText printAsStatements(ATTable tab) throws InterpreterException { 145 return printElements(tab.asNativeTable(), "", "; ", ""); 146 } 147 148 public static final NATText printAsList(ATTable tab) throws InterpreterException { 149 return printElements(tab.asNativeTable(), "(", ", ", ")"); 150 } 151 152 /** 153 * This function is called whenever arguments to a function, message, method need to be evaluated. 154 * TODO(coercers) currently does not work for user-defined tables 155 */ 156 public static final NATTable evaluateArguments(NATTable args, ATContext ctx) throws InterpreterException { 157 if (args == NATTable.EMPTY) return NATTable.EMPTY; 158 159 ATObject[] els = args.elements_; 160 161 LinkedList result = new LinkedList(); 162 int siz = els.length; 163 for (int i = 0; i < els.length; i++) { 164 if (els[i].isSplice()) { 165 ATObject[] tbl = els[i].asSplice().base_getExpression().meta_eval(ctx).asNativeTable().elements_; 166 for (int j = 0; j < tbl.length; j++) { 167 result.add(tbl[j]); 168 } 169 siz += (tbl.length - 1); // -1 because we replace one element by a table of elements 170 } else { 171 result.add(els[i].meta_eval(ctx)); 172 } 173 } 174 return NATTable.atValue((ATObject[]) result.toArray(new ATObject[siz])); 175 } 176 177 // auxiliary interface to support functor objects 178 private interface BindClosure { 179 public void bindParamToArg(ATObject inScope, ATSymbol param, ATObject arg) throws InterpreterException; 180 } 181 182 /** 183 * Auxiliary function to bind formal parameters to actual arguments within a certain scope. 184 * 185 * A formal parameter list is defined as: 186 * (mandatory arg: ATSymbol)* , (optional arg: ATVarAssignment)*, (rest arg: ATSplice)? 187 * 188 * An actual argument list is defined as: 189 * (actual arg: ATObject)* 190 * 191 * @deprecated use partial evalation using {@link PartialBinder#bind(ATObject[], ATContext, edu.vub.at.eval.PartialBinder.BindClosure)} instead. 192 * 193 * @param funnam the name of the function for which to bind these elements, for debugging purposes only 194 * @param context the context whose lexical scope denotes the frame in which to store the bindings 195 * The context is also the context in which to evaluate optional argument expressions. 196 * @param parameters the formal parameter references (of which the last element may be a 'rest' arg to collect left-over arguments) 197 * @param arguments the actual arguments, already evaluated 198 * @param binder a functor object describing the strategy to bind an argument to a parameter (assign or define the parameter) 199 * @throws XArityMismatch when the formals don't match the actuals 200 */ 201 private static final void bindArguments(String funnam, ATContext context, ATTable parameters, ATTable arguments, BindClosure binder) throws InterpreterException { 202 if (parameters == NATTable.EMPTY) { 203 if (arguments == NATTable.EMPTY) 204 return; // no need to bind any arguments 205 else 206 throw new XArityMismatch(funnam, 0, arguments.base_getLength().asNativeNumber().javaValue); 207 } 208 209 ATObject[] pars = parameters.asNativeTable().elements_; 210 ATObject[] args = arguments.asNativeTable().elements_; 211 212 /* Traverse formal parameter list conceptually according to 213 * the following state diagram: 214 * 215 * state: mandatory [start state, end state] 216 * case Symbol => mandatory 217 * case Assignment => optional 218 * case Splice => rest-arg 219 * state: optional [end state] 220 * case Symbol => error // no mandatory pars after optional pars 221 * case Assignment => optional 222 * case Splice => rest-arg 223 * state: rest-arg [end state] 224 * case * => error // rest-arg should be last 225 * state: error [end state] 226 * case * => error 227 */ 228 229 int paridx = 0; 230 int numMandatoryArguments = 0; 231 ATObject scope = context.base_getLexicalScope(); 232 233 // determine number of mandatory arguments 234 for (; paridx < pars.length && pars[paridx].isSymbol(); paridx++) { 235 numMandatoryArguments++; 236 } 237 238 // are there enough actual arguments to satisfy all mandatory args? 239 if (numMandatoryArguments > args.length) { 240 // error: not enough actuals 241 throw new XArityMismatch(funnam, numMandatoryArguments, args.length); 242 } 243 244 // bind all mandatory arguments 245 for (paridx = 0; paridx < numMandatoryArguments; paridx++) { 246 // bind formal to actual 247 binder.bindParamToArg(scope, pars[paridx].asSymbol(), args[paridx]); 248 } 249 250 // if there are no more parameters, make sure all actuals are processed 251 if (numMandatoryArguments == pars.length) { 252 if (numMandatoryArguments < args.length) { 253 // error: too many actuals 254 throw new XArityMismatch(funnam, numMandatoryArguments, args.length); 255 } // else { return; } 256 } else { 257 // if there are more parameters, process optionals first and then rest parameter 258 int numDefaultOptionals = 0; // count the number of optional arguments that had no corresponding actual 259 // determine number of optional arguments 260 for (; paridx < pars.length && pars[paridx].isVariableAssignment(); paridx++) { 261 if (paridx < args.length) { 262 // bind formal to actual and ignore default initialization expression 263 binder.bindParamToArg(scope, pars[paridx].asVariableAssignment().base_getName(), args[paridx]); 264 } else { 265 // no more actuals: bind optional parameter to default initialization expression 266 ATAssignVariable param = pars[paridx].asVariableAssignment(); 267 binder.bindParamToArg(scope, param.base_getName(), param.base_getValueExpression().meta_eval(context)); 268 numDefaultOptionals++; 269 } 270 } 271 272 // if there are no more parameters, make sure all actuals are processed 273 if (paridx == pars.length) { 274 if (paridx < args.length) { 275 // error: too many actuals 276 throw new XArityMismatch(funnam, numMandatoryArguments, args.length); 277 } // else { return; } 278 } else { 279 // all that is left to process is an optional rest-parameter 280 // check whether last param is spliced, which indicates variable parameter list 281 if (pars[paridx].isSplice()) { 282 // bind the last parameter to the remaining arguments 283 int numRemainingArgs = args.length - paridx + numDefaultOptionals; // #actuals - #actuals used to fill in mandatory or optional args 284 ATObject[] restArgs = new ATObject[numRemainingArgs]; 285 for (int i = 0; i < numRemainingArgs; i++) { 286 restArgs[i] = args[i + paridx]; 287 } 288 ATSymbol restArgsName = pars[paridx].asSplice().base_getExpression().asSymbol(); 289 binder.bindParamToArg(scope, restArgsName, NATTable.atValue(restArgs)); 290 291 // rest parameter should always be last 292 if (paridx != pars.length - 1) { 293 throw new XIllegalParameter(funnam, "rest parameter is not the last parameter: " + pars[paridx]); 294 } // else { return; } 295 } else { 296 // optionals followed by mandatory parameter 297 throw new XIllegalParameter(funnam, "optional parameters followed by mandatory parameter " + pars[paridx]); 298 } 299 } 300 } 301 } 302 303 /** 304 * Bind all of the given parameters as newly defined slots in the given scope to the given arguments. 305 * The scope is defined as the lexical scope of the given context. 306 * @deprecated use partial evalation using {@link PartialBinder#bind(ATObject[], ATContext, edu.vub.at.eval.PartialBinder.BindClosure)} instead. 307 */ 308 public static final void defineParamsForArgs(String funnam, ATContext context, ATTable parameters, ATTable arguments) throws InterpreterException { 309 bindArguments(funnam, context, parameters, arguments, new BindClosure() { 310 public void bindParamToArg(ATObject scope, ATSymbol param, ATObject arg) throws InterpreterException { 311 scope.meta_defineField(param, arg); 312 } 313 }); 314 } 315 316 /** 317 * Assign all of the formal parameter names in the scope object to the given arguments 318 * The scope is defined as the lexical scope of the given context. 319 * @deprecated use partial evalation using {@link PartialBinder#bind(ATObject[], ATContext, edu.vub.at.eval.PartialBinder.BindClosure)} instead. 320 */ 321 public static final void assignArgsToParams(String funnam, ATContext context, ATTable parameters, ATTable arguments) throws InterpreterException { 322 bindArguments(funnam, context, parameters, arguments, new BindClosure() { 323 public void bindParamToArg(ATObject scope, ATSymbol param, ATObject arg) throws InterpreterException { 324 scope.meta_assignVariable(param, arg); 325 } 326 }); 327 } 328 329 /** 330 * Given a formal parameter list, this auxiliary method returns a new table 331 * consisting of the values of the bindings of the mandatory parameters of 332 * formals within the context ctx. 333 */ 334 public static NATTable evalMandatoryPars(ATTable formals, ATContext ctx) throws InterpreterException { 335 if (formals == NATTable.EMPTY) { 336 return NATTable.EMPTY; 337 } else { 338 ATObject[] pars = formals.asNativeTable().elements_; 339 int numMandatory; 340 for (numMandatory = 0; numMandatory < pars.length; numMandatory++) { 341 if (!pars[numMandatory].isSymbol()) { 342 break; 343 } 344 } 345 if (numMandatory > 0) { 346 ATObject[] bindings = new ATObject[numMandatory]; 347 for (int i = 0; i < bindings.length; i++) { 348 bindings[i] = pars[i].asSymbol().meta_eval(ctx); 349 } 350 return NATTable.atValue(bindings); 351 } else { 352 return NATTable.EMPTY; 353 } 354 } 355 } 356 357 /** 358 * Returns the raw contents of a file in a String (using this JVM's default character encoding) 359 */ 360 public static String loadContentOfFile(File file) throws IOException { 361 InputStream is = new FileInputStream(file); 362 363 // Get the size of the file 364 long length = file.length(); 365 366 // You cannot create an array using a long type. 367 // It needs to be an int type. 368 // Before converting to an int type, check 369 // to ensure that file is not larger than Integer.MAX_VALUE. 370 if (length > Integer.MAX_VALUE) { 371 throw new IOException("File is too large: "+file.getName()); 372 } 373 374 // Create the byte array to hold the data 375 byte[] bytes = new byte[(int)length]; 376 377 // Read in the bytes 378 int offset = 0; 379 int numRead = 0; 380 while (offset < bytes.length 381 && (numRead=is.read(bytes, offset, bytes.length-offset)) >= 0) { 382 offset += numRead; 383 } 384 385 // Ensure all the bytes have been read in 386 if (offset < bytes.length) { 387 throw new IOException("Could not completely read file "+file.getName()); 388 } 389 390 // Close the input stream and return bytes 391 is.close(); 392 return new String(bytes); 393 } 394 395 /** 396 * @return the 'global' lexical scope of an actor, which is a normal native object 397 * whose lexical parent is OBJLexicalRoot. 398 */ 399 public static NATObject getGlobalLexicalScope() { 400 return (NATObject) _GLOBAL_SCOPE_.get(); 401 } 402 403 /** 404 * @return the lobby namespace of an actor, which is a normal empty object 405 */ 406 public static NATObject getLobbyNamespace() { 407 return (NATObject) _LOBBY_NAMESPACE_.get(); 408 } 409 410 /** 411 * @return the jlobby root package of an actor, which is a JavaPackage with an empty path prefix. 412 */ 413 public static JavaPackage getJLobbyRoot() { 414 return (JavaPackage) _JLOBBY_ROOT_.get(); 415 } 416 417 /** 418 * @return the mirror root of an actor, from which intercessive mirrors usually inherit. 419 */ 420 public static OBJMirrorRoot getMirrorRoot() { 421 return (OBJMirrorRoot) _MIRROR_ROOT_.get(); 422 } 423 424 /** 425 * Restores the global lexical scope to a fresh empty object. 426 * Resets the lobby global namespace. 427 */ 428 public static void resetEnvironment() { 429 _GLOBAL_SCOPE_.set(createGlobalLexicalScope()); 430 _LOBBY_NAMESPACE_.set(createLobbyNamespace()); 431 _JLOBBY_ROOT_.set(createJLobbyRoot()); 432 _MIRROR_ROOT_.set(createMirrorRoot()); 433 } 434 435 /** 436 * A global scope has the sentinel instance as its lexical parent. 437 */ 438 private static NATObject createGlobalLexicalScope() { 439 NATObject root = new NATObject(OBJLexicalRoot._INSTANCE_); 440 return root; 441 } 442 443 /** 444 * A lobby namespace is a simple empty object 445 */ 446 private static NATObject createLobbyNamespace() { 447 return new NATObject(); 448 } 449 450 /** 451 * A jlobby root package is a JavaPackage with an empty path prefix 452 */ 453 private static NATObject createJLobbyRoot() { 454 return new JavaPackage(""); 455 } 456 457 /** 458 * The default mirror root, with an empty base-level object 459 */ 460 private static OBJMirrorRoot createMirrorRoot() { 461 return new OBJMirrorRoot(); 462 } 463 464 public static final String valueNameOf(Class c) { 465 String name = getSimpleName(c); 466 if (name.startsWith("AT")) { 467 return "a" + classnameToValuename(name, "AT"); 468 } else if (name.startsWith("NAT")) { 469 return "a native" + classnameToValuename(name, "NAT"); 470 } else if (name.startsWith("AG")) { 471 return "a native AST" + classnameToValuename(name, "AG"); 472 } else if (name.startsWith("X")) { 473 return "a native exception" + classnameToValuename(name, "X"); 474 } else if (name.startsWith("OBJ")) { 475 return "the native object" + classnameToValuename(name, "OBJ"); 476 } else { 477 return name; 478 } 479 } 480 481 public static final String toString(ATObject obj) { 482 try { 483 return obj.meta_print().javaValue; 484 } catch(InterpreterException e) { 485 return "<unprintable: " + e.getMessage() + ">"; 486 } 487 } 488 489 private static final String classnameToValuename(String classname, String prefix) { 490 // replace all uppercased letters "L" by " l" 491 // Backport from JDK 1.4 to 1.3 492 // Matcher m = p.matcher(classname.replaceFirst(prefix, "")); 493 Pattern p = Pattern.compile("[A-Z]"); 494 Matcher m = p.matcher(new StringBuffer(Pattern.compile(prefix).matcher(new StringBuffer(classname)).replaceFirst(""))); 495 // Matcher m = p.matcher(classname.replaceFirst(prefix, "")); 496 StringBuffer sb = new StringBuffer(); 497 while (m.find()) { 498 m.appendReplacement(sb, " " + new Character(Character.toLowerCase(m.group().charAt(0))).toString()); 499 } 500 m.appendTail(sb); 501 return sb.toString(); 502 } 503 504 // UTILITY METHODS 505 506 /** 507 * Returns the unqualified name of a class. 508 */ 509 public static final String getSimpleName(Class c) { 510 String nam = c.getName(); 511 return nam.substring(nam.lastIndexOf(".") + 1); 512 } 513 514 515 public static final InterpreterException asNativeException(ATObject atObj) throws InterpreterException { 516 if (atObj instanceof NATException) { 517 return ((NATException)atObj).getWrappedException(); 518 } 519 520 if (atObj instanceof JavaObject) { 521 JavaObject jObject = (JavaObject) atObj; 522 523 Object object = jObject.getWrappedObject(); 524 if (object instanceof InterpreterException) { 525 return (InterpreterException) object; 526 } else if (object instanceof Throwable) { 527 return new XJavaException((Throwable)object); 528 } 529 } 530 return new XAmbienttalk(atObj); 531 } 532}