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"); } }