BISE Scripting Engine

Yoshihiro Shindo, an Actionscript programmer and tinkerer who runs the website BeInteractive!, put out a Flash scripting engine in 2006.

The AS2-based engine could run a subset of ECMAScript, and it allowed users to write interpreted scripting for Flash games or applications. It also had some useful features, such as coroutines, a type of function that had the ability to suspend in the middle of its scripting.

Download

The AS2 engine is still available at his website.

The AS3 port hosted here is a straightforward conversion to the new version of Actionscript, and also fixes some bugs and adds a feature or two.

License

BISE is licensed under the MIT license.

Copyright (c) 2008 Yoshihiro Shindo and Sean Givan

Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the “Software”), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:

The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.

Documentation

Basic Use

Start a new Flash project. Put the ‘scripting’ folder from the ZIP file in the same directory as your FLA.

Write the following in your Actionscript window:

import scripting.*;

var codestring:String = "var x = 10; var y = x + 20;" ;

var s:Scanner = new Scanner(codestring);
var p:Parser = new Parser(s);

var vm:VirtualMachine = new VirtualMachine();

try
{
	vm.setByteCode(p.parse());
}
// If an error occurs here, it is likely a syntax error in the source code.
catch (vme:VMSyntaxError)
{
	trace(vme.message);
}

var result = vm.execute();

Run the program, and inside the virtual machine, the variable ‘y’ will be set to 30.

However, this is useless if we can’t see it. Before you run vm.execute(), let’s hook up the VirtualMachine to the outside world by letting it use the trace statement;

import scripting.*;

var codestring:String = "var x = 10; var y = x + 20; trace(y);" ;

var s:Scanner = new Scanner(codestring);
var p:Parser = new Parser(s);

var vm:VirtualMachine = new VirtualMachine();

vm.getGlobalObject().trace = trace;

try
{
	vm.setByteCode(p.parse());
}
catch (vme:VMSyntaxError)
{
	trace(vme.message);
}

var result = vm.execute();

By attaching objects or functions to the properties of the VirtualMachine’s global object, you let the scripting engine make use of those objects or functions. Now, when you run this, the output window of your Flash will report ’30’.

Objects, including library objects, can be attached as well:

vm.getGlobalObject().player = myplayerobject;
// This will enable Math.floor, Math.sin, etc.. for your scripting engine
vm.getGlobalObject().Math = Math;

What is result used for? Result contains the exit code of the scripting program being run. If vm.execute returns false, then the program ran to the end. If it returns true, then the program is still in progress, and was suspended (see below).

Language

The scripting language is a mostly faithful copy of ECMAScript from the ActionScript 2 era. It supports

  • variables
  • arrays
  • objects and properties
  • if-tests
  • while loops and for loops
  • break and continue
  • with blocks

Note that not all features are included; it does not implement

  • try/catch/finally
  • for.. in loops
  • the new operator

Note that while the new operator is not included, new arrays and objects can be created by the code:

var newarray = [];
var newobject = {}; 

New Features

The scripting language also includes some special language features.

  • loop statement
  • suspend/yield
  • coroutine type functions

The loop statement is a simple infinite loop. Note that by itself, that’s not a useful feature; but it’s meant to be combined with the other new language features, such as suspend.

loop { updateGameScripting(); suspend; } 

Suspend (or yield, which is a synonym) will pause the virtual machine and cause vm.execute() to exit with a return code of true. If vm.execute() is then called again, the program will pick up where it left off and continue to run.

Here is an example how you can use suspending to make routines that get further input from the player.

// Code in your Flash project
function askquestion(question)
{
	// Assume that these are custom movie clips being manipulated
	questiondialog.questionfield.text = question;
	questiondialog.visible = true;
	questiondialog.okbutton.addEventListener('click',onAnswerQuestion);
}

function onAnswerQuestion(e:Event)
{
	questiondialog.visible = false;
	vm.getGlobalObject()._engineanswer = questiondialog.answerfield.text;
        // start up the paused scripting engine
        vm.execute();
}

vm.getGlobalObject()._engineaskquestion = askquestion;
 // Scripting code coroutine getinput(question) { _engineaskquestion(question); suspend; // Now that we are back, onAnswerQuestion in the main Flash // has already set _engineanswer for us. return _engineanswer; }
 // Note the use of underscores in the "engine" functions and variables. // This is mainly to disguise the inner workings of your scripting from modders; // More seriously-written code would preparse what a modder writes, for instance to keep // them from overwriting or redeclaring important variables, or using suspend when // the engine wouldn't know how to resume the code. // Of course, if the scripting engine is entirely for the programmer's use, they // can name things how they please. 
var playername = getinput("What is your name?"); 

