2009年01月29日
10分で書いたパーサーを3時間かけて機能を増やした
昨日つくった 10分で書ける、お手軽パーサーを AS3 で が予想外に簡単だったので、練習用に機能を増やしていった。
ECMA-262 3rd Edition の 11 式 (Expressions) の仕様を参考に、主要な演算子をサポートしてみた。10分で書いたやつのノリでどんどん実装していっただけ。目新しいことは何もない。
代入演算子(=, += など)以外は実装できたはず。代入演算子はバックトラックが発生するので、現状の実装では対応できない。
例によって、値を変更するとその場で計算してくれる。
ここまで作って、条件演算子(A ? B : C)を処理するときに、A が true でも false でも B と C を評価してしまうことに気づいた。いまのところ、B も C も副作用はないから問題ないんだけど、なんかかっこ悪い。
if 文なんかも実装していくとしたらこのままでは無理だ。パースするときに構文木を作るだけにして、その後に構文木を元に評価していくようにしなきゃいけない。なるほど、世間一般のパーサーの実装に近づいていく。
ソースコードは以下に(334行)。
// Complex Recursive Descent Parsing // 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 ComplexRecursiveDescentParsing extends Sprite{ public function ComplexRecursiveDescentParsing(){ var parser:Parser = new Parser(); var output:TextField = new TextField(); output.x = 5; output.y = 40; output.autoSize = "left"; addChild(output); var input:TextField = new TextField(); input.border = true; input.x = input.y = 5; input.width = 190; input.height = 20; input.type = "input"; input.text = "1 * 4 > 2 && 3 % 2 == 1 ? 100 / 3 : 3"; input.addEventListener("change", function(event:*):void{ try{ output.text = parser.parse(input.text); output.textColor = 0x000000; }catch(e:Error){ output.text = e.toString(); output.textColor = 0xff0000; } }); input.dispatchEvent(new Event("change")); addChild(input); scaleX = scaleY = 2; stage.scaleMode = "noScale"; stage.align = "TL"; } } } class Parser{ private var pos:int; private var str:String; private var m:Array; private function Is(fn:Function):Boolean{ var old_pos:int = pos; fn(); if(pos != old_pos){ pos = old_pos; return true; } return false; } public function parse(s:String):String{ str = s.replace(/ /g, ""); trace(str); pos = 0; return ExpressionStatement().toString(); } private function ExpressionStatement():Object{ return Expression(); } // Expression = AssignmentExpression { , AssignmentExpression } private function Expression():Object{ var ret:Object = AssignmentExpression(); while(str.charAt(pos) == ","){ pos++; ret = AssignmentExpression(); } return ret; } // TODO: assignment // AssignmentExpression = ConditionalExpression private function AssignmentExpression():Object{ return ConditionalExpression(); } // ConditionalExpression = LogicalORExpression // | LogicalORExpression ? AssignmentExpression : AssignmentExpression private function ConditionalExpression():Object{ var ret:Object = LogicalORExpression(); if(str.charAt(pos) == "?"){ var b:Boolean = Boolean(ret); var tmp:Object; pos++; tmp = AssignmentExpression(); if(b) ret = tmp; if(str.charAt(pos) != ":") throw new Error("Conditional Operator: no :"); pos++; tmp = AssignmentExpression(); if(!b) ret = tmp; } return ret; } // LogicalORExpression = LogicalANDExpression { || LogicalANDExpression } private function LogicalORExpression():Object{ var ret:Object = LogicalANDExpression(); while(str.substr(pos).match(/^\|\|/)){ pos += 2; ret ||= LogicalANDExpression(); } return ret; } // LogicalANDExpression = BitwiseORExpression { && BitwiseORExpression } private function LogicalANDExpression():Object{ var ret:Object = BitwiseORExpression(); while(str.substr(pos, 2) == "&&"){ pos += 2; ret &&= BitwiseORExpression(); } return ret; } // BitwiseORExpression = BitwiseANDExpression { | BitwiseANDExpression } private function BitwiseORExpression():Object{ var ret:Object = BitwiseXORExpression(); while(str.substr(pos).match(/^\|[^|]/)){ pos += 1; ret = int(ret) | int(BitwiseXORExpression()); } return ret; } // BitwiseXORExpression = BitwiseANDExpression { ^ BitwiseANDExpression } private function BitwiseXORExpression():Object{ var ret:Object = BitwiseANDExpression(); while(str.substr(pos).match(/^\^/)){ pos += 1; ret = int(ret) ^ int(BitwiseANDExpression()); } return ret; } // BitwiseANDExpression = EqualityExpression { & EqualityExpression } private function BitwiseANDExpression():Object{ var ret:Object = EqualityExpression(); while(str.substr(pos).match(/^&[^&]/)){ pos += 1; ret = int(ret) & int(EqualityExpression()); } return ret; } // EqualityExpression = RelationalExpression { (==|!=|===|!==) RelationalExpression } private function EqualityExpression():Object{ var ret:Object = RelationalExpression(); while((m = str.substr(pos).match(/^(===|!==|==|!=)/))){ switch(m[1]){ case "==": pos += 2; ret = ret == RelationalExpression(); break; case "!=": pos += 2; ret = ret != RelationalExpression(); break; case "===": pos += 3; ret = ret === RelationalExpression(); break; case "!==": pos += 3; ret = ret !== RelationalExpression(); break; } } return ret; } // RelationalExpression = ShiftExpression { (<|>|<=|>=) ShiftExpression } private function RelationalExpression():Object{ var ret:Object = ShiftExpression(); while((m = str.substr(pos).match(/^(<|>|<=|>=)/))){ switch(m[1]){ case "<": pos += 1; ret = ret < ShiftExpression(); break; case ">": pos += 1; ret = ret > ShiftExpression(); break; case "<=": pos += 2; ret = ret <= ShiftExpression(); break; case ">=": pos += 2; ret = ret >= ShiftExpression(); break; } } return ret; } // ShiftExpression = AdditiveExpression { (<<|>>|>>>) AdditiveExpression } private function ShiftExpression():Object{ var ret:Object = AdditiveExpression(); while((m = str.substr(pos).match(/^(<<|>>|>>>)/))){ switch(m[1]){ case "<<": pos += 2; ret = int(ret) << uint(AdditiveExpression()); break; case ">>": pos += 2; ret = int(ret) >> uint(AdditiveExpression()); break; case ">>>": pos += 3; ret = uint(ret) >>> uint(AdditiveExpression()); break; } } return ret; } // AdditiveExpression = MultiplicativeExpression { (+|-) MultiplicativeExpression} private function AdditiveExpression():Object{ var ret:Object = MultiplicativeExpression(); while((m = str.substr(pos).match(/^(\+|-)/))){ switch(m[1]){ case "+": pos++; ret = Number(ret) + Number(MultiplicativeExpression()); break; case "-": pos++; ret = Number(ret) - Number(MultiplicativeExpression()); break; } } return ret; } // MultiplicativeExpression = UnaryExpression { (*|/|%) UnaryExpression} private function MultiplicativeExpression():Object{ var ret:Object = UnaryExpression(); while((m = str.substr(pos).match(/^(\*|\/|%)/))){ switch(m[1]){ case "*": pos++; ret = Number(ret) * Number(UnaryExpression()); break; case "/": pos++; ret = Number(ret) / Number(UnaryExpression()); break; case "%": pos++; ret = Number(ret) % Number(UnaryExpression()); break; } } return ret; } // UnaryExpression = (delete|void|typeof|++|--|+|-|~|!) UnaryExpression // | PostfixExpression private function UnaryExpression():Object{ if((m = str.substr(pos).match(/^(delete|void|typeof|\+\+|--|\+|-|~|!)/))){ switch(m[1]){ case "delete": throw new Error("not implemented: delete"); case "void": UnaryExpression(); return undefined; case "typeof": pos += 6; return typeof UnaryExpression(); case "++": throw new Error("not implemented: ++"); case "--": throw new Error("not implemented: --"); case "+": pos += 1; return Number(UnaryExpression()); case "-": pos += 1; return -Number(UnaryExpression()); case "~": pos += 1; return ~Number(UnaryExpression()); case "!": pos += 1; return !Number(UnaryExpression()); } } return PostfixExpression(); } // TODO: Skipped some expressions // PostfixExpression = PrimaryExpression private function PostfixExpression():Object{ return PrimaryExpression(); } // PrimaryExpression = this | Identifier | Literal | ArrayLiteral | ObjectLiteral // | ( Expression ) private function PrimaryExpression():Object{ var ret:Object; var s:String = str.substr(pos); if(s.substr(0, 4) == "this"){ throw new Error("not implemented: this"); } else if(s.charAt(0) == "("){ pos++; ret = ExpressionStatement(); if(str.charAt(pos) != ")") throw new Error("No match for )"); pos++; return ret; } else if(s.charAt(0) == "["){ throw new Error("not implemented: [ ]"); } else if(s.charAt(0) == "{"){ throw new Error("not implemented: { }"); } else if(Is(Literal)){ return Literal(); } throw new Error("invalid format"); } // Literal :: // NullLiteral // BooleanLiteral // NumericLiteral // StringLiteral private function Literal():Object{ if(Is(NullLiteral)){ return NullLiteral(); } if(Is(BooleanLiteral)){ return BooleanLiteral(); } if(Is(NumericLiteral)){ return NumericLiteral(); } return null; } // NullLiteral = null private function NullLiteral():Object{ if(str.substr(pos, 4) == "null"){ pos += 4; return null; } return undefined; } // BooleanLiteral = true | false private function BooleanLiteral():Object{ if(str.substr(pos, 4) == "true"){ pos += 4; return true; }else if(str.substr(pos, 5) == "false"){ pos += 5; return false; } return undefined; } // TODO: HexDigit and ExponentialPart are not supported // NumericLiteral private function NumericLiteral():Object{ if((m = str.substr(pos).match(/^([\d\.]+)/))){ pos += m[1].length; return parseFloat(m[1]); } return undefined; } }