The D.eval API and Language User's Guide
D.eval API: Documentation | Product Home

The D.eval API and Language User's Guide

Table of Content

Introduction
    Background
    The Software and Resources
    Introduction to the Supported Language
    Getting Started
The Language
    Variables
    Expressions
    Extended Logical Operator Keywords
    Using ActionScript 3 Classes
    Statements
    User-Defined Functions
    Using Flash Top-Level Classes and Functions
    Built-In Functions
The Runtime Environment and the API
    Using the D.eval API
    The Runtime Context
    Pre-Parse Dynamic Code and Libraries
    Logging and Program Output
    Pre-Importing Global Objects
Examples
    Simple Examples
    Display XML and Complex Data In DataGrid
    An "Expert System"
    Arbitrary Signal Generator
Using D.eval API in Flash Programs
Final Words

Introduction

Background

The eval() function in JavaScript and earlier versions of ActionScript™ was used to execute textual dynamic code at runtime. ActionScript™ 3 chooses to break away from this backward compatibility and deprecated eval(). Yet, some programmers and designers still feel strongly about its needs, including the following applications:

  1. Expression-driven programming.
  2. End-user scriptable environment.
  3. End-user defined conditions and actions.

Examples in this guide will illustrate such use cases.

Expression-driven programming is to treat executable code as data. Executable code can be passed to function calls, loaded from and saved to database tables, or entered by end users. Expression-driven programming can greatly simplify coding in some situations, and can do things impossible without. Let us see some use cases.

Use case 1: provide a single labelFunction for one or more DataGridColumn's, and use their dataField properties as expressions to access parts, aggregated or calculated value of the data.

Use case 2: a production-rule based expert system with end-user specified executable conditions and actions.

Use case 3: a end-user scriptable environment, such as a DOM system within a Flex™ application.

These use cases show that, dynamic code execution not only provides convenience to certain programming situations, but also opens up new architectural and design possibilities. The D.eval API fills this need for ActionScript™ 3. You simply invoke D.eval() in AS3 (hence the name of the API) as you would with eval() in JavaScript. The API is apparently straightforwad, and it supports a powerful language.

The Software and Resources

The D.eval API software is freeware. It consists of a 70K-byte pure-AS3 library, D.eval-?.?.swc, where ?.? is the version number, along with documentation and examples. The software is tested working with Adobe Flex&grade; 3 and Flash CS3™.

The TestRunner program, also included along with examples, is the unit testing platform for the D.eval API itself. The test cases are grouped into a number of categories; they are all compiled into a Flex module, so that more test cases can be compiled in without changing the tester program itself. You can run the test cases, and copy any of them into the code-runner section to edit and try out.

Most examples are for Flex programs, except the Deval Flash Tester application that is for Flash CS3 (source). The SimpleTester example (source) is great for experiementing with arbitrary dynamic code. The UseDynamicCodeLibrary example (source) is very similar to SimpleTester, except that it can take user-defined functions and use them in evaluating separately entered dynamic code. This mimics the use (or reuse) of user-defined dynamic code library.

Other examples demonstrate major usages of this library. The DevalDataGrid example (source) shows how to use expressions for DataGridColumn's dataField. For some reason, Adobe's MXML compiler does not accept expressions for DataGridColumn.dataField; to display a calculated value, you have to either provide a custome labelFunction for each calculation, or pre-calculate them in the data object; neither solution is satisfactory. The D.eval API solves this dilemma gracefully. The SignalGenerator example (source) is an interesting one. The program creates a data set to display in a chart by looping through angular (or time) increments; the user provides a function to calculate the amplitude for each point in time. This constitutes an arbitrary signal generator. A number of predefined functions are provided as examples. The ExpertSystem example (source) is perfect for using dynamic code, because user can provide executable conditions and actions for production rules. Not surprisingly, the "inference engine" in this example is extremely simple: it just does a one-pass scan of all conditions and fire matched rules; but the point is well made.

Some of these examples will be examined in this User's Guide in detail.

Introduction to the Supported Language

