Eval in AS3: Tips for Executing Dynamic Actionscript


By daniel - Posted on 21 September 2008

If you miss the eval method from AS2, the AS3 Eval library from Hurlant is what you're looking for. I found there were extras I needed so I created a wrapper class which you might find useful.

It's not too often that one needs to execute a block of Actionscript which is unknown at compile time. However, in recent weeks I found myself working on a project that allows the user to enter Actionscript code to translate data values to style properties, not unlike translating HTML using CSS.

A more common use for this sort of thing might be a plotting program in which users enter formulas that they would like to see graphed.

Without too much searching you'll probably find that the best tool for this is the AS3 Eval Library at Hurlant. There is no eval method in AS3 but this project, in version 0.3 at time of writing, makes a great substitute.

Still, there were some additional things I wanted from the library and a quick wrapper class helped me achieve them. Here's what I was looking for:

  • Some way to handle execution errors
  • An event indicating that execution is complete
  • A clean way to pass data to and from the scope of the evaluation
var evaluator:Evaluator = Evaluator.getInstance();
evaluator.addEventListener(Event.COMPLETE,
  evaluatorCompleteHandler, false, 0, true);
evaluator.addEventListener(ErrorEvent.ERROR,
  evaluatorErrorHandler, false, 0, true);
 
var expression:String =
  "data.value3 = data.value + data.value2;";
 
var data:Object = 
{
  value: 42,
  value2: 127,
  value3: null
};
 
evaluator.eval(expression, data);
package
{
import com.hurlant.eval.ByteLoader;
import com.hurlant.eval.Evaluator;
 
import flash.display.Loader;
import flash.events.ErrorEvent;
import flash.events.Event;
import flash.events.EventDispatcher;
import flash.system.LoaderContext;
import flash.utils.ByteArray;
 
public class Evaluator extends EventDispatcher
{
  protected static var _currentID:int = 0;
  protected static var _evaluatorCache:Array = new Array();
 
  protected var _id:int;
  protected var _data:Object;
 
  public function Evaluator()
  {
    _data = new Object();
  }
 
  public function get id():int
  {
    return _id;
  }
 
  public static function getInstance(id:int=-1):Evaluator
  {
    if (id >= 0)
    {
      return _evaluatorCache[String(id)];
    }
 
    var evaluator:Evaluator = new Evaluator();
    id = evaluator._id = _currentID++;
 
    _evaluatorCache[String(id)] = evaluator;
 
    return evaluator;
  }
 
  public function get data():Object
  {
    return _data;
  }
 
  public function eval(expression:String, data:Object=null):void
  {
    _data = data;
 
    // wrap the expression in a function to tighten the scope a bit
    expression = "function __eval(evaluator:Evaluator, data:Object)\n" + 
      "{\n" + 
      "\ttry\n" + 
      "\t{\n" +
      "\n" + expression + "\n\n" +
      "\t}\n" + 
      "\tcatch (e:Error)\n" + 
      "\t{\n" + 
      "\t\t__evaluator.handleError(e);\n" +
      "\t}\n" + 
      "}\n" +
      "\n" + 
      "var evaluator:Evaluator = " +
      "Evaluator.getInstance('" + _id + "');\n" + 
      "var data:Object = evaluator.get data();\n" + 
      "__eval(evaluator, data);\n" + 
      "evaluator.handleComplete();\n";
 
    var evaluator:com.hurlant.eval.Evaluator =
      new com.hurlant.eval.Evaluator();
    var bytes:ByteArray = evaluator.eval(expression);
    bytes = ByteLoader.wrapInSWF([bytes]);
 
    var context:LoaderContext = null
    var loader:Loader = new Loader();
 
    loader.loadBytes(bytes, context);
  }
 
  public function handleError(e:Error):void
  {
    var message:String = e.errorID + "," + e.name + "," + e.message; 
    dispatchEvent(new ErrorEvent(
      ErrorEvent.ERROR, false, false, message));
  }
 
  public function handleComplete():void
  {
    dispatchEvent(new Event(Event.COMPLETE));
  }
}
}

Note that version 0.3 doesn't support packages so the Evaluator needs to be a top-level class.