In case Flash no longer exists; a copy of this site is included in the Flashpoint archive's "ultimate" collection.

Dead Code Preservation :: Archived AS3 works from wonderfl.net

forked from: SslbParser

SslbParser
Sslb (Shmup Specific Language:Bullet) parser.
<Operation>
Mouse: Move your ship.
Get Adobe Flash player
by myshimao 15 Aug 2010
/**
 * Copyright myshimao ( http://wonderfl.net/user/myshimao )
 * MIT License ( http://www.opensource.org/licenses/mit-license.php )
 * Downloaded from: http://wonderfl.net/c/k5Xh
 */

// SslbParser
//  Sslb (Shmup Specific Language:Bullet) parser.
//  <Operation>
//   Mouse: Move your ship.
package {
    import flash.display.Sprite;
    [SWF(width="465", height="465", backgroundColor="0", frameRate="30")]
    public class Main extends Sprite {
        public function Main() { main = this; initialize(); }
    }
}
import flash.display.*;
import flash.filters.*;
import flash.geom.*;
import flash.events.*;
import flash.text.*;
import org.si.sion.*;
import org.si.sion.utils.*;
const SCREEN_WIDTH:int = 465, SCREEN_HEIGHT:int = 465;
var sslbRoot:String = (<![CDATA[
fire12 2__10*-1|1 20..60
 repeat 5
   fire 0
   wait 2 
]]>).toString();
var sslbBranch:String = (<![CDATA[
changespeed 0.4__0.8*-1|1 5..30
 speed 1+$1 $2
]]>).toString();
var main:Main, stage:Stage, screen:BitmapData;
var point:Point = new Point;
var commands:Array = new Array;
var rootCommands:Vector.<Command> = new Vector.<Command>;
var branchCommands:Vector.<Command> = new Vector.<Command>;
var sslbTextField:TextField;
var sslbCommandField:TextField;
var driver:SiONDriver = new SiONDriver();
var percusVoices:Array = new SiONPresetVoice()["valsound.percus"];
var drum1:SiONData, drum2:SiONData, drum3:SiONData;
// ----------------------------
// Initialize.
// ----------------------------
function initialize():void {
    stage = main.stage;
    var sslbXmls:Vector.<XML>;
    sslbXmls = parseIndentedText(sslbRoot);
    for each (var sslb:XML in sslbXmls) {
        var c:Command = new Command(sslb);
        commands[c.name] = c;
        rootCommands.push(c);
        //branchCommands.push(c);
    }
    sslbXmls = parseIndentedText(sslbBranch);
    for each (sslb in sslbXmls) {
        c = new Command(sslb);
        commands[c.name] = c;
        branchCommands.push(c);
    }
    Bullet.initialize();
    drum1 = createDrum(14); drum2 = createDrum(27); drum3 = createDrum(0);
    screen = new BitmapData(SCREEN_WIDTH, SCREEN_HEIGHT, false);
    sslbTextField = createTextField(0, 0, SCREEN_WIDTH / 2, SCREEN_HEIGHT, 0xffffff);
    sslbCommandField = createTextField(0, SCREEN_HEIGHT-100, SCREEN_WIDTH, 100, 0xffffff);
    main.addChild(new Bitmap(screen));
    main.addChild(sslbTextField);
    main.addChild(sslbCommandField);
    main.addEventListener(Event.ENTER_FRAME, update);
}
// Update the frame.
const MAX_SSLB_COUNT:int = 2;
var ticks:int = 0;
var rootCommand:Command;
var addCommandFlags:Vector.<Boolean> = new Vector.<Boolean>(MAX_SSLB_COUNT), childCommands:Vector.<Command>;
function update(event:Event):void {
    var t:int = ticks % 60;
    if (t == 0) {
        rootCommand = rootCommands[randi(rootCommands.length)];
        childCommands  = new Vector.<Command>;
        for (var i:int = 0; i < MAX_SSLB_COUNT; i++) addCommandFlags[i] = (randi(2) == 0);
        sslbTextField.text = "root:"+rootCommand.toString();        
        //driver.play(drum1);
    } 
    
    
    else if (t % 7 == 0) {
        var at:int = t / 7 - 1;
        if (at < MAX_SSLB_COUNT && addCommandFlags[at]) {
            var c:Command = branchCommands[randi(branchCommands.length)].getRandomFixedClone();
            childCommands.push(c);
            sslbTextField.appendText("\childs:" + childCommands.length + ":" + c.toString());
            //driver.play(drum3);
        }
    } 
    //*/
    else if (t % 60 == 30) {
        var p:Vector3D = new Vector3D(SCREEN_WIDTH / 2, SCREEN_HEIGHT * 0.2);
        var a:Number;
        /*
        if (randi(2) == 0) a = getPlayerAngle(p);
        else a = randn(PI) - PI / 2;
        */
        a= getPlayerAngle(p);
        addBullet(p, a, 5, true, rootCommand, null, childCommands);
        sslbTextField.appendText("addBullet done:a="+a);
        sslbCommandField.text = "";
        //driver.play(drum2);
    }
    //*/
    
    ticks++;
    for (i = 0; i < bullets.length; i++) if (!bullets[i].update()) { bullets.splice(i, 1); i--; }
    if (bullets.length >= MAX_BULLET_COUNT) bullets.splice(MAX_BULLET_COUNT, bullets.length - MAX_BULLET_COUNT);
    screen.lock();
    screen.fillRect(screen.rect, 0);
    //for each (var b:Bullet in bullets) b.drawBlur();
    player.update();
    for each (var b:Bullet in bullets) b.draw();
    screen.unlock();
}
// Player.
var player:Player = new Player;
class Player {
    private const HIT_RANGE:Number = 5;
    public var pos:Vector3D = new Vector3D;
    private var bd:BitmapData, bdRect:Rectangle;
    private var invincibleTicks:int = 60;
    public function Player() {
        pos.x = SCREEN_WIDTH / 2; pos.y = SCREEN_HEIGHT * 0.75;
        bd = createCircleBitmapData(12, 0x88, 0xff, 0xcc, 0.8, 3, 7);
        bdRect = bd.rect;
    }
    public function update():void {
        pos.x += (stage.mouseX - pos.x) * 0.2;
        pos.y += (stage.mouseY - pos.y) * 0.2;
        point.x = pos.x - bdRect.width / 2; point.y = pos.y - bdRect.height / 2;
        if (invincibleTicks >= 0) {
            invincibleTicks--;
            if (invincibleTicks % 15 < 7) return;
        }
        screen.copyPixels(bd, bdRect, point);
    }
    public function checkHit(p:Vector3D):void {
        if (invincibleTicks < 0 && Vector3D.distance(pos, p) < HIT_RANGE) {
            invincibleTicks = 60;
            for (var i:int = 0; i < 64; i++) {
                bullets.push(new Bullet(pos, randn(PI * 2), randn(10) + 10, false, null, null, null, true));
            }
        }
    }
}
function getPlayerAngle(p:Vector3D):Number {
    return atan2(player.pos.x - p.x, player.pos.y - p.y);
}
// Bullets.
const MAX_BULLET_COUNT:int = 256, MAX_TICKS:int = 30 * 5;
var bullets:Vector.<Bullet> = new Vector.<Bullet>;
class Bullet {
    private static var bd:BitmapData, bdRect:Rectangle;
    private static var fragmentBd:BitmapData, fragmentBdRect:Rectangle;
    private static var blurBds:Vector.<BitmapData> = new Vector.<BitmapData>;
    private static var blurRects:Vector.<Rectangle> = new Vector.<Rectangle>;
    private static const BLUR_SIZE_COUNT:int = 7, BLUR_COLOR_COUNT:int = 8;
    public var pos:Vector3D;
    public var angle:Number, speed:Number;
    public var runState:RunState;
    public var isRemoving:Boolean;
    public var childCommands:Vector.<Command>;
    private var isRoot:Boolean, ticks:int;
    private var isFragment:Boolean;
    public static function initialize():void {
        bd = createCircleBitmapData(10, 0xaa, 0x77, 0xdd, 0.75, 1, 8);
        bdRect = bd.rect;
        fragmentBd = createCircleBitmapData(8, 0xdd, 0xcc, 0x66, 0.5, 8, 4);
        fragmentBdRect = fragmentBd.rect;
        bdRect = bd.rect;
        for (var i:int = 0; i < BLUR_SIZE_COUNT; i++) {
            var radius:Number = (BLUR_SIZE_COUNT - i) * 2, a:Number = 0.5 - i * 0.05;
            for (var j:int = 0; j < BLUR_COLOR_COUNT; j++) {
                var r:int = 0xff - j * 0x11;
                var b:int = 0x88;
                var bbd:BitmapData = createCircleBitmapData(radius, r, 0, b, a, 5);
                blurBds.push(bbd);
                blurRects.push(bbd.rect);
            }
        }
    }
    public function Bullet(p:Vector3D, angle:Number, speed:Number, isRoot:Boolean,
            command:Command, vars:Vector.<Number>, ccs:Vector.<Command>, isFragment:Boolean = false) {
        pos = new Vector3D(p.x, p.y);
        this.angle = angle; this.speed = speed;
        this.isRoot = isRoot;
        this.isFragment = isFragment;
        if (command == null && ccs != null && ccs.length > 0) {
            command = ccs[0];
            childCommands = new Vector.<Command>;
            for (var i:int = 1; i < ccs.length; i++) childCommands.push(ccs[i]);
            vars = null;
            isRoot = true;
        } else {
            childCommands = ccs;
        }
        if (command != null) runState = new RunState(this, command, vars);
    }
    public function update():Boolean {
        if (runState != null) {
            if (!runState.isEnded) runState.update();
            else if (isRoot) return false;
            if (isRemoving) return false;
        }
        if (!isRoot) {
            pos.x += sin(angle) * speed; pos.y += cos(angle) * speed;
            player.checkHit(pos);
        }
        ticks++;
        return (pos.x >= 0 && pos.x <= SCREEN_WIDTH && pos.y >= 0 && pos.y <= SCREEN_HEIGHT && ticks < MAX_TICKS);
    }
    public function draw():void {
        if (isRoot) return;
        if (!isFragment)  {
            point.x = pos.x - bdRect.width / 2; point.y = pos.y - bdRect.height / 2;
            screen.copyPixels(bd, bdRect, point);
        } else {
            point.x = pos.x - fragmentBdRect.width / 2; point.y = pos.y - fragmentBdRect.height / 2;
            screen.copyPixels(fragmentBd, fragmentBdRect, point);
        }
    }
    public function drawBlur():void {
        if (isRoot || isFragment) return;
        var px:Number = pos.x, py:Number = pos.y;
        var vx:Number = sin(angle) * speed * 2;
        var vy:Number = cos(angle) * speed * 2;
        var t:int = ticks;
        for (var i:int = 0; i < BLUR_SIZE_COUNT; i++) {
            if (t-- <= 0) break;
            var c:int  = t % (BLUR_COLOR_COUNT * 2);
            if (c >= BLUR_COLOR_COUNT) c = BLUR_COLOR_COUNT * 2 - c - 1;
            var bi:int = c + i * BLUR_COLOR_COUNT;
            point.x = px - blurRects[bi].width / 2;
            point.y = py - blurRects[bi].height / 2;
            screen.copyPixels(blurBds[bi], blurRects[bi], point);
            px -= vx; py -= vy;
        }
    }
}
function addBullet(p:Vector3D, angle:Number, speed:Number, isRoot:Boolean,
        command:Command, vars:Vector.<Number>, ccs:Vector.<Command>):void {
    bullets.push(new Bullet(p, angle, speed, isRoot, command, vars, ccs));
}
// Sslb run state.
class RunState {
    public var bullet:Bullet;
    public var fireAngle:Number, fireSpeed:Number, fireBaseSpeed:Number;
    public var speedVel:Number, speedVelTicks:int;
    public var angVel:Number, angVelTicks:int;
    public var waitTicks:int;
    private var states:Vector.<ScopeState> = new Vector.<ScopeState>;
    private var current:ScopeState;
    public function RunState(bullet:Bullet, root:Command, vars:Vector.<Number>) {
        this.bullet = bullet;
        fireAngle = bullet.angle; fireSpeed = fireBaseSpeed = bullet.speed;
        current = new ScopeState(root, vars, 1, this);
    }
    public function update():void {
        if (speedVelTicks > 0) {
            bullet.speed += speedVel;
            speedVelTicks--;
        }
        if (angVelTicks > 0) {
            bullet.angle += angVel;
            angVelTicks--;
        }
        if (waitTicks > 0) {
            waitTicks--;
            return;
        }
        if (current != null) current.update();
    }
    public function pushCurrent(newState:ScopeState):void {
        states.push(current);
        current = newState;
        update();
    }
    public function popCurrent():void {
        if (states.length <= 0)    {
            current = null;
            return;
        }
        current = states.pop();
        update();
    }
    public function get isEnded():Boolean {
        return current == null && speedVelTicks <= 0 && angVelTicks <= 0;
    }
}
class ScopeState {
    private var runState:RunState, bullet:Bullet;
    private var command:Command;
    private var vars:Vector.<Number> = new Vector.<Number>;
    private var programCount:int, repeatCount:int;
    public function ScopeState(command:Command, vs:Vector.<Number>, repeatCount:int, runState:RunState) {
        this.runState = runState; bullet = runState.bullet; 
        this.command = command;
        this.repeatCount = repeatCount;
        var vc:int = 0;
        if (vs != null) {
            for each (var v:Number in vs) {
                vars.push(v);
                vc++;
            }
        }
        for (var i:int = vc; i < command.args.length; i++) vars.push(command.args[i].calc(vars));
    }
    public function update():void {
        if (programCount >= command.children.length) {
            repeatCount--;
            if (repeatCount <= 0) {
                runState.popCurrent();
                return;
            } else {
                programCount = 0;
            }
        }
        var c:Command = command.children[programCount];
        programCount++;
        var cn:String = c.name;
        if (cn == "fire" || cn == "fireaim") {
            if (bullet.pos.y < SCREEN_HEIGHT / 2) {
                var al:int = c.args.length;
                if (al > 0) {
                    if (cn == "fire") {
                        sslbCommandField.appendText("\nfire:" + c.args[0].calc(vars));
                        runState.fireAngle += c.args[0].calc(vars) * PI / 180;
                    } else { 
                        sslbCommandField.appendText("\nfireaim:" + c.args[0].calc(vars));
                        runState.fireAngle =
                        getPlayerAngle(bullet.pos) + c.args[0].calc(vars) * PI / 180;
                        
                    }
                }
                if (al > 1) runState.fireSpeed += runState.fireBaseSpeed * c.args[1].calc(vars);
                var command:Command;
                if (c.children.length > 0) command = c;
                addBullet(bullet.pos, runState.fireAngle, runState.fireSpeed,
                    false, command, vars, bullet.childCommands);
            }
        } else if (cn == "repeat") {
            sslbCommandField.appendText("\nrepeat:" + c.args[0].calc(vars));
            runState.pushCurrent(new ScopeState(c, vars, c.args[0].calc(vars), runState));
            return;
        } else if (cn == "wait") {
            sslbCommandField.appendText("\nwait:" + c.args[0].calc(vars));
            runState.waitTicks = c.args[0].calc(vars);
            if (runState.waitTicks > 0) return;
        } else if (cn == "vanish") {
            if (c.args.length <= 0 || c.args[0].calc(vars) <= 0) bullet.isRemoving = true;
        } else if (cn == "angvel") {
            runState.angVel = c.args[0].calc(vars) * PI / 180;
            if (c.args.length > 1) runState.angVelTicks = c.args[1].calc(vars);
            else runState.angVelTicks = 9999999;
        } else if (cn == "speed") {            
            sslbCommandField.appendText("\nspeed:" + c.args[0].calc(vars));
            var ts:Number = c.args[0].calc(vars);
            var t:int = 1;
            if (c.args.length > 1) t = c.args[1].calc(vars);
            runState.speedVel = bullet.speed * (ts - 1) / t;
            runState.speedVelTicks = t;
        } else {
            var vs:Vector.<Number> = new Vector.<Number>;
            for each (var arg:Expression in c.args) vs.push(arg.calc(vars));
            runState.pushCurrent(new ScopeState(commands[cn], vs, 1, runState));
            return;
        }
        update();
    }
}
// Sslb command.
class Command {
    public var name:String;
    public var args:Vector.<Expression> = new Vector.<Expression>;
    public var children:Vector.<Command> = new Vector.<Command>;
    public var value:XML;
    public function Command(v:XML) {
        value = v;
        name = v.name();
        for each (var a:String in v._arg) args.push(new Expression(a));
        if (v._line.length() >= 1)
            for each (var l:XML in v._line[0].children()) children.push(new Command(l));
    }
    public function getRandomFixedClone():Command {
        var c:Command = new Command(value);
        c.fixRandom();
        return c;
    }
    public function fixRandom():void {
        for each (var a:Expression in args) a.fixRandom();
        for each (var c:Command in children) c.fixRandom();
    }
    public function toString(indent:int = 0):String {
        var s:String = "";
        for (var i:int = 0; i < indent; i++) s += " ";
        s += name + " ";
        for each (var a:Expression in args) s += a.str + " ";
        s += "\n";
        for each (var c:Command in children) s += c.toString(indent + 1);
        return s;
    }
}
// Expression.
class Expression {
    private static const PLUS:int = -1, MINUS:int = -2, MULTIPLE:int = -3, DIVISION:int = -4, MODULO:int = -5;
    private static const INT_RAND:int = -6, NUMBER_RAND:int = -7, OR_RAND:int = -8, VARIABLE:int = -9;
    public var str:String;
    private var operator:Vector.<int> = new Vector.<int>;
    private var value:Vector.<Number> = new Vector.<Number>;
    public function Expression(s:String) {
        str = s;
        removeWhiteSpace();
        parseToRPN(0, str.length);
    }
    public function calc(vars:Vector.<Number>):Number {
        var stack:Vector.<Number> = new Vector.<Number>;
        for each (var op:int in operator) {
            if (op >= 0) {
                stack.push(value[op]);
            } else if (op <= VARIABLE) {
                stack.push(vars[VARIABLE - op]);
            } else {
                switch(op) {
                case PLUS: case MINUS: case MULTIPLE: case DIVISION: case MODULO:
                case INT_RAND: case NUMBER_RAND: case OR_RAND:
                    var n1:Number = stack.pop();
                    var n2:Number = stack.pop();
                    stack.push(calcOperator(op, n2, n1));
                    break;
                default:
                    throw new Error("illegal operator: " + op);
                }
            }
        }
        return stack.pop();
    }
    public function fixRandom():void {
        for (var i:int = 0; i < operator.length; i++) {
            var op:int = operator[i];
            if (op == INT_RAND || op == NUMBER_RAND || op == OR_RAND) {
                var i1:int = operator[i - 1];
                var i2:int = operator[i - 2];
                if (i1 >= 0 && i2 >= 0) {
                    var n1:Number = value[i1];
                    var n2:Number = value[i2];
                    var ni:int = value.push(calcOperator(op, n1, n2)) - 1;
                    i -= 2;
                    operator[i] = ni;
                    operator.splice(i + 1, 2);
                }
            }
        }
    }
    private function removeWhiteSpace():void {
        var cs:String = new String;
        var skip:Boolean = false;
        var depth:int = 0;
        for (var i:int = 0; i < str.length; i++) {
            switch (str.charAt(i)) {
            case ' ':
            case '\n':
            case '\r':
                skip = true;
                break;
            case ')':
                depth--;
                if ( depth < 0 ) throw new Error("bracket not match");
                break;
            case '(':
                depth++;
                break;
            }
            if (skip)
                skip = false;
            else
                cs += str.charAt(i);
        }
        if (depth != 0) throw new Error("bracket not match");
        str = cs;
    }
    private function parseToRPN(stIdx:int, edIdx:int):void {
        var op0:int = -1, op1:int = -1, op2:int = -1;
        for (var i:int = edIdx - 1; i >= stIdx; i--) {
            var c:String = str.charAt(i);
            if (c == ')') {
                var bc:int = 1;
                do {
                    i--;
                    if (str.charAt(i) == ')') bc++;
                    else if (str.charAt(i) == '(') bc--;
                } while (bc > 0);
            } else if (op0 < 0 && (c == '.' || c == '_' || c == '|')) {
                if (c == '.' || c == '_') {
                    var c2:String = str.charAt(i - 1);
                    if ((c == '.' && c2 != '.')    || (c == '_' && c2 != '_')) continue;
                    i--;
                }
                op0 = i;
            } else if (op1 < 0 && (c == '*' || c == '/' || c == '%')) {
                op1 = i;
            } else if (op2 < 0 && (c == '+' || c == '-')) {
                op2 = i;
            }
        }
        if (op0 < 0 && op1 < 0 && op2 < 0 && str.charAt(stIdx) == '(' && str.charAt(edIdx - 1) == ')') {
            parseToRPN(stIdx + 1, edIdx - 1);
            return;
        }
        if (op2 == stIdx) {
            if (op0 < 0 && op1 < 0) {
                switch (str.charAt(op2)) {
                case '-':
                    parseToRPN(stIdx + 1, edIdx);
                    pushNumber(-1);
                    pushOperator(MULTIPLE);
                    break;
                case '+':
                    parseToRPN(stIdx + 1, edIdx);
                    break;
                default:
                    throw new Error("unknown unary operator: " + str.charAt(op2));
                }
                return;
            } else {
                op2 = -1;
            }
        }
        if (op2 >= 0) {
            c = str.charAt(op2 - 1);
            if (c == '+' || c == '-' || c == '*' || c == '/' || c == '%' || c == '.' || c == '_' || c == '|')
                op2 = -1;
        }
        if (op2 < 0) {
            if (op1 < 0) {
                if (op0 < 0) {
                    parseFloatValue(stIdx, edIdx - stIdx);
                } else {
                    parseToRPN(stIdx, op0);
                    var ni:int = 1;
                    c = str.charAt(op0);
                    if (c == '.' || c =='_') ni = 2;
                    parseToRPN(op0 + ni, edIdx);
                    switch(c) {
                    case '.': operator.push(INT_RAND); break;
                    case '_': operator.push(NUMBER_RAND); break;
                    case '|': operator.push(OR_RAND); break;
                    default: throw new Error("unknown operator: " + str.charAt(op0));
                    }
                }
            } else {
                parseToRPN(stIdx, op1);
                c = str.charAt(op1);
                parseToRPN(op1 + 1, edIdx);
                switch(c) {
                case '*': pushOperator(MULTIPLE); break;
                case '/': pushOperator(DIVISION); break;
                case '%': pushOperator(MODULO); break;
                default: throw new Error("unknown operator: " + str.charAt(op1));
                }
            }
        } else {
            parseToRPN(stIdx, op2);
            parseToRPN(op2 + 1, edIdx);
            switch(str.charAt(op2)) {
            case '+': pushOperator(PLUS); break;
            case '-': pushOperator(MINUS); break;
            default: throw new Error("unknown operator: " + str.charAt(op2));
            }
        }
    }
    private function parseFloatValue(stIdx:int, lgt:int):void {
        if (str.charAt(stIdx) == '$') {
            var label:String = str.substr(stIdx + 1, lgt - 1);
            var nidx:Number = parseInt(label);
            if (isNaN(nidx)) throw new Error("illegal variable: $" + label);
            var idx:int = nidx;
            idx--;
            pushVariable(VARIABLE - idx);
        } else {
            var s:String = str.substr(stIdx, lgt);
            if (s == "inf") {
                pushNumber(9999999);
            } else {
                var v:Number = Number(s);
                if (isNaN(v)) 
                    throw new Error ("illegal number: " + str.substr(stIdx, lgt));
                pushNumber(v);
            }
        }
    }
    private function pushOperator(op:int):void {
        var idx:int = operator.length;
        if (operator[idx - 2] >= 0 && operator[idx - 1] >= 0) {
            var i1:int = operator.pop();
            var i2:int = operator.pop();
            var n1:Number = value[i1];
            var n2:Number = value[i2];
            if (i1 > i2) {
                value.splice(i1, 1);
                value.splice(i2, 1);
            } else {
                value.splice(i2, 1);
                value.splice(i1, 1);
            }
            var ni:int = value.push(calcOperator(op, n2, n1)) - 1;
            operator.push(ni);
        } else {
            operator.push(op);
        }
    }
    private function pushVariable(vi:int):void {
        operator.push(vi);
    }
    private function pushNumber(n:Number):void {
        var ni:int = value.push(n) - 1;
        operator.push(ni);
    }
    private function calcOperator(op:int, n1:Number, n2:Number):Number {
        switch (op) {
        case PLUS:            return n1 + n2;
        case MINUS:            return n1 - n2;
        case MULTIPLE:        return n1 * n2;
        case DIVISION:        return n1 / n2;
        case MODULO:        return n1 % n2;
        case INT_RAND:        return randi(n2 - n1) + n1;
        case NUMBER_RAND:    return randn(n2 - n1) + n1;
        case OR_RAND:        return (randi(2) == 0 ? n1 : n2);
        default:            throw new Error("illegal operator: " + op);
        }
    }
}
// Indented text parser.
function parseIndentedText(s:String):Vector.<XML> {
    var indentSpaceCounts:Vector.<int> = new Vector.<int>;
    var indentSpaceCount:int = 0;
    var parentXmls:Vector.<XML> = new Vector.<XML>;
    indentSpaceCounts.push(indentSpaceCount);
    var lines:Array = s.split("\r\n").join("\n").split("\n");
    var texts:Vector.<XML> = new Vector.<XML>;
    var parent:XML, current:XML;
    for each (var line:String in lines) {
        var l:String = trimStart(line);
        if (l.length <= 0) continue;
        var strs:Array = l.split(" ");
        var isc:int = line.length  - l.length;
        if (isc > indentSpaceCount) {
            indentSpaceCounts.push(indentSpaceCount);
            indentSpaceCount = isc;
            parentXmls.push(parent);
            var lineXml:XML = new XML("<_line />");
            current.appendChild(lineXml);
            parent = lineXml;
        } else if (isc < indentSpaceCount) {
            while (isc < indentSpaceCount) {
                indentSpaceCount = indentSpaceCounts.pop();
                parent = parentXmls.pop();
            }
        }
        if (parentXmls.length == 0 && parent != null) {
            texts.push(parent);
            parent = null;
        }
        current = new XML("<" + strs[0] + " />");
        for (var i:int = 1; i < strs.length; i++) {
            current.appendChild(new XML("<_arg>" + strs[i] + "</_arg>"));
        }
        if (parent != null) parent.appendChild(current);
        else                parent = current;
    }
    if (parentXmls.length > 0) texts.push(parentXmls[0]);
    return texts;
}
// Create a circle bitmap data.
function createCircleBitmapData(radius:int, r:int, g:int, b:int, a:Number, blurSize:int, shineCount:int = 1):BitmapData {
    var s:Shape, gr:Graphics, bd:BitmapData;
    var blur:BlurFilter = new BlurFilter;
    blur.blurX = blur.blurY = blurSize;
    s = new Shape;
    gr = s.graphics;
    var p:Number = radius + blurSize, rd:int = radius;
    for (var i:int = 0; i < shineCount; i++) {
        gr.beginFill(r * 0x10000 + g * 0x100 + b, a);
        gr.drawCircle(p, p, rd);
        gr.endFill();
        p += 0.5; rd -= 1;
        r += 0x10; if (r > 0xff) r = 0xff;
        g += 0x10; if (g > 0xff) g = 0xff;
        b += 0x10; if (b > 0xff) b = 0xff;
    }
    // disable blur
    //s.filters = [blur];
    var sz:int = (radius + blurSize) * 2;
    bd = new BitmapData(sz, sz, true, 0);
    bd.draw(s);
    return bd;
}
// Utility functions.
var sin:Function = Math.sin, cos:Function = Math.cos, atan2:Function = Math.atan2;
var abs:Function = Math.abs, PI:Number = Math.PI;
function createTextField(x:int, y:int, width:int, height:int, color:int):TextField {
    var fm:TextFormat = new TextFormat, fi:TextField = new TextField;
    fm.font = "_typewriter"; fm.bold = true; fm.size = 12; fm.color = color;
    fi.defaultTextFormat = fm; fi.x = x; fi.y = y; fi.width = width; fi.height = height;  fi.selectable = false;
    return fi;
}
function createDrum(voiceNum:int):SiONData {
    var drum:SiONData = driver.compile("#EFFECT0{ws95lf4000}; %6@0o2v1c16");
    drum.setVoice(0, percusVoices[voiceNum]);
    return drum;
}
function randi(n:int):int {
    return Math.random() * n;
}
function randn(n:Number):Number {
    return Math.random() * n;
}
function trimStart(s:String):String {
        if (s.charAt(0) == ' ') s = trimStart(s.substring(1));
        return s;
}