The D.eval API supports a powerful language, which is essentially a full JavaScript with some minor differences. The following list shows its major features:

  1. Supports all the operators and flow control statements of JavaScript/AS3.
  2. Supports all E4X operators and operations.
  3. Supports user function declaration.
  4. Supports the syntax of AS3-style type declarations for var and function return values and parameters. Type, however, is simply discarded.
  5. Supports import statement to import AS3 classes so that they can be used to new instances or call its static methods.
  6. All Flash top-level functions are available to dynamic code, and a number of built-in functions, including printf(), importFunction() and importStaticMethods().
  7. Provides comprehensive interaction with the hosting AS3 environment, with support at both the language and the API levels.
  8. Does not support JavaScript object-orientation afterthoughts such as prototype and JavaScript-style object constructors. In other words, the language does not support defining classes in any ways. Object usage, i.e., the dot-notation, is supported.
  9. Extended keyword support of and, or, not, xor, nand, and nor, useful for taking end-user input with more English-like logical expressions.

Getting Started

To use the D.eval API, all you have to do is import the r1.deval.D class and call D.eval(codeString).

Let us build a simple test run application that takes and executes user code. From there, we can experiment with the supported language. Its source code is listed below.

<?xml version="1.0" encoding="utf-8"?>
<mx:Application layout="absolute" xmlns:mx="http://www.adobe.com/2006/mxml"
  creationComplete="D.setTextControlOutput(stdout)">

<mx:Script><![CDATA[
  import r1.deval.D;

  private function doRun():void {
    var result:Object = D.eval(srcCode.text);
    D.display("D.eval RESULT: " + result);
  }
]]></mx:Script>

  <mx:HDividedBox width="100%" height="100%">
    <mx:TextArea id="stdout" width="40%" height="100%" />
    <mx:VBox width="60%" height="100%">
      <mx:TextArea id="srcCode" width="100%" height="100%">
<mx:text><![CDATA[// sample code
arr = [ "a", 9, new Date ];
for each (x in arr)
  printf(x)
]]></mx:text>
      </mx:TextArea>
      <mx:Button label="Run" click="doRun()" />
    </mx:VBox>
  </mx:HDividedBox>

</mx:Application>

This is the typical way of using the D.eval API: prior to using D.eval, set the output destination to receive parse and runtime output. Then, when the user dynamic code is available, call D.eval(). In this example, we do not have too much to pass to and from the dynamic code, so the call is as simple as it gets. A screenshot is shown here:


The Language

The language supported by the D.eval API is essentially a full JavaScript with some minor differences. Its major features are listed before. This chapter explains the language features in detail.

Variables

Variables do not have to be declared with var before use. All variables are dynamically typed; you can specify a type for a variable in var but that type is ignored.

Expressions

All AS3's expressions are supported, including literals and E4X expressions.

Literal syntax includes null, this, boolean, integer, number, string, object, array and E4X objects. The this references the "this" object as the second parameter passed from the D.eval(codeString, context, thisObject). The super keyword in AS3 is not supported.

Extended Logical Operator Keywords

In addition to JavaScript/AS3 operators, the dynamic language supports these extended logical operator keywords: and, or, not, nand, nor, and xor. They are useful for taking end-user input with more English-like logical expressions:

var sick = false;
var young = true;

if (young and not sick)
  premium = "low";

Using ActionScript 3 Classes

Like in AS3, a class must first be imported via the import statement before it can be used. Unlike AS3, only fully qualified class names are accepted; no wildcard characters are allowed. Also, the AS3 classes to be used by the dynamic code must be present at runtime. You can import multiple classes in a single statement.

Once a class is imported, it can be used just like in AS3. The class name without the package part is used; classes can be assigned to variables; you can create instances of a class, and call static methods of that class. For instance:

import flash.display.Sprite, mx.utils.StringUtil;

var mySprite:Sprite = new Sprite;

printf( StringUtil.trim('   abc   ') );

Statements

All AS3 statements are supported in the dynamic language with a handful of exceptions. The following table lists supported statements and directives.

