/**
* Copyright termat ( http://wonderfl.net/user/termat )
* MIT License ( http://www.opensource.org/licenses/mit-license.php )
* Downloaded from: http://wonderfl.net/c/idLq
*/
package
{
/**
勉強がてら、簡易タートルグラフィクス環境を作ってみた。
下部のテキストフィールドにLispもどきのスクリプトをいれ、
「右クリック」→「メニュー:実行」で描画が開始される。
まだ、関数定義ができないので、たいした図形は描けそうにない。
とりあえず、ソースの行数500~600程度に抑えて、コッホ曲線が
描けるようにしたいなあ。
*/
import flash.display.Sprite;
import flash.events.ContextMenuEvent;
import flash.events.Event;
import flash.text.TextField;
import flash.text.TextFieldType;
import flash.ui.ContextMenu;
import flash.ui.ContextMenuItem;
[SWF(framerate="30",width="480",height="480",backgroundColor="0xffffff")]
public class Turtle extends Sprite
{
private var text:TextField;
private var vm:VM;
private var tg:TurtleGraphics;
public function Turtle()
{
text = new TextField();
text.width = 470;
text.height = 70;
text.multiline = true;
var str:String = "(width 4)\n(point (350 250))\n(def n 0)\n(def r 255)\n(def g 0)\n(def b 0)\n";
str = str + "(while (< n 200)\n((color (r g b))\n(move (- 50 (/ n 4)))\n(turn -20)\n(set n (+ n 1))\n";
str=str+"(set r ( - r 2))\n(set g ( + g 1))\n(set b (+ b 2)))\n)"
text.text = str;
text.border = true;
text.type = TextFieldType.INPUT;
text.x = 5;
text.y = 405;
this.addChild(text);
var cm:ContextMenu = new ContextMenu();
cm.hideBuiltInItems();
addItem("実 行",cm);
addItem("コマンド消去",cm);
addItem("画面初期化",cm);
text.contextMenu = cm;
tg = new TurtleGraphics(this, 480, 400);
vm = new VM();
TurtleFunc.turtle = tg;
tg.init();
addEventListener(Event.ENTER_FRAME,tg.play);
}
private function addItem(str:String,cm:ContextMenu):void {
var item1:ContextMenuItem = new ContextMenuItem(str);
item1.separatorBefore = true;
item1.enabled = true;
cm.customItems.push(item1);
item1.addEventListener(ContextMenuEvent.MENU_ITEM_SELECT, onSelectItem);
}
private function interpret():void {
vm.run(text.text);
vm.stackFlush();
tg.playing = true;
}
private function onSelectItem(e:ContextMenuEvent):void {
var str:String = e.target.caption;
if (str == "コマンド消去") {
text.text = "";
}else if (str == "実 行") {
interpret();
}else if (str == "画面初期化") {
vm.stackFlush();
tg.clear();
tg.init();
}
}
}
}
import flash.display.MovieClip;
import flash.display.Sprite;
import flash.events.WeakMethodClosure;
import flash.geom.Point;
import flash.events.Event;
//import org.flashdevelop.utils.FlashConnect;
/* パーサー */
class Parser {
private const LPAR:int="(".charCodeAt(0);
private const RPAR:int=")".charCodeAt(0);
private const PLUS:int="+".charCodeAt(0);
private const MINUS:int="-".charCodeAt(0);
private const MUL:int="*".charCodeAt(0);
private const DIV:int="/".charCodeAt(0);
private const EQ:int="=".charCodeAt(0);
private const GT:int=">".charCodeAt(0);
private const LT:int="<".charCodeAt(0);
private const WHITE:int = " ".charCodeAt(0);
private const DIGIT_0:int = "0".charCodeAt(0);
private var line:String;
private var index:int;
private var currentLine:int;
/* 文字列の解釈 */
public function parse(str:String):Array {
if (str.length==0) return new Array();
index = 0;
currentLine = 0;
var code:Array = new Array();
line = str;
line = (line.split("\r\n")).join("\n");
line = (line.split("\r")).join("\n");
while (index < line.length) {
var o:Object = getToken();
if (o != null) code.push(o);
}
return code;
}
/* 解釈したコードの表示 */
public static function show(c:Array):String {
var ret:String = "(";
for (var i:int = 0; i < c.length; i++) {
var o:Object = c[i];
if (o is Array) {
ret = ret + show(o as Array)+" ";
}else if (o is Symbol) {
ret = ret + (o as Symbol).toStr()+" ";
}else if (o is Func) {
ret = ret + (o as Func).toStr()+" ";
}else {
ret = ret + o.toString()+" ";
}
}
ret = ret + ")";
return ret;
}
private function getToken():Object {
if (index < line.length) {
var str:String = line.charAt(index++);
if (isDigit(str)) return number(str);
if (isDelimiter(str)) return getToken();
var ch:int = str.charCodeAt(0);
switch(ch) {
case MUL:
case DIV:
case EQ:
case GT:
case LT:
return symbol(str);
case LPAR:
return cell();
case PLUS:
case MINUS:
return plus(str);
default:
return symbol(str);
}
}else {
return null;
}
}
private function isDelimiter(s:String):Boolean {
var n:int = s.charCodeAt(0);
if (n == WHITE) {
return true;
}else {
return (s == "\n");
}
}
private function isDigit(s:String):Boolean {
var ch:int = s.charCodeAt(0);
return (ch >= DIGIT_0 && ch <= DIGIT_0 + 9);
}
private function symbol(ch:String):Object {
var ret:String = ch;
while (index < line.length) {
var str:String = line.charAt(index++);
if(isDelimiter(str)){
break;
}else {
if (str.charCodeAt(0) == RPAR) {
index--;
return new Symbol(ret);
}else {
ret = ret + str;
}
}
}
return new Symbol(ret);
}
private function plus(ch:String):Object {
var str:String = line.charAt(index++);
if (isDelimiter(str)) {
return new Symbol(ch);
}else if (isDigit(str)) {
return number(ch+str);
}else {
return symbol(ch);
}
}
private function number(ch:String):Object {
while (index < line.length) {
var str:String = line.charAt(index++);
if (isDigit(str)) {
ch = ch + str;
}else if (isDelimiter(str)) {
break;
}else {
if (str.charCodeAt(0) == RPAR) {
index--;
return parseFloat(ch);
}else {
throw(new Error("Syntax Error. line="+currentLine.toString(),0));
}
}
}
return parseFloat(ch);
}
private function cell():Object{
var ret:Array = new Array();
var obj:Object;
while (true) {
if (index < line.length) {
var str:String = line.charAt(index);
if (str == "\n") {
currentLine++;
index++;
}else if(isDelimiter(str)){
index++;
}else {
var ch:int = str.charCodeAt(0);
switch(ch){
case RPAR:
index++;
return ret;
default:
obj = getToken();
if (obj != null) ret.push(obj);
}
}
}else{
break;
}
}
throw(new Error("Syntax Error. line="+currentLine.toString(),0));
}
public function compile(list:Array):Array {
var ret:Array = new Array();
while (list.length>0) {
var obj:Object = list.shift();
if (obj is Array) {
ret.push(compile(obj as Array));
}else if (obj is Symbol) {
ret.push(obj);
ret.push(VM.ACC);
}else {
ret.push(obj);
}
}
return ret;
}
}
class Env {
private var map:Array;
private var parent:Env = null;
public function Env(e:Env=null):void {
parent = e;
map = new Array();
}
/* 上位の環境を取得 */
public function getParent():Env {
return parent;
}
/* 最上位の環境を取得 */
public function getRoot():Env {
if (parent == null) {
return this;
}else {
return parent.getRoot();
}
}
/* シンボルの登録 */
public function put(key:Symbol, val:Object):void {
map[key.str] = val;
}
/* 値の取得 */
public function get(key:Symbol):Object {
if (map[key.str] != undefined) {
return map[key.str];
}else {
if (parent == null) {
return null;
}else {
return parent.get(key);
}
}
}
/* 値の更新 */
public function set(key:Symbol, val:Object):void {
if (map[key.str] == undefined) {
if (parent != null) {
parent.set(key, val);
}
}else {
map[key.str] = val;
}
}
}
/* シンボルオブジェクト */
class Symbol {
public var str:String;
public function Symbol(s:String):void {
str = s.toLocaleLowerCase();
}
public function toStr():String{return "["+str+"]"}
}
/* インタプリタ */
class VM {
public static const ACC:Symbol = new Symbol("#ACC");
internal var stack:Array;
internal var env:Env = new Env();
internal var dump:Array;
private var parser:Parser = new Parser();
public function VM():void {
stack = new Array();
env = new Env();
dump = new Array();
env.put(new Symbol("+"), new NumFunc(NumFunc.ADD));
env.put(new Symbol("-"), new NumFunc(NumFunc.SUB));
env.put(new Symbol("*"), new NumFunc(NumFunc.MUL));
env.put(new Symbol("/"), new NumFunc(NumFunc.DIV));
env.put(new Symbol("="), new CompFunc(CompFunc.EQ));
env.put(new Symbol(">"), new CompFunc(CompFunc.GT));
env.put(new Symbol("<"), new CompFunc(CompFunc.LT));
env.put(new Symbol("and"), new CompFunc(CompFunc.AND));
env.put(new Symbol("or"), new CompFunc(CompFunc.OR));
env.put(new Symbol("def"), new Bind(Bind.DEF));
env.put(new Symbol("set"), new Bind(Bind.SET));
env.put(new Symbol("while"), new While());
env.put(new Symbol("move"), new TurtleFunc(TurtleFunc.MOVE));
env.put(new Symbol("turn"), new TurtleFunc(TurtleFunc.TURN));
env.put(new Symbol("point"), new TurtleFunc(TurtleFunc.POINT));
env.put(new Symbol("color"), new TurtleFunc(TurtleFunc.COLOR));
env.put(new Symbol("width"), new TurtleFunc(TurtleFunc.WIDTH));
env.put(new Symbol("dir"), new TurtleFunc(TurtleFunc.DIR));
env.put(new Symbol("clear"), new TurtleFunc(TurtleFunc.CLEAR));
}
public function run(s:String):void {
var c:Array = parser.parse(s);
// FlashConnect.trace(Parser.show(c));
eval(c);
stackFlush();
}
public function stackFlush():void {
// while(stack.length>0)FlashConnect.trace(stack.pop());
}
public function eval(code:Array):void {
pushEnv();
var obj:Object;
while (code.length > 0) {
obj = code.shift();
if (obj is Array) {
eval(obj as Array);
}else if(obj is Func){
(obj as Func).accept(code,this);
}else if (obj is Symbol) {
obj = env.get(obj as Symbol);
if (obj != null) {
code.unshift(obj);
}else {
throw(new Error("Error. :"+obj,0));
}
}else {
stack.push(obj);
}
}
popEnv();
}
public function pushEnv():void {
dump.push(new Array(stack, env));
stack = new Array();
env = new Env(env);
}
public function popEnv():void {
var tmp:Array = dump.pop();
var val:Array = stack;
stack = tmp[0] as Array;
if (val.length == 1) {
stack.push(val.pop());
}else if (val.length > 1) {
stack.push(val);
}
env = tmp[1] as Env;
}
}
class Func {
protected var id:int;
public function Func(i:int):void {id = i;}
public function accept(code:Array, vm:VM):void {}
public function toStr():String{return "#FUNC"}
}
class Bind extends Func {
public static const DEF:int = 0;
public static const SET:int = 1;
public function Bind(i:int ):void {
super(i);
}
public override function accept(code:Array, vm:VM):void {
vm.pushEnv();
var obj:Object = code.shift();
if (obj is Symbol) {
var sym:Symbol = obj as Symbol;
var val:Object = code.shift();
if (val is Array) {
vm.eval(val as Array);
bind(vm,sym,vm.stack.pop());
}else if (val is Symbol) {
val = vm.env.get(val as Symbol);
if (val == null) {
throw(new Error("Error. :"+obj,0));
}else {
bind(vm,sym,val);
}
}else {
bind(vm,sym,val);
}
while (code.length > 0) code.pop();
}else {
throw(new Error("Error. :"+obj,0));
}
vm.popEnv();
}
private function bind(vm: VM, sym:Symbol,obj:Object):void {
if (id == DEF) {
vm.env.getRoot().put(sym,obj);
}else {
var tmp:Object = vm.env.get(sym);
if (tmp == null) {
vm.env.put(sym,obj);
}else {
vm.env.set(sym, obj);
}
}
}
}
class NumFunc extends Func {
public static const ADD:int = 0;
public static const SUB:int = 1;
public static const MUL:int = 2;
public static const DIV:int = 3;
public function NumFunc(i:int ):void {
super(i);
}
public override function accept(code:Array,vm:VM):void {
vm.pushEnv();
var obj:Object;
while (code.length > 0) {
obj = code.shift();
if (obj is Array) {
vm.eval(obj as Array);
number(vm, vm.stack.pop() as Number);
}else if (obj is Symbol) {
obj = vm.env.get(obj as Symbol);
if (obj != null) {
code.unshift(obj);
}else {
throw(new Error("Error. :"+obj,0));
}
}else if (obj is Number) {
number(vm, obj as Number);
}else if(obj is Boolean){
boolean(vm,obj as Boolean);
}else {
throw(new Error("Error. :"+obj,0));
}
}
vm.popEnv();
}
protected function number(vm:VM, n:Number):void {
if (vm.stack.length == 0) {
vm.stack.push(n);
}else {
var val:Number = vm.stack.pop() as Number;
switch(id) {
case ADD:
vm.stack.push(val + n);
break;
case SUB:
vm.stack.push(val - n);
break;
case MUL:
vm.stack.push(val * n);
break;
default:
vm.stack.push(Math.round(val / n));
break;
}
}
}
protected function boolean(vm:VM, n:Boolean):void {
throw(new Error("Error. :"+n,0));
}
}
class While extends Func {
private var flg:Array;
private var code:Array;
public function While() {
super(0);
}
public override function accept(code:Array, vm:VM):void {
this.flg = code[0] as Array;
this.code = code[1] as Array;
doLoop(vm);
}
private function doLoop(vm:VM):void {
vm.pushEnv();
var limit:int = 0;
while (isLoop(vm)) {
var tp:Array = copyArray(code);
vm.eval(tp);
if (limit++ > 400) break; //暴走防止。現段階では、ループは400回までに制限
}
vm.popEnv();
}
private function isLoop(vm:VM):Boolean {
vm.pushEnv();
var tp:Array = copyArray(flg);
vm.eval(tp);
var val:Object = vm.stack.pop();
vm.popEnv();
if (val is Boolean) {
return val;
}else {
throw(new Error("Error. :",0));
}
}
private function copyArray(s:Array):Array {
var ret:Array = new Array();
for (var i:int = 0; i < s.length; i++) {
if (s[i] is Array) {
ret.push(copyArray(s[i] as Array));
}else {
ret.push(s[i]);
}
}
return ret;
}
}
class CompFunc extends NumFunc {
public static const EQ:int = 0;
public static const GT:int = 1;
public static const LT:int = 2;
public static const AND:int = 3;
public static const OR:int = 4;
public function CompFunc(i:int ):void {
super(i);
}
protected override function number(vm:VM, n:Number):void {
if (vm.stack.length == 0) {
vm.stack.push(n);
}else {
var val:Number = vm.stack.pop() as Number;
switch(id) {
case EQ:
vm.stack.push((val == n));
break;
case GT:
vm.stack.push((val > n));
break;
case LT:
vm.stack.push((val < n));
break;
default:
throw(new Error("Error. :"+n,0));
}
}
}
protected override function boolean(vm:VM, n:Boolean):void {
if (vm.stack.length == 0) {
vm.stack.push(n);
}else {
var val:Boolean = vm.stack.pop() as Boolean;
switch(id) {
case EQ:
case GT:
case LT:
throw(new Error("Error. :" + n, 0));
case AND:
vm.stack.push(val && n);
case AND:
vm.stack.push(val || n);
default:
throw(new Error("Error. :"+n,0));
}
}
}
}
class TurtleFunc extends Func {
public static var turtle:TurtleGraphics;
public static const MOVE:int = 0;
public static const TURN:int = 1;
public static const POINT:int = 2;
public static const COLOR:int = 3;
public static const WIDTH:int = 4;
public static const DIR:int = 5;
public static const CLEAR:int = 6;
public function TurtleFunc(i:int):void {
super(i);
}
public override function accept(code:Array, vm:VM):void {
vm.pushEnv();
var obj:Object = code.shift();
if (obj is Number) {
play(obj);
}else if (obj is Array) {
vm.eval(obj as Array);
play(vm.stack.pop());
}else if (obj is Symbol) {
obj = vm.env.get(obj as Symbol);
if (obj != null) {
code.unshift(obj);
}else {
throw(new Error("Error. :"+obj,0));
}
}else {
throw(new Error("Error. :"+obj,0));
}
vm.popEnv();
}
private function play(obj:Object):void {
switch(id) {
case MOVE:
if (obj is Number) {
turtle.move(obj as Number);
}else {
throw(new Error("Error. :"+obj,0));
}
break;
case TURN:
if (obj is Number) {
turtle.turn(obj as Number);
}else {
throw(new Error("Error. :"+obj,0));
}
break;
case POINT:
if (obj is Array) {
var tp:Array = obj as Array;
turtle.setPoint(tp[0] as Number, tp[1] as Number);
}else {
throw(new Error("Error. :"+obj,0));
}
break;
case COLOR:
if (obj is Array) {
tp= obj as Array;
turtle.setLineColor(tp[0] as Number,tp[1] as Number,tp[2] as Number);
}else {
throw(new Error("Error. :"+obj,0));
}
break;
case WIDTH:
if (obj is Number) {
turtle.setLineWidth(obj as Number);
}else {
throw(new Error("Error. :"+obj,0));
}
break;
case DIR:
if (obj is Number) {
turtle.dir(obj as Number);
}else {
throw(new Error("Error. :"+obj,0));
}
break;
case CLEAR:
turtle.clear();
}
}
}
/* タートルグラフィクス */
class TurtleGraphics {
private var sp:Sprite;
private var pos:Point;
private var angle:Number;
private var vx:Number;
private var vy:Number;
private var lineColor:uint;
private var lineWidth:Number;
private var data:Array;
public var playing:Boolean = false;
public function TurtleGraphics(s:Sprite,w:Number,h:Number):void {
sp = s;
pos = new Point();
sp.graphics.lineStyle(1, 0) ;
sp.graphics.drawRect(1, 1, w - 2, h - 2);
data = new Array();
}
public function init():void {
clear();
pos.x = sp.width / 2;
pos.y = sp.height / 2;
angle = -90;
turn(0);
setLineColor(0,0,0);
setLineWidth(1);
}
public function play(e:Event):void {
if (playing && data.length > 0) {
var l:Line = data.shift();
sp.addChild(l);
if (data.length == 0) playing = false;
}
}
public function turn(deg:Number):void {
angle += deg;
vx = Math.cos(angle / 180 * Math.PI);
vy = Math.sin(angle / 180 * Math.PI);
}
public function move(v:Number):void {
var p2:Point = new Point(pos.x + vx * v, pos.y + vy * v);
var l:Line = new Line(pos, p2,lineColor,lineWidth);
data.push(l);
pos = p2;
}
public function dir(deg:Number):void {
angle = deg;
vx = Math.cos(angle / 180 * Math.PI);
vy = Math.sin(angle / 180 * Math.PI);
}
public function setPoint(xx:Number, yy:Number ):void {
pos.x = xx;
pos.y = yy;
}
public function clear():void {
while (sp.numChildren > 1) {
sp.removeChildAt(sp.numChildren - 1);
}
}
public function setLineColor(r:Number, g:Number, b:Number):void {
if (r > 255) r = 255;
if (g > 255) g = 255;
if (b > 255) b = 255;
if (r < 0) r = 0;
if (g < 0) g = 0;
if (b < 0) b = 0;
lineColor = (r<<16)+(g<<8)+b as uint;
}
public function setLineWidth(c:Number):void {
lineWidth = c;
}
}
class Line extends MovieClip {
public function Line(p0:Point, p1:Point, col:uint, wd:Number) {
graphics.lineStyle(wd, col);
graphics.moveTo(p0.x, p0.y);
graphics.lineTo(p1.x,p1.y);
}
}