ming描画用言語

ビューティフルコードていう本に下向き演算子順位解析法
とかの解説があるのでそれ見るとうれしいと思う今日この頃です。
なんか、Compact言語で面白いものをと思って、flash描画言語を作ってみました。
XMLSVGのヘボヘボバージョンだと思ってもらえれば分かりやすいと思います。
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>