ActionScript 3 Dynamic Language Comment
break breakSame as AS3
case caseSame as AS3
continue continueSame as AS3
default defaultSame as AS3
do..while do..whileSame as AS3
else elseSame as AS3
for forSame as AS3
for..in for..inSame as AS3
for each..in for each..inSame as AS3 except that, when var is used to declare the variable, type is not allowed; this is different from the rest of the language.
if ifSame as AS3
return returnSame as AS3
switch switchSame as AS3 and JavaScript; the case values can be any expressions and are not necessarily constants.
throw throwSame as AS3
try..catch..finally  Not supported (but may be supported in the future.)
while whileSame as AS3
with  Not supported.
default xml namespace default xml namespaceSame as AS3
import importSupported to import AS3 classes that are avaiable at the time of running.
include  Not supported.
use namespace  N/A

 

User-Defined Functions

Functions can be defined dynamically, too. The syntax is exactly the same as in AS3, but the type information for parameters and return type is simply discarded. But notice! These functions are not the same as AS3 functions! Therefore, they can not be returned to the calling AS3 code nor be passed as parameters to imported AS3 functions (see below). User-defined functions can be assigned to variables or passed as parameters to other user-defined functions, like so:

function inc(x) {
  return x+1
}
function dec(x) {
  return x-1
}
function delegate(x:int, f:Function):int {
  return f(x)
}

delegate(5, dec)

Using Flash Top-Level Classes and Functions

Flash top-level classes and functions can be used exactly like in AS3. For example,

var date = new Date(1987, 3, 5);

var value = escape('cond=age<50 and age>30');

var num = Number('1234');

trace('TRACE from dynamic code.');

Built-In Functions

The dynamic language includes a few built-in functions.

function printf(msg_fmt, ...):void

This function prints the message to the system's output display. See Logging and Program Output for more discussion. The first parameter can be a string containing parameter indicators like {0}, {1}, etc.; they are replaced by values of the following parameters.

function importFunction(name, theFunction):void

This function imports a function, typically a class static method, to be globally invokable. For instance:

import mx.utils.StringUtil;
importFunction('trim', StringUtil.trim);

printf( trim('    abc    ') ); // call the imported function

function importStaticMethods(cls, criteria:*=null):void

This function imports all the static methods of cls, a class object, that match the criteria. If criteria is null, all static methods are returned; otherwise, the criteria can be either a RegExp object or an array of strings. For instance:

importStaticMethod(Math, [ 'sin', 'cos' ]);

printf( sin(0.12) ); // call the imported function

The Runtime Environment and the API

Using the D.eval API

The API consists of just one class, r1.deval.D; all of its methods are static. The class documentation is the most accurate. In this section, we discuss a few specific points.

The Runtime Context

The D.eval(program, context, thisObject) method is the centerpiece of the API. Its program parameter normally is a String of program code, but can also be an executable object returned by the D.parseProgram() method. The return value of D.eval() is that of either a return statement or the last expression executed.

In the language, variables, classes and functions are all named entities. When retrieving a named entity, the order of their resolution is: local scope chain, this object (if present), the context object and finally, the global context. The global context keeps global objects such as system functions, classes and imported objects like classes and functions. When setting a variable with an unqualified name, the same resolution chain is followed; if a settable value is found, the value is set therein; otherwise, a new variable is created in the current (local) scope.

The variable declaration with the keyword var declares a variable in the current scope. The top level scope is the context object passed in to the D.eval(program, context, thisObject) call.

The thisObject and context parameters provides more sophisticated communication between the dynamic code and the hosting environment. The thisObject is referenced by the this. The context object can hold prepolulated values as named variables accessible by the dynamic code. Often times, the context is omitted and thisObject is to pass a value object on entry. The top-level variables in the dynamic code are stored in the context object. To set new values in thisObject, use the this. On exit, values set in either object can be used by the calling AS3 code.

So why we need these two parameters rather than a single one? The context is needed when a) the thisObject is not a dynamic AS3 class instance, and b) you intend to use the values set by the dynamic code. This is probably not happening very often, but it may.

Let us see some examples. The following code passes a values to the dynamic code as variable named "fruit", "price", and "count".

