Beautifl - Flash Gallery

Thumbnail : SslbParser
SslbParser
ABA 2010-04-09 MIT License

再生するにはFlash Playerが必要です。デスクトップのブラウザでご覧ください。

// 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[
nway 1..12 30__90
 nwayslide $1 $2 0
nwayslide 1..12 10__90 0.2__0.5
 fire -$2 -$3
 repeat $1
  fire $2*2/$1 $3*2/($1-1)
nwayturn 1..12 30__90*-1|1 1..6
 fire -$2
 repeat $1
  wait $3
  fire $2*2/$1
nwayturncenter 1..12 30__120*-1|1 1..6
 fire
 repeat $1
  wait $3
  fire $2/$1
nwayturntoandfrom 1..12 30__120*-1|1 1..6
 fire -$2
 repeat $1
  wait $3
  fire $2*2/$1
 repeat $1
  wait $3
  fire -$2*2/$1
wedge 1..6 30__120 0.2__0.5*-1|1
 fire -$2 -$3
 repeat $1
  fire $2/$1 $3*2/$1
 repeat $1
  fire $2/$1 -$3*2/$1
round 3..32
 repeat $1
  fire 360/$1
roundturn 3..32 1..4 -1|1
 fire
 repeat $1
  wait $2
  fire 360/$1*$3
]]>).toString();
var sslbBranch:String = (<![CDATA[
bar 2..6 3..8
 speed 0
 whip $1 $2
whip 2..6 3..8 0.2__0.6
 fire 0 -$3
 repeat $1
  wait $2
  fire 0 $3*2/$1
shotside 3..12
 fire 90
  vanish
 repeat inf
  wait $1
  fire 180
  fire 180
shotaim 3..12
 repeat inf
  wait $1
  fireaim 0
shotslow 3..12
 wait $1
 fire 0 -0.5
 repeat inf
  wait $1
  fire
changespeed 0.4__0.8*-1|1 5..30
 speed 1+$1 $2
curve 2__10*-1|1 20..60
 angvel $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 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);
	main.addChild(new Bitmap(screen));
	main.addChild(sslbTextField);
	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 = 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(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;
		addBullet(p, a, 5, true, rootCommand, null, childCommands);
		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 (b 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") runState.fireAngle += c.args[0].calc(vars) * PI / 180;
					else 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") {
			runState.pushCurrent(new ScopeState(c, vars, c.args[0].calc(vars), runState));
			return;
		} else if (cn == "wait") {
			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") {
			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;
	}
	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;
}