ming描画用言語
ビューティフルコードていう本に下向き演算子順位解析法
とかの解説があるのでそれ見るとうれしいと思う今日この頃です。
なんか、Compact言語で面白いものをと思って、flash描画言語を作ってみました。
XMLのSVGのヘボヘボバージョンだと思ってもらえれば分かりやすいと思います。
Compact言語のゴチャゴチャしたパーサでパースしてそれを、xmlのようにグリグリ周って出力してます。
いまのところ、線を描けて、アニメーションできますが、線は消せません。
演算子は定義してあるけど、使えません。使いたかったら実装してください。
こういう、言語を簡単に作る手法を作りあげたい!
そのためには、生け垣理論でオートマトンかなんかつくってらくらくできるようにしろ!
ってことなんですけど、なかなかできないわけでした。orz...
SWFMovie2(void){ setDimension(240,240) setRate(20) SWFShape2(void){ setLine(5,0,0,0) movePenTo(120, 20) drawLineTo(220, 220) drawLineTo(20, 220) drawLineTo(120, 20) } nextFrame() save("file.swf") }
こんなかんじのコードで描画できます。(void)はコンストラクタ的に使おうと思ってやめた名残です。
以下コードです。
セキュリティホールとかありまくると思われるので、ローカルで実行してくださいまし。
なにげに、html,php,compact,ming用謎言語の4つが入り乱れててわけわからなくなっている550行です。
テキストエリアにソース書いてparseを押すとパースできて、runを押すと、file.swfを作って、
運がよければ、表示されます。
ming.php
<? $in = $_REQUEST["in"]; if($in==""){ $in = <<< END SWFMovie2(void){ setDimension(240,240) setRate(20) SWFShape2(void){ setLine(5,0,0,0) movePenTo(120, 20) drawLineTo(220, 220) drawLineTo(20, 220) drawLineTo(120, 20) } SWFShape2(void){ setLine(5, 255, 0, 0) movePenTo(10, 10) drawLineTo(230, 230) } SWFShape2(void){ setLine(5, 255, 0, 0) movePenTo(230, 10) drawLineTo(10, 230) } nextFrame() SWFShape2(void){ setLine(5, 255, 255, 0) movePenTo(10, 10) drawLineTo(230, 230) } SWFShape2(void){ setLine(5, 255, 255, 0) movePenTo(230, 10) drawLineTo(10, 230) } nextFrame() save("file.swf") } END; } $p = new Parser(); $p ->set(245,"L","::") ->set(240,"F","new") ->set(240,"L",".") ->set(240,"L","->") ->set(240,"P","{","}") ->set(240,"P","(",")") ->set(240,"P","[","]") ->set(240,"M","{","}") ->set(240,"M","(",")") ->set(240,"M","[","]") ->set(230,"B","--") ->set(230,"B","++") //->set(230,"F","cast") //->set(230,"F","sizeof") ->set(230,"F","!") ->set(230,"F","~") ->set(230,"F","-") ->set(230,"F","+") ->set(230,"F","*") ->set(230,"F","&") ->set(230,"F","--") ->set(230,"F","++") ->set(220,"L","%") ->set(220,"L","/") ->set(220,"L","*") ->set(210,"L","-") ->set(210,"L","+") ->set(200,"L",">>") ->set(200,"L","<<") ->set(190,"L",">=") ->set(190,"L","<=") ->set(190,"L",">") ->set(190,"L","<") ->set(180,"L","!=") ->set(180,"L","==") ->set(170,"L","&") ->set(160,"L","^") ->set(150,"L","|") ->set(140,"L","&&") ->set(130,"L","||") ->set(120,"L","?") ->set(110,"R","=") ->set(110,"R","*=") ->set(110,"R","/=") ->set(110,"R","%=") ->set(110,"R","+=") ->set(110,"R","-=") ->set(110,"R","<<=") ->set(110,"R",">>=") ->set(110,"R","&=") ->set(110,"R","^=") ->set(110,"R","|=") ->set(110,"R",":=") ->set(115,"R",":") ->set(100,"L",",") ->set(96,"F","return") ->set(96,"F","break") ->set(96,"F","continue") ->set(95,"B",";") ->set(94,"L","else") ->set(90,"S","if","(") ->set(90,"S","for","(") ->set(90,"S","do","{") ->set(90,"S","while","(") ->set(90,"S","function","("); switch($_REQUEST[run]) { case "parse": $out = p($p->parse($in)); break; case "run": $out = execMing($p->parse($in)); } function p($str) { return htmlspecialchars($str); } class Token { var $string; var $val; var $type; function Token($string, $type, $val) { $this->string = $string; $this->type = $type; $this->val = $val; } function __toString() { return $this->string; } function toArray() { return array($this->val); } } class Msg extends Token { var $obj; var $op; var $prm; function Msg($obj, $op, $prm) { $this->obj = $obj; $this->op = $op; $this->prm = $prm; } function toArray() { if($this->op == "opRCrn") { return array(execMing($this->obj) => execMing($this->prm)); } else if($this->op == "opLCmm") { $obj = $this->obj; $prm = $this->prm; if($obj instanceof Token) $obj=$obj->toArray(); else $obj = array($obj); if($prm instanceof Token) $prm=$prm->toArray(); else $prm = array($prm); return array_merge($obj, $prm); } else { return array(); } } function __toString() { return $this->obj.".".$this->op."($this->prm)"; } } class Parser { var $token; var $str; var $val; static $newline; var $infixs = array(); var $infixModes = array(); var $prefixs = array(); var $prefixModes = array(); var $parens = array("("=>")","["=>"]","{"=>"}"); var $parenEnds = array(")"=>"(","]"=>"[","}"=>"{"); var $sparens = array(); var $names = array ( '+' => "Add", '-' => "Sub", '*' => "Mul", '/' => "Div", '<' => "Lt", '>' => "Gt", '!' => "Atn", '%' => "Per", '&' => "Amp", '=' => "Eq", '~' => "Cld", '^' => "Xor", '|' => "Or", ':' => "Crn", '.' => "Dot", '?' => "Qst", '(' => "P", ')' => "", '{' => "B", '}' => "", '[' => "A", ']' => "", ',' => "Cmm", ';' => "Scr" ); function name($m, $op) { if(array_key_exists($op->string, $this->names)) return "op".$m.$this->names[$op->string]; else return "op".$m.$op->string; } function set($p, $m, $op, $op2 = NULL) { switch ($m) { case "S": $this->sparens[$op] = $op2; case "P": case "F": $this->prefixModes[$op] = $m; $this->prefixs[$op] = $p; break; case "M": case "B": case "R": case "L": $this->infixModes[$op] = $m; $this->infixs[$op] = $p; break; } return $this; } function parse($str) { $this->str = $str; $this->advance(); return $this->expr(); } static function unescape ($string) { $map = array(b=> "\b", f=> "\f", n=> "\n", r=> "\r", t=> "\t", v=> "\v"); return preg_replace_callback( '/[\\\\](([bfnrtv])|(u[0-9A-Fa-f]{4}|x[0-9A-Fa-f]{2})|([0-7]{1,3})|.)/', create_function('$s',' return $s[2] ? $s[2] : $s[3] ? chr(intval(substr($s[3],1),16)) : $s[4] ? chr(intval($s[4], 8)) : $s[1]; '), $string ); } function advance() { Parser::$newline = FALSE; $this->token = null; $str = $this->str; $str = preg_replace_callback( '/^(\/\/[^\r\n]*[\r\n]+|\/\*\/?([\r\n]|[^\/]|[^*]\/)*\*\/|\s)*/', create_function('$s',' if(preg_match("/[\r\n]/",$s[1]))Parser::$newline = TRUE; return "";'), $str); if ($str == "") { return $this->token = new Token("null", "eof", NULL ); } else if (preg_match('/^(?:[0-9]+(?:\.[0-9]*)|(?:\.[0-9]+))(?:[eE][-+]?[0-9]+)?/', $str, $m)) { $token = new Token($m[0],"float", floatval($m[0])); } else if (preg_match('/^[0-9]+/', $str, $m)) { $token = new Token($m[0],"int", intval($m[0])); } else if (preg_match('/^[\+\-\*\/\%\=\|\&\<\>\~\^\!\?\:\.]+|^[\;\,]/', $str, $m)) { $token = new Token($m[0], "operator", $m[0]); } else if (preg_match('/^[a-zA-Z_$@][a-zA-Z_0-9$@]*/', $str, $m)) { $token = new Token($m[0], "symbol", $m[0]); } else if (preg_match('/^[\(\)\[\]\{\}]/', $str, $m)) { $token = new Token($m[0], "paren", $m[0]); } else if (preg_match('/^"(\\.|[^"])*"/', $str, $m)) { $val = Parser::unescape( substr($m[0], 1, strlen($m[0]) - 2) ); $token = new Token($m[0],"string", $val); } else { throw new Exception("tokenize error " . $str); } $this->str = substr($str, strlen($token->string)); return $this->token = $token; } function expr() { $v = $this->expn(0); if ($v == NULL) return $v; if ($this->token->val) { $e = $this->expr(); if ($e) $v = new Msg($v, "opC", $e); } return $v; } function expn($n) { $v = $this->token; if(array_key_exists($v->string, $this->parenEnds)) return NULL; $this->advance(); $lp = 0x7fffffff; while (array_key_exists($v->string, $this->prefixs)) { $op = $v; $p = $this->prefixs[$op->string]; $m = $this->prefixModes[$op->string]; if ($m == "F") { if ($p < $n) break; $v = new Msg( new Token("null","null", NULL), "opF".$this->names[$op->string], $this->expn($p)); $lp = $p; break; } if ($m == "P") { if ($p < $n) break; $e = $this->expn(0); if ($this->token != $this->parens[$op->string]) throw new Exception("error"); $v = new Msg( new Token("null","null", NULL), $this->name($m, $op), $e ); $this->advance(); $lp = $p; break; } if ($m == "S") { //if (p < n) break; $p1 = $this->token->string; if ($this->sparens[$op->string] != $p1) break; $this->advance(); $e = $this->expr(); $p2 = $this->parens[$p1]; if ($this->token->string != $p2) throw new Exception("found '"+$this->token+"' when expecting '"+$p2+"'"); $this->advance(); $v = new Msg($e, "opS".$this->names[$v->string], $this->expr(p)); $lp = $p; break; } break; } while(array_key_exists($this->token->string, $this->infixs)) { $op = $this->token; $p = $this->infixs[$op->string]; $m = $this->infixModes[$op->string]; if ($m == "B") { if ($p <= $n || $p > $lp) break; $this->advance(); $v = new Msg($v, "opB".$this->names[$op->string], $v); $lp = $p; continue; } if ($m == "M") { if ($p <= $n || $p > $lp || Parser::$newline) break; $this->advance(); $e = $this->expr(); if ($this->token != $this->parens[$op->string]) throw new Exception("error"); $v = new Msg($v, "opM".$this->names[$op->string], $e); $this->advance(); $lp = $p; continue; } if ($m == "L") { if ($p <= $n || $p > $lp) break; $this->advance(); $v = new Msg($v, "opL".$this->names[$op->string], $this->expn($p)); $lp = $p; continue; } if ($m == "R") { if ($p < $n || $p > $lp) break; $this->advance(); $v = new Msg($v, "opR".$this->names[$op->string], $this->expn($p)); $lp = $p; continue; } } return $v; } } $m = new SWFMovie(); $m->setDimension(240,240); $m->setRate(10); $m->save("f.swf"); class SWFMovie2 { var $width; var $height; var $movie; function SWFMovie2() { $this->movie = new SWFMovie(); } function SWFShape2($data) { $shape = new SWFShape2($data); $this->movie->add($shape->movie); return $shape; } public function setRate($data) { $prm = $data->toArray(); $this->movie->setRate($prm[0]); return $this; } public function opMB($data) { if($data instanceof Msg) { $obj = $this->opMB($data->obj); $op = $data->op; //echo "<hr>".$data; switch($op) { case "opC": $this->opMB($obj); return $this->opMB($data->prm); case "opMB": if($obj instanceof String) return $this->$obj->$op($data->prm); else return $obj->$op($data->prm); case "opMP": return $this->$obj($data->prm); default: return $this->$obj->$op($this->opMB($data->prm)); } } if($data instanceof Token) return $data->val; return $data; } function opC($data) { return opMB($data); } function nextFrame($data) { $this->movie->nextFrame(); return $this; } function setDimension($data) { $prm = $data->toArray(); $this->width=$prm[0]; $this->height=$prm[1]; $this->movie->setDimension($prm[0], $prm[1]); return $this; } function save($file) { $this->movie->save($file->val); $file = $file->val."?".rand(); return <<< END <OBJECT classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000" codebase="http://fpdownload.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=4,0,0,0" WIDTH="$this->width" HEIGHT="$this->height" id="ming" ALIGN=""> <PARAM NAME="allowScriptAccess" VALUE="sameDomain"> <PARAM NAME="movie" VALUE="$file"> <PARAM NAME="menu" VALUE="false"> <PARAM NAME="quality" VALUE="high"> <EMBED src="$file" menu="false" quality="high" WIDTH="$this->width" HEIGHT="$this->height" NAME="ming" align="" allowScriptAccess="sameDomain" type="application/x-shockwave-flash" pluginspage="http://www.macromedia.com/go/getflashplayer"> </EMBED> </OBJECT> END ; } function __toString() { return "SWFMovie2"; } } function SWFMovie2($data) { return new SWFMovie2($data); } class SWFShape2 { var $width; var $height; var $movie; function SWFShape2() { //echo "SWFShape2"; $this->movie = new SWFShape(); } public function opMB($data) { //echo "SWFShape2.opMB"; if($data instanceof Msg) { $obj = $this->opMB($data->obj); $op = $data->op; switch($op) { case "opC": return $this->opMB($obj)->opMB($data->prm); case "opMB": if($obj instanceof String) return $this->$obj->$op($data->prm); return $obj->$op($data->prm); case "opMP": return $this->$obj($data->prm); default: return $this->$obj->$op($this->opMB($data->prm)); } } if($data instanceof Token) return $data->val; return $data; } function opC($data) { return opMB($data); } // $shape3->setRightFill($shape3->addFill(0, 0xFF, 0)); function setLine($data) { $prm = $data->toArray(); $this->movie->setLine($prm[0], $prm[1],$prm[2],$prm[3]); return $this; } function movePenTo($data) { $prm = $data->toArray(); $this->movie->movePenTo($prm[0], $prm[1]); return $this; } function drawLineTo($data) { $prm = $data->toArray(); $this->movie->drawLineTo($prm[0], $prm[1]); return $this; } function __toString() { return "SWFShape2tostring"; } } function SWFShape2($data) { return new SWFShape2($data); } function execMing($data) { if($data instanceof Msg) { $obj = execMing($data->obj); $op = $data->op; switch($op) { case "opC": execMing($obj); return execMing($data->prm); case "opMB": return $obj->$op($data->prm); case "opMP": return $obj($data->prm); default: return $obj->$op(execMing($data->prm)); } } if($data instanceof Token) return $data->val; return $data; } ?> <html> <body> <form method="post"> <input type="submit" value="parse" name="run"> <input type="submit" value="run" name="run"><br/> <textarea name="in" rows=10 cols=80><?=p($in)?></textarea><br/> </form> <pre> <?=$out?> </pre> </body> </html>