<?xml version="1.0" encoding="utf-8"?>
<Application xmlns="http://www.adobe.com/2006/mxml"
  creationComplete="test()">
<Script><![CDATA[
import r1.deval.D;

function test():void {
  var code:String =
    'switch(fruit) {\n' +
    '  case "apple": return "sweet";\n' +
    '  case "lemon": return "sour";\n' +
    '  default:      return "unknown";\n' +
    '}';
  var taste:String = D.eval(code, { fruit:'apple' });

  var purchase:Object = { price:3.95, count:12 };
  var cost:Number = D.evalToNumber('price * count', purchase);
}
]]></Script>
</Application>

We will use this program structure for other simple tests, where only the test() function code will be present.

The next example demonstrates receiving values from the dynamic code and use them in the hosting environment.

function test():void {
  var code:String =
    'this.name = "Ron";\n' +
    'this.age=50;\n' +
    'var strategy = "plan A";\n' +
    'var premium = 2000;';

  var thisObj:Object = {};
  var context:Object = {};

  D.eval(code, context, thisObj);

  trace("this.name:        " + thisObj.name);
  trace("this.age:         " + thisObj.age);
  trace("context.strategy: " + context.strategy);
  trace("context.premium:  " + context.premium);
}

Pre-Parse Dynamic Code and Libraries

If a piece of dynamic code is repeatedly used, it makes good sense to pre-parse it, because parsing is a computationally intensive process and there is no point to parse the same code again and again. The first parameter of D.eval() can be an executable code object produced by the D.parseProgram() call.

By default, for small programs, the D.eval API caches the parsed executable object. You can call D.useCache() to change this behavior.

Functions declared in the dynamic code are stored like named variables within the context object. You can retrieve them by a) explicitly passing a context object to the eval() call, and b) on exit, call the helper method, D.collectUserFunctions(), to cleanse non-function data in the context object. You can reuse these functions by passing that object as context to the following eval() calls. This effectively enables a library mechanism of dynamically declared functions. You can also call the D.parseFunctions() to simply retrieve the function declarations, as in this example:

function test():void {
  // Compile the user library:
  var libCode:String =
    'function sum(data:Array):Number {\n' +
    '  var ret:Number = 0;\n' +
    '  for each(var x in data) ret += x;\n' +
    '  return ret;\n' +
    '}\n\n' +
    'function avg(data:Array):Number {\n' +
    '  return (data == null) ? 0 : (sum(data) / data.length);\n' +
    '}';
  var userlib:Object = D.parseFunctions(libCode);

  // Use the library to execute dynamic code:
  var prog:String =
    'var data = [ 1, 3, 5, 7 ];\n' +
    'printf("Sum is:     {0}", sum(data));\n' +
    'printf("Average is: {0}", avg(data));\n';
  D.eval(prog, null, userlib);
}

Logging and Program Output

The D.eval API parser and runtime engine may print out error and warning messages. The dynamic language includes a built-in function, printf(), allowing the dynamic code to print out user messages as well. The D.eval API uses a user-function, assigned via D.setOutput(f:Function). By default, that destination is trace(). The D provides a convenience method to use a text control (such as <mx:TextArea>) to write out multiple lines of messages, via the D.setTextControlOutput(host:Object, prop:String, limit:int) method call. The following is a typical setup for D.eval:

import r1.deval.D;

// creationComplete handler.
private function init():void {
  // stdout is a <mx:TextArea>
  D.setTextControlOutput(stdout);
}

Pre-Importing Global Objects

The D.importClass(), D.importFunction() and D.importStaticMethods() methods allow you to programmatically import these objects into the global scope for Deval execution. In the dynamic code, the import statement and importFunction() and importStaticMethods() functions simply invoke those methods and behave the same way.

Because all global objects share the same namespace, name collisions can happen. You can use the setOverrideGlobalOption() function with constants OVERRIDE_GLOBAL_OVERRIDE, OVERRIDE_GLOBAL_WARN or OVERRIDE_GLOBAL_ERROR to indicate name collision resolution. By default it is OVERRIDE_GLOBAL_OVERRIDE.


