// Flash プレイヤー用 Tiny BASIC インタープリタ // このプログラムは,Li-Chen Wang さんが作成された“Palo Alto Tiny BASIC”を基にしています. // この Flash バージョンは 馬渕義彦 が作成しました. package { import flash.display.Sprite; import flash.display.Shape; import flash.display.Stage; import flash.display.StageScaleMode; import flash.display.SimpleButton; import flash.text.TextField; import flash.text.TextFieldType; import flash.text.TextFieldAutoSize; import flash.text.TextFormat; import flash.text.TextLineMetrics; import flash.text.TextFormatAlign; import flash.events.Event; import flash.events.MouseEvent; import flash.events.KeyboardEvent; import flash.events.TextEvent; import flash.events.FocusEvent; import flash.events.IOErrorEvent; import flash.events.TimerEvent; import flash.system.fscommand; import flash.system.Capabilities; import flash.net.FileReference; import flash.net.FileFilter; import flash.utils.*; [SWF(width=600, height=472, frameRate=30, backgroundColor=0xCCFFFF)] public class tbasic extends Sprite { private var scr:TextField; // 画面 private var cur:Shape; // カーソル private var load:SimpleButton; // 読み込みボタン private var save:SimpleButton; // 保存ボタン private var ctlc:SimpleButton; // Ctrl-C ボタン private var msg:Sprite; // メッセージ表示 private var msg_text:TextField; // メッセージ文字列 private var msg_btn:SimpleButton; // 確認ボタン private var msg_post:Function; // 後処理 private var key:TextField; // ダミー入力フィールド private var cont_exec:Timer; // コマンド実行用ダミー タイマー private var cont_list:Timer; // LIST 用ダミー タイマー private var cont_input:Timer; // INPUT 用ダミー タイマー private var file_ref:FileReference = null; // ファイル 読み込み/保存 private var file_name:String = null; // ファイル名 // ダイレクト コマンド private var token1:Array = ["LIST", "NEW", "RUN"]; private var func1:Array = [com_list, com_new, com_run]; // ダイレクト/ステートメント コマンド private var token2:Array = ["NEXT", "LET", "IF", "GOTO", "GOSUB", "RETURN", "REM", "FOR", "INPUT", "PRINT", "STOP"]; private var func2:Array = [com_next, com_let, com_if, com_goto, com_gosub, com_return, com_rem, com_for, com_input, com_print, com_stop]; // 関数 private var token3:Array = ["RND", "ABS", "SIZE"]; private var func3:Array = [fnc_rnd, fnc_abs, fnc_size]; private var cur_x:int, cur_y:int; // 画面 現在位置 private var line:Array = []; // プログラム保存領域 private var i_current:int; // 現在の行インデックス(-1: ダイレクト モード) private var list:Boolean, input:Boolean; // LIST,INPUT 処理中フラグ private var tb_vars:Array = new Array(26); // 変数 [0] ~ [25]: A ~ Z,[26] ~: @ 配列 private var text:String; // コマンド文字列 private var list_i_line:int; // LIST 行インデックス private var list_lines:int; // LIST 行数 private var for_info:Object; // FOR ループ情報 private var for_stack:Array = []; // FOR ループ情報スタック private var gosub_stack:Array = []; // GOSUB 情報スタック private var input_text:String; // INPUT 文字列退避領域 private var input_i_var:int; // INPUT 変数インデックス private var buffer:String; // 入力バッファ private var brk:Boolean; // 中断フラグ // stage サイズ初期値 // scaleMode が "noScale" の場合,画面をリサイズすると stageWidth,stageHeight の // 値は変わるが,stage の子オブジェクトの位置の原点は移動しないため,stageWidth, // stageHeight を基にして子オブジェクトを配置すると,位置がおかしくなる. private const STAGE_WIDTH:int = 600; private const STAGE_HEIGHT:int = 472; private const INT_ERR:int = 0x7FFFFFFF; public function tbasic():void { if (stage) init(); else addEventListener(Event.ADDED_TO_STAGE, init); } private function init(e:Event = null):void { removeEventListener(Event.ADDED_TO_STAGE, init); stage.scaleMode = StageScaleMode.NO_SCALE; try { fscommand("trapallkeys", "true"); } catch(e:Error) { } var fmt:TextFormat; // 表示オブジェクト作成 // 画面 scr = new TextField(); with(scr) { y = 32; type = TextFieldType.DYNAMIC; selectable = false; multiline = true; background = true; backgroundColor = 0x000000; fmt = new TextFormat(); with(fmt) { font = "_typewriter"; align = TextFormatAlign.LEFT; leading = 0; color = 0xFFFFFF; } // 位置/サイズ調整 var tlm:TextLineMetrics; scr.text = "X"; for(var size:int = 14; ; size--) { fmt.size = size; setTextFormat(fmt); tlm = getLineMetrics(0); width = 80 * tlm.width + 4; height = 25 * tlm.height + 4; if(width <= STAGE_WIDTH && height <= 392) break; } scr.text = ""; x = (STAGE_WIDTH - width) >> 1; defaultTextFormat = fmt; } addChild(scr); // カーソル cur = new Shape(); cur.visible = false; with(cur.graphics) { lineStyle(); beginFill(0xFFFFFF); drawRect(0, 0, tlm.width, tlm.height); endFill(); } addChild(cur); // タイトル var ttl:TextField = new TextField(); with(ttl) { x = 0; y = 8; width = 80 * tlm.width + 4; height = 24; type = TextFieldType.DYNAMIC; selectable = false; ttl.text = "Tiny Basic"; fmt = new TextFormat(); with(fmt) { align = TextFormatAlign.CENTER; size = 12; color = 0xCC0000; bold = true; } setTextFormat(fmt); } addChild(ttl); // 読み込みボタン load = create_button("読込", load_click); load.x = 40; load.y = 432; addChild(load); // 保存ボタン save = create_button("保存", save_click); save.x = 112; save.y = 432; addChild(save); // Ctrl-C ボタン ctlc = create_button("Ctrl-C", ctlc_click); ctlc.x = 200; ctlc.y = 432; addChild(ctlc); // メッセージ表示 msg = new Sprite(); msg.visible = false; // メッセージ文字列 msg_text = new TextField(); with(msg_text) { type = TextFieldType.DYNAMIC; selectable = false; autoSize = TextFieldAutoSize.LEFT; fmt = new TextFormat(); fmt.size = 12; fmt.color = 0x000000; defaultTextFormat = fmt; x = 16; y = 12; } msg.addChild(msg_text); // 確認ボタン msg_btn = create_button("確認", msg_btn_click); msg_btn.y = 44; msg.addChild(msg_btn); stage.addChild(msg); token1 = token1.concat(token2); func1 = func1.concat(func2); stage.addEventListener(Event.ACTIVATE, activate); stage.addEventListener(Event.DEACTIVATE, deactivate); // キー入力処理のための仕掛 // KeyboardEvent の charCode では,特殊記号キーが英語キーボード配列のコードで // 表されるため,実際のキーとコードが一致しない. // そこで,ダミーの TextField を用意して,そこに入力された文字を TextEvent で // 拾うようにする. // TextField からフォーカスが外れないようにするため,FocusEvent も併用する. stage.addEventListener(KeyboardEvent.KEY_DOWN, key_down); stage.addEventListener(FocusEvent.KEY_FOCUS_CHANGE, focus_change); stage.addEventListener(FocusEvent.MOUSE_FOCUS_CHANGE, focus_change); key = new TextField(); with(key) { x = 0; y = 0; type = TextFieldType.INPUT; visible = false; addEventListener(TextEvent.TEXT_INPUT, key_text_input); addEventListener(FocusEvent.FOCUS_OUT, key_focus_out); } addChild(key); stage.focus = key; cont_exec = new Timer(1, 1); cont_exec.addEventListener(TimerEvent.TIMER, cont_exec_timer); cont_list = new Timer(5, 1); cont_list.addEventListener(TimerEvent.TIMER, cont_list_timer); cont_input = new Timer(0, 1); cont_input.addEventListener(TimerEvent.TIMER, cont_input_timer); cur_x = cur_y = 0; // 画面 現在位置 restart(false); } // メッセージを表示する private function show_msg(text:String, post:Function = null):void { msg_text.text = text; msg_post = post; var w:int = msg_text.width + 32; with(msg.graphics) { clear(); lineStyle(3, 0xCC0000); beginFill(0xFFFFFF); drawRoundRect(0, 0, w, 80, 12); endFill(); } msg_btn.x = (w - msg_btn.width) >> 1; msg.x = (STAGE_WIDTH - w) >> 1; msg.y = (STAGE_HEIGHT >> 1) - 40; mouseChildren = false; msg.visible = true; } // メッセージ確認ボタン押下 private function msg_btn_click(e:MouseEvent):void { msg.visible = false; mouseChildren = true; if(msg_post != null) msg_post(); } // ボタンを作成する private function create_button(lbl:String, btn_click:Function):SimpleButton { var btn:SimpleButton = new SimpleButton(); with(btn) { upState = overState = hitTestState = draw_button(lbl, false); downState = draw_button(lbl, true); tabEnabled = false; addEventListener(MouseEvent.CLICK, btn_click); } return btn; } // ボタンのイメージを作成する private function draw_button(lbl:String, down:Boolean):Sprite { var s:Sprite = new Sprite(); var txt:TextField = new TextField(); with(txt) { x = 9; y = 2; type = TextFieldType.DYNAMIC; txt.text = lbl; selectable = false; autoSize = TextFieldAutoSize.LEFT; var fmt:TextFormat = new TextFormat(); fmt.size = 12; fmt.color = (down) ? 0xFFFFFF : 0x000000; setTextFormat(fmt); var w:int = width + 18; var h:int = height + 4; } s.addChild(txt); with(s.graphics) { lineStyle(2, 0xCC0000); beginFill((down) ? 0xCC0000 : 0xFFFFFF); drawRoundRect(0, 0, w, h, 10); endFill(); } return s; } // カーソル オン private function activate(e:Event):void { cur.visible = true; } // カーソル オフ private function deactivate(e:Event):void { cur.visible = false; } // キー入力 private function key_down(e:KeyboardEvent):void { if(file_ref != null) // 読み込み/保存 処理中 return; if(msg.visible) { // メッセージ表示中 if(e.charCode == 0x1B) // Esc msg_btn_click(null) return; } if(e.ctrlKey) { // Ctrl if(e.keyCode == 67) { // Ctrl-C if(i_current < 0 && !list || input) { // LIST 以外のダイレクト モードと INPUT cont_exec.stop(); cont_input.stop(); restart(true); } else { brk = true; } } return; } if(i_current < 0 && !list || input) { // LIST 以外のダイレクト モードと INPUT switch(e.charCode) { case 0x08: // Back Space if(buffer.length) { putstr("\b"); buffer = buffer.substr(0, buffer.length - 1); } return; case 0x0D: // Enter putstr("\r"); if(input) // INPUT com_input3(); else edit_direct(); return; } } } // キー入力 private function key_text_input(e:TextEvent):void { if(file_ref == null && !msg.visible) { // 読み込み/保存 処理中,メッセージ表示中でない if(i_current < 0 && !list || input) { // LIST 以外のダイレクト モードと INPUT if(e.text.length == 1) { if(e.text.charCodeAt() >= 0x20 && e.text.charCodeAt() < 0x7F) { if(buffer.length < 132) { putstr(e.text); buffer += e.text; } } } } } e.preventDefault(); } // ダミーの TextField からフォーカスが外れないようにする private function focus_change(e:FocusEvent):void { if(e.target == key) e.preventDefault(); } // ダミーの TextField からフォーカスが外れないようにする private function key_focus_out(e:FocusEvent):void { stage.focus = key; } // 読み込み private function load_click(e:MouseEvent):void { file_ref = new FileReference(); with(file_ref) { addEventListener(Event.SELECT, load_select); addEventListener(Event.CANCEL, load_cancel); addEventListener(Event.COMPLETE, load_complete); addEventListener(IOErrorEvent.IO_ERROR, load_io_error); } mouseChildren = false; file_ref.browse(); } private function load_select(e:Event):void { file_ref.load(); } private function load_cancel(e:Event):void { with(file_ref) { removeEventListener(Event.SELECT, load_select); removeEventListener(Event.CANCEL, load_cancel); removeEventListener(Event.COMPLETE, load_complete); removeEventListener(IOErrorEvent.IO_ERROR, load_io_error); } file_ref = null; mouseChildren = true; } private function load_complete(e:Event):void { line.length = 0; // クリア var str:String = file_ref.data.readMultiByte(file_ref.data.length, "us-ascii"); file_name = file_ref.name; load_cancel(null); // 改行コード CR+LF,CR を LF に変換 // Flex 3 では,正規表現を使ったときの“\r”の扱いがおかしいようなので, // ここでは正規表現は使わない. str = str.split("\r\n").join("\n"); str = str.split("\r").join("\n"); var source:Array = str.split("\n"); var last_num:int = 0; for(var i:int = 0; i < source.length; i++) { var w:Array = source[i].match(/^ *(\d*) *(.*)/); if(!w[1].length) { if(w[2].length) { show_msg("行番号がありません。", load_err); return; } continue; } var num:int = parseInt(w[1]) if(num > 32767) { show_msg("行番号が範囲外です。", load_err); return; } if(num <= last_num) { show_msg("行番号の順序が正しくありません。", load_err); return; } if(w[2].length) { line.push({num:num, text:w[2]}); last_num = num; } } restart(true); } private function load_io_error(e:IOErrorEvent):void { load_cancel(null); show_msg("入出力エラーが発生しました。", load_err); } private function load_err():void { restart(true); } // 保存 private function save_click(e:MouseEvent):void { var os:String = Capabilities.os; var sep:String; if (os.indexOf("MacOS") >= 0) sep = "\r"; else if(os.indexOf("Windows") >= 0) sep = "\r\n"; else sep = "\n"; var source:String = ""; for(var i:int = 0; i < line.length; i++) { var str:String = line[i].num.toString(); if(str.length < 4) str = (" " + str).substr(-4); source += str + " " + line[i].text + sep; } file_ref = new FileReference(); with(file_ref) { addEventListener(Event.CANCEL, save_cancel); addEventListener(Event.COMPLETE, save_complete); addEventListener(IOErrorEvent.IO_ERROR, save_io_error); } mouseChildren = false; file_ref.save(source, file_name); } private function save_cancel(e:Event):void { with(file_ref) { removeEventListener(Event.CANCEL, save_cancel); removeEventListener(Event.COMPLETE, save_complete); removeEventListener(IOErrorEvent.IO_ERROR, save_io_error); } file_ref = null; mouseChildren = true; } private function save_complete(e:Event):void { file_name = file_ref.name; save_cancel(null); } private function save_io_error(e:IOErrorEvent):void { save_cancel(null); show_msg("入出力エラーが発生しました。"); } // Ctrl-C private function ctlc_click(e:MouseEvent):void { if(i_current < 0 && !list || input) { // LIST 以外のダイレクト モードと INPUT cont_exec.stop(); cont_input.stop(); restart(true); } else { brk = true; } } // 文字列を画面に出力する private function putstr(str:String):void { for(var i:int = 0; i < str.length; i++) putc(str.charAt(i)); } private function putc(c:String):void { switch(c) { case "\b": // Back Space if(cur_x) { scr.replaceText(scr.length - 1, scr.length, ""); cur_x--; } else { if(cur_y) { cur_y--; var len:int = scr.getLineLength(cur_y); if(len == 81) { scr.replaceText(scr.length - 2, scr.length, ""); // 改行と最後の文字を削除 } else { scr.replaceText(scr.length - 1, scr.length, ""); // 改行を削除 for(; len < 80; len++) scr.appendText(" "); } cur_x = 79; } } break; case "\t": // Tab for(; ; ) { putc(" "); if(!(cur_x % 8)) return; } case "\f": // Form Feed scr.text = ""; cur_x = cur_y = 0; break; default: if(c == "\r") cur_x = 79; else scr.appendText(c); if(cur_x == 79) { scr.appendText("\n"); cur_x = 0; if(cur_y == 24) // スクロ-ル scr.replaceText(0, scr.getLineLength(0), ""); else cur_y++; } else { cur_x++; } } // カーソル移動 cur.x = scr.x + 2 + cur_x * cur.width; cur.y = scr.y + 2 + cur_y * cur.height; } // 再起動 private function restart(init:Boolean):void { buffer = ""; if(init) putstr("\r"); putstr("OK\r>"); i_current = -1; list = input = false; for_info = {i_var:-1}; for_stack.length = gosub_stack.length = 0; brk = false; } // プログラム編集/ダイレクト コマンド private function edit_direct():void { text = buffer; var num:int; if((num = get_num()) == INT_ERR) { restart(true); return; } if(num > 0) { // プログラム編集 ignore_blanks(); var f:Boolean = false; for(var i:int = 0; i < line.length; i++) { if(line[i].num >= num) { if(line[i].num == num) f = true; break; } } if(f) { if(text.length) // 置換 line[i].text = text; else // 削除 line.splice(i, 1); } else { if(text.length) // 挿入 line.splice(i, 0, {num:num, text:text}); } buffer = ""; putstr(">"); } else { exec(false); } } // コマンド実行 private function exec(cont:Boolean):void { var sts:int; if(cont) { sts = 1; } else { var i:int; if((i = get_token((i_current < 0) ? token1 : token2)) < 0) { // マッチしない if(text.length) // LET sts = com_let(); else sts = 2; } else { // なぜ,これがエラーになる? //sts = ((i_current < 0) ? func1 : func2)[i](); var func:Array = (i_current < 0) ? func1 : func2; sts = func[i](); } } switch(sts) { case -1: // エラーまたは NEW restart(true); return; case 0: // コマンド プロンプトに戻る restart(false); return; case 1: // 次のコマンド if(test_char(";")) // 同一行 break; if(text.length) { // EOL でない error_what(); restart(true); return; } // fall thru case 2: // 次の行 if(i_current < 0) { // ダイレクト モード restart(false); return; } if(++i_current == line.length) { // プログラムの最後 restart(false); return; } // fall thru case 3: // 新しい行 text = line[i_current].text; break; case 4: // continue break; case 5: // 処理中(LIST, INPUT) return; } if(brk) { restart(true); return; } cont_exec.reset(); cont_exec.start(); } private function cont_exec_timer(e:TimerEvent):void { e.updateAfterEvent(); exec(false); } //-------- コマンド -------------------- // LIST private function com_list():int { var num:int; if((num = get_num()) == INT_ERR) return -1; if(test_char(",")) { if((list_lines = get_num()) == INT_ERR) return -1; if(list_lines < 0) // 数値でない list_lines = 0; } else { list_lines = 0x7FFFFFFF; } if(check_eol()) return -1; list_i_line = 0; if(num > 0) { for(; list_i_line < line.length; list_i_line++) { if(line[list_i_line].num >= num) break; } } list = true; com_list2(); return 5; } private function com_list2():void { if(!list_lines || list_i_line == line.length || brk) { restart(false); return; } var str:String = line[list_i_line].num.toString(); if(str.length < 4) str = (" " + str).substr(-4); putstr(str + " " + line[list_i_line].text + "\r"); list_i_line++; list_lines--; cont_list.reset(); cont_list.start(); } private function cont_list_timer(e:TimerEvent):void { e.updateAfterEvent(); com_list2(); } // NEW private function com_new():int { if(check_eol()) return -1; line.length = 0; // クリア file_name = null; return -1; } // RUN private function com_run():int { if(check_eol()) return -1; if(line.length) { i_current = 0; return 3; } return 0; } // NEXT private function com_next():int { var i_var:int; if((i_var = test_var()) == INT_ERR) return -1; if(i_var < 0) { // 変数でない error_what(); return -1; } for(; ; ) { if(for_info.i_var < 0) { error_what(); return -1; } if(for_info.i_var == i_var) break; for_info = for_stack.pop(); } var val:int = tb_vars[for_info.i_var] + for_info.inc; if(val >= -32768 && val <= 32767) { tb_vars[for_info.i_var] = val; if((for_info.inc < 0) ? (val >= for_info.lmt) : (val <= for_info.lmt)) { // 上下限以内 i_current = for_info.i_line; text = for_info.text; return 1; } } for_info = for_stack.pop(); return 1; } // LET private function com_let():int { for(; ; ) { if(assign_val() == INT_ERR) return -1; if(!test_char(",")) return 1; } return 0; // ダミー } // IF private function com_if():int { var val:int; if((val = expr()) == INT_ERR) return -1; if(val) return 4; return 2; } // GOTO private function com_goto():int { // 行番号 var num:int; if((num = expr()) == INT_ERR) return -1; if(check_eol()) return -1; var i:int; if((i = find_line(num)) < 0) return -1; i_current = i; return 3; } // GOSUB private function com_gosub():int { // 行番号 var num:int; if((num = expr()) == INT_ERR) return -1; var i:int; if((i = find_line(num)) < 0) return -1; gosub_stack.push({for_info:for_info, i_line:i_current, text:text}); // 現在の状態を退避 for_info = {i_var:-1}; i_current = i; return 3; } // RETURN private function com_return():int { if(check_eol()) return -1; if(!gosub_stack.length) { error_what(); return -1; } var info:Object = gosub_stack.pop(); for_info = info.for_info; i_current = info.i_line; text = info.text; return 1; } // REM private function com_rem():int { return 2; } // FOR private function com_for():int { for_stack.push(for_info); // 前の FOR 情報を退避 for_info = {}; // 初期値 if((for_info.i_var = assign_val()) == INT_ERR) return -1; // 上下限値 if(get_token(["TO"]) < 0) { // マッチしない error_what(); return -1; } if((for_info.lmt = expr()) == INT_ERR) return -1; // 増分 if(get_token(["STEP"]) < 0) { // マッチしない for_info.inc = 1; } else { if((for_info.inc = expr()) == INT_ERR) return -1; } // 現在の位置 for_info.i_line = i_current; for_info.text = text; // 同一の変数使用中? for(var i:int = for_stack.length - 1; i >= 0; i--) { if(for_stack[i].i_var == for_info.i_var) { // 使用中 // 古い情報を破棄 for_stack.splice(i, 1); break; } } return 1; } // INPUT private function com_input():int { if(com_input2()) return -1; return 5; } private function com_input2():int { input_text = text; if(quoted_str()) { if(!text.length) { // EOL if(i_current < 0) restart(false); else exec(true); return 0; } if((input_i_var = test_var()) == INT_ERR) return -1; } else { if((input_i_var = test_var()) == INT_ERR) return -1; if(input_i_var < 0) { // 変数でない error_what(); return -1; } putstr(input_text.substr(0, input_text.length - text.length)); } if(input_i_var >= 0) { // 変数 buffer = ""; putstr(":"); input = true; return 0; } cont_input.reset(); cont_input.start(); return 0; } private function cont_input_timer(e:TimerEvent):void { e.updateAfterEvent(); com_input3(); } private function com_input3():void { if(input_i_var >= 0) { var save_text:String = text; text = buffer; var f:Boolean; var val:int; if(!(f = ((val = expr()) == INT_ERR))) { if(check_eol()) f = true; } input = false; if(f) { // エラー // 再入力 text = input_text; com_input2(); return; } text = save_text; tb_vars[input_i_var] = val; } if(test_char(",")) { // さらに項目あり if(com_input2() < 0) restart(true); return; } exec(true); } // PRINT private function com_print():int { ignore_blanks(); if(!text.length) { // EOL putstr("\r"); return 1; } if(text.charAt() == ";") { putstr("\r"); return 1; } var width:int = 8; for(; ; ) { if(test_char("#")) { // フォーマット if((width = expr()) == INT_ERR) return -1; if(width >= 64) { error_how(); return -1; } } else { if(!quoted_str()) { var val:int; if((val = expr()) == INT_ERR) return -1; var str:String = val.toString(); while(str.length < width) str = " " + str; putstr(str); } } if(test_char(",")) { while(test_char(",")) putstr(" "); if(!text.length) // EOL return 1; if(text.charAt() == ";") return 1; } else { putstr("\r"); return 1; } } return 0; // ダミー } // STOP private function com_stop():int { if(check_eol()) return -1; return 0; } //-------- 関数 ------------------------ // RND private function fnc_rnd():int { var n:int; if((n = paren()) == INT_ERR) return INT_ERR; if(n < 0) { error_how(); return INT_ERR; } return Math.floor(Math.random() * n) + 1; } // ABS private function fnc_abs():int { var n:int; if((n = paren()) == INT_ERR) return INT_ERR; if(n == -32768) { error_how(); return INT_ERR; } return Math.abs(n); } // SIZE private function fnc_size():int { return 32766; } //-------------------------------------- // トークンを取得する // 戻り値 - トークン テーブルのインデックス(≧0),-1: マッチしない private function get_token(token_tbl:Array):int { ignore_blanks(); for(var i_token:int = 0; i_token < token_tbl.length; i_token++) { var token:String = token_tbl[i_token]; var f:Boolean = true; for(var i:int = 0; i < token.length; i++) { if(text.charAt(i) == ".") { i++; break; } if(text.charAt(i) != token.charAt(i)) { f = false; break; } } if(f) { // マッチした text = text.substr(i); return i_token; } } // マッチしない return -1; } // 変数に値を代入する // 戻り値 - tb_vars のインデックス(≧0),INT_ERR: エラー private function assign_val():int { var i_var:int; if((i_var = test_var()) == INT_ERR) return INT_ERR; if(i_var < 0) { // 変数でない error_what(); return INT_ERR; } if(!test_char("=")) { error_what(); return INT_ERR; } var val:int; if((val = expr()) == INT_ERR) return INT_ERR; tb_vars[i_var] = val; return i_var; } // 変数かどうかテストする // 戻り値 - tb_vars のインデックス(≧0),-1: 変数でない,INT_ERR: エラー private function test_var():int { ignore_blanks(); if(!text.length) // EOL return -1; var c:int = text.charCodeAt(); if(c >= 64 && c <= 90) { // @,A ~ Z text = text.substr(1); if(c == 64) { // 配列 var i:int; if((i = paren()) == INT_ERR) return INT_ERR; if(i < 0 || i > 16383) { error_how(); return INT_ERR; } return i + 26; } return c - 65; } return -1; } // 引用符で囲まれた文字列/制御コードを印字する // 戻り値 - true: 引用符で囲まれた文字列/制御コードが見つかった,false: 見つからなかった private function quoted_str():Boolean { var quot:String; switch(test_char("\"", "'", "^")) { case 1: quot = "\""; break; case 2: quot = "'"; break; case 3: // ^ if(text.length) { putstr(String.fromCharCode(text.charCodeAt() ^ 0x40)); text = text.substr(1); } // オリジナルの PATB は ^CR でも印字するが,使い道はないと思われる return true; default: return false; } var str:String = text.split(quot, 1)[0]; putstr(str); text = text.substr(str.length + 1); return true; } // 式 // 戻り値 - 式の結果(0 または 1),INT_ERR: エラー private function expr():int { var val1:int, val2:int; if((val1 = expr1()) == INT_ERR) return INT_ERR; var op:int; if((op = get_token([">=", "#", ">", "=", "<=", "<"])) < 0) // 関係演算子 // 関係演算子でない return val1; if((val2 = expr1()) == INT_ERR) return INT_ERR; switch(op) { case 0: // >= return (val1 >= val2) ? 1 : 0; case 1: // # return (val1 != val2) ? 1 : 0; case 2: // > return (val1 > val2) ? 1 : 0; case 3: // = return (val1 == val2) ? 1 : 0; case 4: // <= return (val1 <= val2) ? 1 : 0; case 5: // < return (val1 < val2) ? 1 : 0; } return 0; // ダミー } // 戻り値 - 式の結果,INT_ERR: エラー private function expr1():int { var val1:int, val2:int; switch(test_char("-", "+")) { case 1: // 減算 if((val1 = expr2()) == INT_ERR) return INT_ERR; val1 = - val1; break; case 2: // 加算 // fall thru default: if((val1 = expr2()) == INT_ERR) return INT_ERR; } for(; ; ) { switch(test_char("+", "-")) { case 1: // 加算 if((val2 = expr2()) == INT_ERR) return INT_ERR; val1 += val2; break; case 2: // 減算 if((val2 = expr2()) == INT_ERR) return INT_ERR; val1 -= val2; break; default: return val1; } if(val1 > 32767 || val1 < -32768) { error_how(); return INT_ERR; } } return 0; // ダミー } // 戻り値 - 式の結果,INT_ERR: エラー private function expr2():int { var val1:int, val2:int; if((val1 = expr3()) == INT_ERR) return INT_ERR; if(val1 == -32768) { error_how(); return INT_ERR; } for(; ; ) { var op:int; if(!(op = test_char("*", "/"))) return val1; if((val2 = expr3()) == INT_ERR) return INT_ERR; if(val2 == -32768) { error_how(); return INT_ERR; } if(op == 1) { // 乗算 val1 *= val2; if(val1 > 32767 || val1 < -32767) { error_how(); return INT_ERR; } } else { // 除算 if(!val2) { // divide by zero error_how(); return INT_ERR; } val1 /= val2; if(val1 < 0) val1 = Math.ceil(val1); else val1 = Math.floor(val1); } } return 0; } // 戻り値 - 式の結果,INT_ERR: エラー private function expr3():int { var i:int; if((i = get_token(token3)) >= 0) // 関数 return func3[i](); // 関数でない var i_var:int; if((i_var = test_var()) == INT_ERR) return INT_ERR; if(i_var >= 0) { // 変数 if(tb_vars[i_var] == undefined) return 0; return tb_vars[i_var]; } var val:int; if((val = get_num()) == INT_ERR) return INT_ERR; if(val >= 0) // 数値 return val; if((val = paren()) == INT_ERR) return INT_ERR; return val; // (式) } // 関数パラメータ/配列インデックス/(式) // 戻り値 - 式の結果,INT_ERR: エラー private function paren():int { if(test_char("(")) { var val:int; if((val = expr()) == INT_ERR) return INT_ERR; if(test_char(")")) return val; } error_what(); return INT_ERR; } // 数値取得 // 戻り値 - 数値(≧0),-1: 数値でない,INT_ERR: エラー private function get_num():int { ignore_blanks(); var w:Array = text.match(/^(\d*)(.*)/); if(w[1].length) { // 数値 text = w[2]; var val:Number = parseInt(w[1]); if(val > 32767) { error_how(); return INT_ERR; } return int(val); } return -1; } // 文字をテストする // 引数 - 任意個数の文字,たとえば test_char("A", "B", "C") // 戻り値 - 0: マッチしない,1: 1 番目の文字にマッチ,2: 2 番目の文字にマッチ,... private function test_char(... args):int { ignore_blanks(); for(var i:int = 0; i < args.length; i++) { if(text.charAt() == args[i]) { text = text.substr(1); return i + 1; } } return 0; } // 行の終わりをチェックする // 戻り値 - 0: OK(EOL),-1: EOL でない private function check_eol():int { ignore_blanks(); if(text.length) { // EOL でない error_what(); return -1; } return 0; } // 先頭の空白を無視する private function ignore_blanks():void { text = text.replace(/^ */, ""); } // 目的の行を探す // 戻り値 - 行インデックス(≧0),-1: 見つからなかった private function find_line(num:int):int { for(var i:int = 0; i < line.length; i++) { if(line[i].num >= num) { if(line[i].num == num) return i; break; } } error_how(); return -1; } // エラー WHAT? private function error_what():void { put_error("WHAT?"); } // エラー HOW? private function error_how():void { put_error("HOW?"); } // エラー メセージ private function put_error(msg:String):void { putstr("\r" + msg + "\r"); if(i_current >= 0 && !input) { // ダイレクト モード,INPUT 以外 var str:String = line[i_current].num.toString(); if(str.length < 4) str = (" " + str).substr(-4); putstr(str + " " + line[i_current].text.substr(0, line[i_current].text.length - text.length) + "?" + text + "\r"); } } } }