電卓!
Simple Recursive Descent Parsing
see also: http://fxp.hp.infoseek.co.jp/arti/parser.html
電卓!
* パーサの練習として下のページ見ながら電卓作った!
* wonderflのコードには載ってないけど、AS3Unit使ってテストがんがん使った。
* めんどくさかったのが、数値かどうかの判定と、パーサーエラーの処理。
*
* 使えるやつ
* 整数、少数、()、+-/*
* 16進数、指数
* @see http://d.hatena.ne.jp/nitoyon/20090128/as3_simple_parser
* @see http://www.ibm.com/developerworks/jp/java/library/j-scala10248.html
* @see http://d.hatena.ne.jp/ActionScript/20090512/as3_exponentiation
* @author coppieee
// forked from nitoyon's Simple Recursive Descent Parsing
// Simple Recursive Descent Parsing
// see also: http://fxp.hp.infoseek.co.jp/arti/parser.html
/**
* 電卓!
* パーサの練習として下のページ見ながら電卓作った!
* wonderflのコードには載ってないけど、AS3Unit使ってテストがんがん使った。
* めんどくさかったのが、数値かどうかの判定と、パーサーエラーの処理。
*
* 使えるやつ
* 整数、少数、()、+-/*
* 16進数、指数
* @see http://d.hatena.ne.jp/nitoyon/20090128/as3_simple_parser
* @see http://www.ibm.com/developerworks/jp/java/library/j-scala10248.html
* @see http://d.hatena.ne.jp/ActionScript/20090512/as3_exponentiation
* @author coppieee
*/
package {
import flash.display.Sprite;
import flash.events.Event;
import flash.text.TextField;
import flash.text.TextFieldAutoSize;
import flash.text.TextFieldType;
public class Main extends Sprite{
public function Main(){
var tf:TextField = new TextField();
tf.x = 20;
tf.y = 20;
tf.width = 200;
tf.height = 20;
tf.multiline = false;
tf.border = true;
tf.type = TextFieldType.INPUT;
addChild(tf);
tf.addEventListener(Event.CHANGE, function(e:Event):void {
try{
out.text = Parser.parse(tf.text) + "";
}catch(e:ParserError) {
out.htmlText = e.codeToHtmlString();
}
});
var out:TextField = new TextField();
out.height = tf.height;
out.width = tf.width;
out.y = tf.height + tf.y;
out.x = tf.x;
addChild(out);
stage.focus = tf;
scaleX = 2;
scaleY = 2;
tf.text = "-1.1 + 2 * (0xf + 1e+3)";
tf.dispatchEvent(new Event(Event.CHANGE));
}
}
}
//package
//{
//public
class Parser{
public static function parse(source:String):Number {
var parser:Parser = new Parser(source);
return parser.input();
}
public function Parser(source:String) {
_source = source;
}
public function get source():String { return _source; }
private var _source:String;
public function get currentSource():String {
return _source.substr(index);
}
public function get currentChar():String {
if (_source.length <= index) { return null; }
return _source.charAt(index);
}
public function get index():int { return _index; }
private var _index:int;
//input ::= ws expr ws eoi
public function input():Number {
ws();
var result:Number = expr();
ws();
if (!isEoi) {
throw new ParserError("not eoi",index,source);
}
return result;
}
private function get isEoi():Boolean {
return _index == source.length;
}
//expr ::= ws term { ws (+|-) ws term}
public function expr():Number {
ws();
var result:Number = term();
while(true){
ws();
var resultAdd:Function;
if (currentChar == "+") {
resultAdd = function():void { result += term(); }
}else if (currentChar == "-") {
resultAdd = function():void { result -= term(); }
}else {
break;
}
var exprIndex:int = _index;
_index++;
ws();
if (isEoi) {
throw new ParserError("+-",exprIndex,source);
}
resultAdd();
}
return result;
}
//term ::= ws fact { ws (*|/) ws fact}
public function term():Number {
ws();
var result:Number = fact();
while (true) {
ws();
var resultAdd:Function;
if (currentChar == "*") {
resultAdd = function():void { result *= fact(); }
} else if(currentChar =="/") {
resultAdd = function():void { result /= fact(); }
}else {
break;
}
var termIndex:int = _index;
_index++;
ws();
if (isEoi) {
throw new ParserError("*/",termIndex,source);
}
resultAdd();
}
return result;
}
//fact ::= ws (expr) | -|+ ws fact | ws number
public function fact():Number {
ws();
if (currentChar == "(") {
var parentheseIndex:int = index;
_index ++;
ws();
if (_index == _source.length) {
throw new ParserError("()error",parentheseIndex,source);
}
var result:Number = expr();
if (currentChar == ")") {
_index++;
return result;
}
throw new ParserError("()error",parentheseIndex,source);
}else if (currentChar == "-") {
var subIndex:int = _index;
_index++;
ws();
if (isEoi) { throw new ParserError("-",subIndex,source); }
return - fact();
}else if (currentChar == "+") {
var addIndex:int = _index;
_index++;
ws();
if (isEoi) { throw new ParserError("+",addIndex,source); }
return fact();
}else {
return number();
}
}
//ws ::= [{\s|\t|\n|\r}]
public function ws():void {
var w:Object = /\s+/.exec(currentSource);
if (w != null && w.index==0) {
_index += w[0].length;
}
}
//number ::= 0x{dgt|a-f} |
// ([{dgt}].{dgt} | [{{dgt})(e[-|+] {dgt}})] |
// | .{dgt}
public function number():Number {
function getNum():Number{
var n16:Object = /0x[\da-f]+/i.exec(currentSource);
if (n16 != null && n16.index == 0) {
_index += n16[0].length;
return parseInt(n16[0]);
}
var n:Object =
/(((\d*\.\d+)|(\d+))(e(\-|\+)?\d+)?)|(\.\d+)/i.
exec(currentSource);
if (n != null && n.index == 0) {
_index += n[0].length;
return parseFloat(n[0]);
}
throw new ParserError("numberError index:"+index+",source:"+source, index, source);
}
var result:Number = getNum();
if (currentChar != null &&
!currentChar.match(/[\(\)\s\+\-\*\/]/i)) {
throw new ParserError("numberError:",index,source);
}
return result;
}
}
//}
//package {
//public
class ParserError extends Error {
public function get col():int { return _col; }
private var _col:int;
public function get source():String { return _source; }
private var _source:String;
public function ParserError(message:String,col:int,source:String ) {
_col = col;
_source = source;
super(message);
}
public function codeToHtmlString():String {
var str:String = "";
str += (_col == 0) ? "" : _source.substr(0, _col);
str += "<font color='#FF0000'>" + _source.charAt(_col) + "</font>";
str += (_col == _source.length) ? "" : _source.substr(_col + 1);
return str;
}
}
//}