Examples

Let us see some examples of using the D.eval API.

Simple Examples

Run simple expressions. D.eval returns the value of the last expression execution if no return statement is run; hence, running simple expressions can not be easier:

var six:int = D.eval('1 + 5') as int;

Run expressions with parameters. By setting values to a this/context object, you can achieve parameterized expressions:

function dynaExpr(x:*, y:*, expr:String):* {
  return D.eval(expr, {x:x, y:y});
}

// Use it:
trace( dynaExpr(1, 5, 'x + y') );

Import classes through API. AS3 classes can be used in dynamic code to create new instances or invoke their static methods. Before AS3 classes can be used, they must first be imported. There are two ways to import classes, programmatically through the API and use the import statement in the dynamic code. If all possible, import through API to ensure the used AS3 classes are indeed linked in to the executable swf file. The AS3 compiler may not include classes not explicitly used in the code; when you use the import directive in the dynamic code to load AS3 classes that are not embedded or loaded, you will get runtime errors. The following example AS3 code uses API to import classes.

import my.invention.MyClass;

D.importClass(MyClass);

var obj:MyClass = D.eval('new MyClass(1, 2, 3)') as MyClass;

Import classes in the dynamic code. Assuming your class is available at runtime, you can dynamically import that class like so:

var prog:String = 'import my.invention.MyClass;\n' +
                  'new MyClass(1, 2, 3)';
var obj:MyClass = D.eval(prog) as MyClass;

Import functions through API.

AS3 functions can be called from the dynamic code, but they must be imported first either through the API or dynamically in the code. Flash top-level functions, however, can be used directly. The following AS3 code uses the API to import classes.

import mx.utils.StringUtil;

D.importFunction('trim', StringUtil.trim);

trace( D.evalToString('trim("   abc  ")') );

You can import all the static methods of a class with a single call:

import mx.utils.StringUtil;

D.importStaticMethods(StringUtil);

trace( D.evalToString( 'trim("   abc  ")' ) );
trace( D.evalToString( 'substitute("Hello, {0}!", "World")' ) );

Import functions in dynamic code. The following example AS3 code dynamically imports and uses functions:

var prog:String =
  'import mx.utils.StringUtil;\n' +
  'importStaticMethods(StringUtil);\n' +
  'trim("    abc  ");\n' +
  'substitute("Hello, {0}!", "World")';
trace( D.evalToString(prog) );

Display XML and Complex Data In DataGrid

This is the case where DataGrid can use D.eval API to gracefully display calculated or manipulated values. The crux of the issue is that, for some reason, Adobe's MXML compiler does not accept expressions for DataGridColumn.dataField; to display a calculated value, you have to either provide a custome labelFunction for each calculation, or pre-calculate them in the data object; neither solution is satisfactory. With D.eval API, all you have to do is create and use a generic label function like this in those DataGridColumn's:

protected function showData(row:Object, col:DataGridColumn):String {
  return String(D.eval(col.dataField, row));
}

A generic DataGridColumn-derived class can be developed. This class is thin and easy. Why the D.eval API does not define such a class? Only because the D.eval API is intended as a pure AS3 library without any bearing on the Flex framework. The following is a complete example using this technique.

<?xml version="1.0" encoding="utf-8"?>
<mx:Application layout="absolute"
  xmlns:mx="http://www.adobe.com/2006/mxml">

<mx:XML id="order">
  <order>
    <item id='1'>
      <menuName>burger</menuName>			
      <criticalInfo>
        <vegetarian>false</vegetarian>
        <containsPeanuts>false</containsPeanuts>
      </criticalInfo>
      <salesInfo>
        <quantity>3</quantity>
        <price>3.95</price>
      </salesInfo>
    </item>
    <item id='2'>
      <menuName>fries</menuName>
      <criticalInfo>
        <vegetarian>true</vegetarian>
        <containsPeanuts>false</containsPeanuts>
      </criticalInfo>
      <salesInfo>
        <quantity>4</quantity>
        <price>1.45</price>
      </salesInfo>
    </item>
  </order>