There are some important limitations to when and where you can suspend a scripting program.

  • You can suspend code on the top level of your script (i.e. not inside any functions.)
  • You can not suspend code that is inside a function. A VM function is essentially a wrapped Actionscript function that asks the virtual machine to process some bytecodes. (Note that this is why it’s so easy to attach external functions to the VM’s global object – because VM and AS functions are peers.) However, much like how you can’t stop an AS function midway through, you can’t stop VM code that’s written as a function.
  • However, if instead of a function, you use a coroutine, you can suspend inside the coroutine. A coroutine is a special Object, not a wrapped function, that the VM is capable of suspending.
  • Finally, if you want to suspend a coroutine, you must not have called that coroutine from inside a function. There needs to be a chain of coroutines from the top on down, if you want to be able to suspend.

If the rules here are a bit too complex (for either you, or more importantly, for modders), just use coroutines exclusively instead of functions when writing code where you might need to stop for input. Alternately, use this command:

var s:Scanner = new Scanner(codestring);
var p:Parser = new Parser(s);
p.setForceCoroutine(true);

setForceCoroutine, when used, will cause any reference to a function to be compiled to a coroutine instead, simplifying the issue.

Reusing Compiled Code

Once the virtual machine has executed once, you can use the compiled code as a library of functions to call on freely.

If you want to call on a compiled function, simply access the global object and use that function’s name.

vm.getGlobalObject().mycompiledfunction(myarg,myarg2);

If you want to call on a coroutine, use runCoroutine instead, and pass it a string of the coroutine’s name, and an optional Array of arguments.

vm.runCoroutine("mycoroutinename",[myarg1,myarg2]);

Note that you should be sure to run execute first to be certain that these functions and coroutines are moved from the source code to the VM’s global object.

If your functions and coroutines change the values of variables at the global level of the code, those changes will be preserved from call to call.

External Suspending

If you are using runCoroutine to run code, it is possible to suspend the scripting engine from regular Actionscript.

If you call vm.suspend() from an external function that the scripting engine has called, the scripting engine will exit and pause after it returns from the function.

Note that if the Actionscript function returns a value, it will not be used by the script. To return a value to the script when resuming, call the execute function with a single argument – the argument being what you want to return.

Optimizing

It is possible to optimize the code you compile, by passing an optional argument to Parser.parse() – the virtual machine that you are targeting.

vm.setByteCode(p.parse(vm));

If you do this, the array of bytecodes that parse() produces cannot be used in any other virtual machine. However, the code will run with an estimated 40% speed boost.

Dynamic Scope Variables

A dynamic scope variable is a global variable that keeps copies of its value through function calls. For instance, if a dynamic scoped variable x had a value of 10, and it was changed to 20 inside a function call, it would remain 20 while inside the function, and then return to 10 once the function exited.

This scripting engine has limited support for dynamic scope variables. The VirtualMachine object has a public array called registeredDynamicScopeVars. Before your code begins execution, you can pass a list of Strings to the array, representing the names of global variables you want to have dynamic scope.

The scripting engine will track and manage the values of those variables as it enters and exits functions. Note that the engine cannot track what happens to those variables during an external function call.

