2009年01月30日
パーサーが生成する構文木を木にしてみた
昨日の実験で、構文木を作らなきゃなーと思い知ったので作ってみた。
ただ作るだけじゃ面白くないので、木として描画してみることにした。これが成果物。
- 数式を編集するとその場で木が再描画されます。
- +-×÷()しかサポートしてません。
複雑な例だとちょっと分かりにくいので、1+2*3 などとしてみると分かりやすいかと。

木構造は木で図示するに限りますね。
(参考文献) フラクタルについて
ソースコードは以下に(157行)。
// Parse Node
// see also: http://fxp.hp.infoseek.co.jp/arti/parser.html
package{
import flash.display.*;
import flash.events.Event;
import flash.text.*;
[SWF(backgroundColor="#ffffff")]
public class ParseNode extends Sprite{
private const THETA:Number = .6;
private const RATIO:Number = .6;
private const SIZE:Number = 180;
private const WIDTH:Number = 15;
private const CIRCLE_R:Number = 10;
private var canvas:Sprite;
public function ParseNode(){
var parser:Parser = new Parser();
addChild(canvas = new Sprite());
var input:TextField = new TextField();
input.border = true;
input.x = 10; input.y = 10;
input.width = 180; input.height = 20;
input.type = "input";
input.text = "1*(2+3)+4/5+6+7*(-8+9)";
input.addEventListener("change", function(event:*):void{
while(canvas.numChildren) canvas.removeChildAt(0);
canvas.graphics.clear();
try{
var result:Object = parser.parse(input.text)
draw(result, 100, 500, -Math.PI);
}catch(e:Error){
}
});
input.dispatchEvent(new Event("change"));
input.scaleX = input.scaleY = 2;
addChild(input);
stage.scaleMode = "noScale";
stage.align = "TL";
}
private function draw(result:Object, x:Number, y:Number, angle:Number, ratio:Number = 1, f:Boolean = false):void{
canvas.graphics.lineStyle(WIDTH * ratio, 0x994c00, 1, false, "normal", "none");
canvas.graphics.moveTo(x, y);
x += SIZE * ratio * Math.sin(angle);
y += SIZE * ratio * Math.cos(angle);
canvas.graphics.lineTo(x, y);
var isBranch = result is Array;
if(isBranch){
angle += (Math.random() - .5) * .2;
if(result.length > 1) draw(result[1], x, y, angle, ratio * RATIO, !f);
angle += THETA * (f ? 1 : -1)
if(result.length > 2) draw(result[2], x, y, angle, ratio * RATIO, !f);
}
ratio = Math.pow(ratio, .8);
if(isBranch){
canvas.graphics.lineStyle(.2 * WIDTH * ratio, 0x994c00);
canvas.graphics.beginFill(0xffffff);
canvas.graphics.drawCircle(x, y, CIRCLE_R * ratio);
canvas.graphics.endFill();
}else{
var leaf:Leaf = new Leaf();
leaf.x = x;
leaf.y = y;
leaf.rotation = -angle / Math.PI * 180;
leaf.scaleX = leaf.scaleY = CIRCLE_R * ratio;
canvas.addChild(leaf);
}
var tf:TextField = new TextField();
tf.autoSize = "left";
tf.text = (result is Array ? result[0] : result.toString());
tf.scaleX = tf.scaleY = ratio * 4;
tf.x = x;
tf.y = y - ratio * 8;
tf.selectable = false;
canvas.addChild(tf);
}
}
}
import flash.display.*;
class Leaf extends Sprite{
public function Leaf(){
graphics.beginFill(0x006600);
graphics.drawEllipse(-2, -4, 4, 8);
graphics.endFill();
}
}
class Parser{
private var pos:int;
private var str:String;
public function parse(s:String):Object{
str = s.replace(/ /g, "");
pos = 0;
return expr();
}
// Expr = Term { (+|-) Term}
private function expr():Object{
var ret:Object = term();
while(true){
switch(str.charAt(pos)){
case "+": pos++; ret = ["+", ret, term()]; break;
case "-": pos++; ret = ["-", ret, term()]; break;
default: return ret;
}
}
return 0; // never comes here
}
// Term = Fact { (*|/) Fact}
private function term():Object{
var ret:Object = fact();
while(true){
switch(str.charAt(pos)){
case "*": pos++; ret = ["*", ret, fact()]; break;
case "/": pos++; ret = ["/", ret, fact()]; break;
default: return ret;
}
}
return 0; // never comes here
}
// Fact = ( Expr ) | - Fact | number
private function fact():Object{
var ret:Object;
var m:Array;
if((m = str.substr(pos).match(/^(\d+)/))){
pos += m[1].length;
return parseInt(m[1]);
}
else if(str.charAt(pos) == "-"){
pos++;
return ["-", fact()];
}
else if(str.charAt(pos) == "("){
pos++;
ret = ["( )", expr()];
if(str.charAt(pos) != ")") throw new Error("No match for )");
pos++;
return ret;
}
throw new Error("invalid format");
}
}