</mx:XML>

<mx:Script><![CDATA[
  import mx.controls.dataGridClasses.DataGridColumn;
  import r1.deval.D;

  protected function showData(row:Object, col:DataGridColumn):String {
    return String(D.eval(col.dataField, row));
  }
]]></mx:Script>

<mx:DataGrid dataProvider="{order.item}" width="450">
  <mx:columns>
    <mx:DataGridColumn headerText="Name"
      dataField="menuName" labelFunction="showData"/>
    <mx:DataGridColumn headerText="Vegetarian"
      dataField="criticalInfo.vegetarian" labelFunction="showData"/>
    <mx:DataGridColumn headerText="Peanuts"
      dataField="criticalInfo.containsPeanuts" labelFunction="showData"/>
    <mx:DataGridColumn headerText="Quantity"
      dataField="salesInfo.quantity" labelFunction="showData"/>
    <mx:DataGridColumn headerText="Price"
      dataField="salesInfo.price" labelFunction="showData"/>
    <mx:DataGridColumn headerText="Line Total"
      dataField="salesInfo.price * salesInfo.quantity" labelFunction="showData"/>
  </mx:columns>
</mx:DataGrid>

</mx:Application>

An "Expert System"

Production-rule-based expert systems store human knowledge in the form of if-then rules, and produces "intelligent" results for given input using its innate inference engine. The if-conditions are boolean expressions, and the then-actions can be anything from a piece of message that is understood by machine or human to fully executable programs. What a perfect use case for the D.eval API! In addition, the conditions would be a perfect place to use the extended logical operator keywords like and, or, not, etc.

The example only reflects the condition and action aspect of a real expert system; its inference engine does not quite live up to its name yet — it only does a one-pass scan of all rule conditions and fire the matched rules. The UI has two parts: the first one allows user to edit the knowledge base of conditions and actions; the second takes user input and applies the knowledge. The following screenshots show both.

The source code follows.

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
  width="100%" height="100%" creationComplete="D.setTextControlOutput(stdout)">