30 thoughts on “BISE Scripting Engine

  1. Yes, something like this should work:

    
    // Assume trace is defined elsewhere
    
    var newobject = { };
    newobject.test = function () { trace("Hello World"); };
    newobject.test();
    
    

    Alternately,

    
    function proto_test()
    {
    trace("Hello World");
    }
    
    var newobject = {};
    newobject.test = proto_test;
    newobject.test();
    
  2. hey, awesome tutorial!

    is it possible to send bytecode to VM for couple of times but preserving variables (globalObject) set in earlier runs?

    what i am trying to achieve is to add portions of code to VM but not loosing environment.

  3. Call on a coroutines from the outside does not work???
    for example:
    vm.runCoroutine(“mycoroutinename”,[myarg1,myarg2]); will throw errors. However, coroutines can run within the script.
    //script
    coroutine getinput(question)
    {
    return (1+2+question);
    }
    trace(getinput(2));//this line works, can trace 5 in the console

    //as3

    vm.getGlobalObject().trace=MyTrace;

    vm.setByteCode(p.parse());

    var result = vm.execute();//can print 5 in the console

    trace(vm.runCoroutine(“getinput”,[2]));//this line will fail!!!
    /*
    TypeError: Error #1006: value is not a function。
    at scripting::VirtualMachine/execute()
    at scripting::VirtualMachine/executeFunction()
    at scripting::VirtualMachine/runCoroutine()
    */

  4. Thanks for posting, I’ll have to take a look at that. I’ve also been making some updates to the code, so I’ll probably upload a new version in the next few days.

  5. The issue there is that external suspending only works if you initially use runCoroutine(), and are using coroutines in your code.

    Try this:
    function traceE(var1:*):void
    {
    trace(var1);
    avm.suspend();
    }
    var s:Scanner = new Scanner(“function hello(){trace(1); trace(2);}”);
    var p:Parser = new Parser(s);
    var avm:VirtualMachine = new VirtualMachine();
    avm.getGlobalObject().trace = traceE;
    p.setForceCoroutine(true);
    avm.setByteCode(p.parse());

    avm.execute(); // initial run to set up coroutines
    avm.runCoroutine(“hello”); // first run
    avm.execute(); // after suspending, second run

  6. Ok, so here’s the problem, I’m doing some processing and the BEST way to achieve that is using a recursive function.

    Here’s a brief description of the problem. I have a nest of similar objects, one inside the other. All of them have the same properties. To do the recursion, we check if this variable has children. If it has, we do the recursive function with it, and so on, this function returns some data of the item. The MAIN description of one of these items is based on all its children. This variables also has a “name”, that together turns to be the description of the main target. ok…. to the code.

    function main()
    {
    var object = { name: ‘ob1’, children:
    [ {name: ‘ob2’},
    {name: ‘ob3, children: [ {name: ‘ob4’}]}
    ]}

    var str = getInfo(object);
    trace(str);
    }

    function getinfo(variable) // recursive one
    {
    var str = name;
    if (variable.children)
    {
    for (var i = 0; i < children.length. i++)
    {
    str += " – " + getinfo(children[i]);
    }
    }
    return str;
    }

    OK! The output have to be…

    "ob1 – ob2 – ob3 – ob4"

    simple like that, BUT the recursion goes wrong. 'Cos the function thinks "str" is just ONE variable, when in fact it must be a new one every time you call the function.

    I did the SAME thing inside flash and it worked just fine, cos flash recognize each variable inside the function as a local one.

    At least I think this is what's going on. I want to know if there's a way to do this inside the script, without the need to write this function in flash and call for it in the script.

  7. Hi. It looks like you’re running into a problem that’s similar to a known issue with statements that include complex returns and/or recursion.

    The current workaround is to simplify the problem spot:

    var tailend = getInfo(children[i]);
    str = ” – ” + tailend;

  8. This is fantastic – really great stuff.

    One obvious application of this would be gaming, acting as the script engine for dialogue and what-not. Do you know of any games that are currently using this system?

    Also am I correct in saying that this kind of emulates the “eval” of As2? Correct me if I’m wrong of course. Either way I know that “eval” was “evil” because it created a second environment to run the script in, which is resource intensive.. Is BISE as resource intensive??

  9. Hi, Chris.

    I don’t know of any games that come out and say that they use BISE, but I do know that commenters to this site and my previous site had links to game sites. And of course, I use BISE, (admittedly in a half-dozen games that writer’s block didn’t let me finish..)

    BISE is certainly resource intensive, between the compiling of code from strings to byte-array, and the virtual machine which loops through instructions one at a time. If you don’t use it for heavy calculating though, and instead use it to organize a bunch of high-level function calls, it should run fine. I couldn’t tell you how its internals compared to what AS2’s eval did, though.

    Basically, if you ever found yourself making a game and writing a little language to write your scripts in, consider using BISE as a full-fledged one that’s already made.

  10. Excellent goods from you, man. I’ve bear in mind your stuff previous to and you’re just too excellent. I actually like what you’ve received here, really like what you’re saying and the way in which by which you assert it. You are making it entertaining and you continue to care for to stay it smart. I can not wait to read far more from you. This is actually a tremendous site.

  11. Hi Sean,
    Great work! How would I utilize the return of a function to access the properties on an object as such:

    function(“NAME”).Properties.position.x

    How can I do this? If it is not implemented can you point me in the right direction?

    • Hi, Lavon.

      There’s an issue in the code with complex statements that try to use a function’s result right away in the same line.

      In this case, try simplifying the code:

      var lookup = function(“NAME”);
      var result = lookup.Properties.position.x;

      • That’s what I ended up doing, thanks. Are there any other performance improvements that you might have had in mind but didn’t have time to implement? 🙂

      • It’s been a while since I last touched the code, and my main role was porting it from AS2 to AS3. I suppose the project is in its maintenance phase now, and I’m not planning to do anything new to it.

        If you’re wondering if there’s any other issues in the code you need to know about, I think the readme and version log should have what you need to know.

  12. Pingback: [转][开源]AS3 Evaldraw (动态代码绘图引擎)[更新在线演示] - 算法网

  13. Do you still have the code for COTG Desktop? There are a lot of people that are looking for it. I have a copy but it doesn’t seem to work on any computers other than the one I had originally installed on.

Leave a reply to Hosea Wernli Cancel reply