<mx:Script><![CDATA[
  import r1.deval.D;

  [Bindable] private var rules:Array = [  // { name, cond, action }'s.
    { name: 'Young',  cond: 'age < 40', action: '"Is young."' },
    { name: 'Mid-Age', cond: 'age >= 40 and age < 60', action: '"Is middle-aged."' },
    { name: 'Old', cond: 'age >= 60', action: '"Is old."' }    
  ];

  [Bindable] private var attrs:Array = [ // { attr, value }'s.
    { attr: 'age',  value: 40, type: 'number' }
  ];

  private function setCurrentRule():void {
    var rule:Object = rulesList.selectedItem;
    ruleName.text = rule.name;
    ruleCond.text = rule.cond;
    ruleAction.text = rule.action;
  }

  private function updateRule(asNew:Boolean=false):void {
    var rule:Object = {
      name: ruleName.text,
      cond: ruleCond.text,
      action: ruleAction.text
    }

    var a:Array = rules == null ? [] : rules;
    if (asNew)
      a.push(rule)
    else
      a[rulesList.selectedIndex] = rule;

    rules = null;
    rules = a;
  }

  private function addAttr():void {
    var a:Array = (attrs == null) ? [] : attrs;
    a.push({ attr: "(EDIT THIS)", type: "number", value: "(EDIT THIS)" });
    attrs = null;
    attrs = a;
  }

  private function evaluate():void {
    // Collect all attributes and set it to x:
    var x:Object = {};
    var o:Object;
    D.display("Attributes:");
    for each(o in attrs) {
      if (o.attr != '' && o.attr != '(EDIT THIS)') {
        D.display("  " + o.attr + " = " + o.value);
        switch(o.type) {
        case 'number':  x[o.attr] = Number(o.value); break;
        case 'boolean': x[o.attr] = Boolean(o.value); break;
        case 'string':  x[o.attr] = String(o.value); break;
        default:        x[o.attr] = o.value; break;
        }
      }
    }

    for each(o in rules) {
      if (D.evalToBoolean(o.cond, x)) {
        D.display('\n[Match: "' + o.name + '"]');
        var result:Object = D.eval(o.action, x);
        if (result != null)
          D.display("  Returned: " + result);
      }
    }
    D.display("\n---------------------------------\n");
  }
]]></mx:Script>

  <mx:TabNavigator width="100%" height="100%" selectedIndex="1">

    <mx:HBox label="Define Rules" width="100%" height="100%">
      <mx:List id="rulesList" labelField="name" width="40%" height="100%"
        dataProvider="{rules}" change="setCurrentRule()" />
      <mx:Form label="Production Rule Details" width="60%" height="100%">
        <mx:FormItem label="Name:" width="100%">
          <mx:TextInput id="ruleName" width="100%"/>
        </mx:FormItem>
        <mx:FormItem label="Condition:" width="100%">
          <mx:TextArea id="ruleCond" width="100%" height="100"/>
        </mx:FormItem>
        <mx:FormItem label="Action:" width="100%">
          <mx:TextArea id="ruleAction" width="100%" height="100"/>
        </mx:FormItem>
        <mx:FormItem direction="horizontal">
          <mx:Button label="Update" click="updateRule()"
            enabled="{rulesList.selectedIndex >= 0}" />
          <mx:Button label="Add As New" click="updateRule(true)"/>
        </mx:FormItem>
      </mx:Form>
    </mx:HBox>

    <mx:HBox label="Apply Knowledge" width="100%" height="100%">
      <mx:VBox width="60%" height="100%">
        <mx:HBox width="100%" paddingLeft="10" paddingRight="10">
          <mx:Label text="Attributes:"/>
          <mx:Spacer width="100%"/>
          <mx:Button label="New Attribute" click="addAttr()"/>
        </mx:HBox>
        <mx:DataGrid id="attributes" width="100%" height="100%"
          dataProvider="{attrs}" editable="true">
        <mx:columns>
          <mx:DataGridColumn headerText="Attribute" rendererIsEditor="true"
            itemRenderer="mx.controls.TextInput"
            dataField="attr" editorDataField="text"/>
          <mx:DataGridColumn headerText="Type" rendererIsEditor="true"
            dataField="type" editorDataField="selectedItem" width="90">
            <mx:itemRenderer>
              <mx:Component>
                <mx:ComboBox>
                  <mx:Array>
                    <mx:String>number</mx:String>
                    <mx:String>boolean</mx:String>
                    <mx:String>string</mx:String>
                  </mx:Array>
                </mx:ComboBox>
              </mx:Component>
            </mx:itemRenderer>
          </mx:DataGridColumn>
          <mx:DataGridColumn headerText="Value"
            itemRenderer="mx.controls.TextInput" rendererIsEditor="true"
            dataField="value" editorDataField="text"/>
        </mx:columns>
        </mx:DataGrid>
      </mx:VBox>
      <mx:VBox width="40%" height="100%">
        <mx:Button label="Evaluate" click="evaluate()" />
        <mx:TextArea id="stdout" width="100%" height="100%" />
      </mx:VBox>
    </mx:HBox>

  </mx:TabNavigator>

</mx:Application>

Arbitrary Signal Generator

Let us do something more fun. We will create an arbitrary signal generator in Flex! It looks like:

This signal generator takes the function code that you enter and your samples/step selections and creates an array of numbers which is set to the chart's dataProvider. Your code is responsible for generating the signal sample value at a particular moment which is represented in degrees. A number of sample functions are provided in the drop-down list.

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
  layout="absolute" backgroundColor="#FFFFFF" creationComplete="init()">

<mx:Script><![CDATA[
  import r1.deval.D;

  private function init():void {
    examples.dataProvider = [
      { label: 'sinusoidal',
        code:  'importStaticMethods(Math, ["sin", "cos"]);\n\n' +
               'r = degrees * Math.PI/180;\n' +
               '1.5 * sin(r) + 0.6 * cos(r*3)'
      },
      { label: 'noise',
        code:  'Math.random() * 2 - 1'
      }, 
      { label: 'triangular',
        code:  'period = 64;\n' +
               'period_4 = period / 4;\n' +
               'period_3_4 = period - period_4;\n\n' +
               'x = degrees % period;\n' +
               'if (x < period_4) return x;\n' +
               'if (x < period_3_4) return period/2 - x;\n' +
               'return x - period;'
      },
      { label: 'square',
        code:  'period = 64;\n' +
               'period_2 = period / 2;\n\n' +
               'x = degrees % period;\n' +
               'return (x < period_2) ? 1 : -1;'
      }
    ];
    examples.selectedIndex = 0;
    pickExample();
  }

  private function pickExample():void {
    fxn.text = examples.selectedItem.code;
    doGen();
  }

  private function doGen():void {
    var _samples:int = samples.selectedItem as int;
    var _step:int = step.selectedItem as int;
    var prog:Object = D.parseProgram(fxn.text);

    // Generate the signal sample values:
    var signal:Array = [];
    for (var i:int=0; i<_samples; ++i)
      signal.push(Number(D.eval(prog, {degrees:i*_step})));

    // Display them:
    oscilloscope.dataProvider = signal;
  }
]]></mx:Script>

  <mx:HBox width="100%" height="100%">
    <mx:VBox width="50%" height="100%" paddingLeft="10"
      paddingRight="10" paddingTop="10" paddingBottom="10">
      <mx:Label text="Arbitrary Signal Generator" fontSize="16" fontWeight="bold"/>
      <mx:HBox>
        <mx:Label text="Samples:"/>
        <mx:ComboBox id="samples" selectedIndex="1"
          dataProvider="{ [90,180,360,720] }" change="doGen()"/>
        <mx:Label text=" Step:"/>
        <mx:ComboBox id="step" selectedIndex="1"
          dataProvider="{ [1,2,4,8] }" change="doGen()"/>
      </mx:HBox>
      <mx:HBox width="100%">
        <mx:Label text="Function: (Use 'degrees' as input)"/>
        <mx:ComboBox id="examples" change="pickExample()"/>
      </mx:HBox>
      <mx:TextArea id="fxn" width="100%" height="100%"/>
    </mx:VBox>
    
    <mx:VBox width="50%" height="100%">
      <mx:AreaChart id="oscilloscope" width="350" height="250" paddingRight="10">
        <mx:series><mx:AreaSeries/></mx:series>
      </mx:AreaChart>
      <mx:Button label="Refresh" click="doGen()"/>
    </mx:VBox>
  </mx:HBox>

</mx:Application>

Using D.eval API in Flash Programs

The D.eval API version 1.1 is made a pure ActionScript 3 library and works with both Adobe Flex and Flash programming. To use it in Flash CS3, the development environment for making Flash programs, you need to drop the D.eval API SWC file into Flash CS3's components directory; on Windows, this directory is C:\Documents and Settings\username\Local Settings\Application Data\Adobe\Flash CS3\en\Configuration\Components\. Then, within Flash CS3, a component named D will show up in the components panel. Drag an instance into the stage to make it available to your .fla file. The rest of thethe programming is the same as described above.

A sample .fla is included in the samples/flash_test/ directory, as well as its executable .swf file. It looks like this:

In this program, the two multi-line text boxes are named input_txt and output_txt, respectively. The relevant code is like this:

import r1.deval.D;

D.setTextControlOutput(output_txt);

run_btn.addEventListener(MouseEvent.CLICK,
   function(e:Event):void { D.eval(input_txt.text) });

Final Words

The goal of the D.eval API is straightforward: to dynamically execute AS3 expressions and algorithms. However, the API's design and implementation go beyond that — it sports a full-blown embedded JavaScript language within ActionScript™ 3, supporting all JavaScript/AS3 expressions, flow-control statements and user-defined functions.

The D.eval API and the supported dynamic language work hand-in-hand for executing dynamic code at runtime, allow sophisticated data exchange between custom dynamic code and the hosting AS3 environment.

The capability to do dynamic programming opens a whole new world to your Flex™ 3 and Flash™ application design and development!



Copyright 2007,2008 RiaOne Company. All Rights Reserved.