tbasic2.htm

戻る

<!DOCTYPE HTML>
<HTML LANG="ja">

<HEAD>
<META CHARSET="Shift_JIS">
<TITLE>今どきの Tiny BASIC</TITLE>
</HEAD>

<BODY STYLE="background-color:#CCFFFF; user-select:none">
<DIV STYLE="text-align:center">
<BR>
<B><SPAN STYLE="color:#CC0000">今どきの Tiny BASIC</SPAN></B>

<DIV ID="scr" TABINDEX="0" STYLE="width:80ch; margin-left:auto; margin-right:auto; text-align:left; color:white; background-color:black; border:solid 4px black; font-family:monospace; font-size:large; outline:none">
<SPAN NAME="scr"></SPAN><BR>
<SPAN NAME="scr"></SPAN><BR>
<SPAN NAME="scr"></SPAN><BR>
<SPAN NAME="scr"></SPAN><BR>
<SPAN NAME="scr"></SPAN><BR>
<SPAN NAME="scr"></SPAN><BR>
<SPAN NAME="scr"></SPAN><BR>
<SPAN NAME="scr"></SPAN><BR>
<SPAN NAME="scr"></SPAN><BR>
<SPAN NAME="scr"></SPAN><BR>
<SPAN NAME="scr"></SPAN><BR>
<SPAN NAME="scr"></SPAN><BR>
<SPAN NAME="scr"></SPAN><BR>
<SPAN NAME="scr"></SPAN><BR>
<SPAN NAME="scr"></SPAN><BR>
<SPAN NAME="scr"></SPAN><BR>
<SPAN NAME="scr"></SPAN><BR>
<SPAN NAME="scr"></SPAN><BR>
<SPAN NAME="scr"></SPAN><BR>
<SPAN NAME="scr"></SPAN><BR>
<SPAN NAME="scr"></SPAN><BR>
<SPAN NAME="scr"></SPAN><BR>
<SPAN NAME="scr"></SPAN><BR>
<SPAN NAME="scr"></SPAN><BR>
<SPAN NAME="scr"></SPAN><BR>
</DIV>
<BR>

<BUTTON TYPE=BUTTON ID="load" onClick="load_source()">ロード</BUTTON>
<SPAN STYLE="margin-left:4em">改行:</SPAN>
<SELECT ID="le">
<OPTION>LF
<OPTION>CR
<OPTION>CR+LF
</SELECT>
<BUTTON TYPE=BUTTON ID="save" onClick="save_source()" STYLE="margin-left:0.5em">セーブ</BUTTON>
<BR>

</DIV>

<SCRIPT TYPE="text/javascript">
<!--
// Tiny BASIC インタープリタ
// このプログラムは Li-Chen Wang さん作の Palo Alto Tiny BASIC を基にしています.
// この JavaScript 版は馬渕 義彦が作りました.

function load_source() {
  elem_file.value = "";
  elem_file.click();
  elem_scr.focus();
}

function load_source2() {
  elem_load.disabled = elem_save.disabled = true;
  prog.length = 0;  // クリア
  elem_dl.download = "untitled.bas";

  file_reader = new FileReader();
  file_reader.onloadend = load_source4;
  file_reader.onload = load_source3;
  file_reader.readAsText(elem_file.files.item(0), "US-ASCII");
}

function load_source3() {
  var source = file_reader.result.split(/\n|\r|\r\n/);
  var last_num = 0;
  for(var i = 0; i < source.length; i++) {
    var w = source[i].match(/^ *(\d*) *(.*)/);
    if(!w[1].length) {
      if(w[2].length) {
        alert("行番号がありません。");
        break;
      }
      continue;
    }
    var num = parseInt(w[1], 10)
    if(num > 32767) {
      alert("行番号が範囲外です。");
      break;
    }
    if(num <= last_num) {
      alert("行番号が順番に並んでいません。");
      break;
    }
    if(w[2].length) {
      prog.push({num:num, text:w[2]});
      last_num = num;
    }
  }

  elem_dl.download = elem_file.files.item(0).name;
  restart(true);
}

function load_source4() {
  file_reader = undefined;
  elem_load.disabled = elem_save.disabled = false;
}

function save_source() {
  var le;
  switch(elem_le.selectedIndex) {  // 改行
  case 1:  // CR
    le = "\r";
    break;
  case 2:  // CR+LF
    le = "\r\n";
    break;
  default:  // LF
    le = "\n";
  }
  var source = "";
  for(var line of prog)
    source += line.num.toString().padStart(4, " ") + " " + line.text + le;
  elem_dl.href = "data:text/plain;charset=US-ASCII;base64," + btoa(source);
  elem_dl.click();
  elem_scr.focus();
}

function scr_focus() {
  if(!scr_focused) {
    scr_focused = true;
    refresh(cur_y);
  }
}

function scr_blur() {
  if(scr_focused) {
    scr_focused = false;
    refresh(cur_y);
  }
}

function keydown(e) {
  if(!scr_focused)
    return true;

  if(e.key == "Tab")
    return true;

  if(e.ctrlKey) {  // Ctrl
    switch(e.key) {
    case "C":  // Ctrl-C
    case "c":
      if(i_current < 0 && !list || input) {  // LIST 以外のダイレクト モード または INPUT
        if(to_id != undefined) {
          clearTimeout(to_id);
          to_id = undefined;
        }
        restart(true);
      }
      else {
        brk = true;
      }
      break;
    }
    return false;
  }

  if(e.metaKey)  // Meta
    return false;

  if(i_current < 0 && !list || input) {  // LIST 以外のダイレクト モード または INPUT
    switch(e.key) {
    case "Backspace":
      if(buffer.length) {
        putstr("\b");
        buffer = buffer.slice(0, -1);
      }
      return false;

    case "Enter":
      putstr("\r");

      if(input)  // INPUT
        com_input3();
      else
        edit_direct();
      return false;
    }
    if(e.key.length > 1 || e.key < " " || e.key > "~")
      return false;
    if(buffer.length < 132) {
      putstr(e.key);
      buffer += e.key;
    }
  }

  return false;
}

function mouseup(e) {
  if(e.target === elem_load || e.target === elem_le || e.target === elem_save
       || e.target.parentElement === elem_le)
    return;
  if(!scr_focused)
    elem_scr.focus();
}

// 画面に文字列を出力する
function putstr(str) {
  for(var c of str)
    putc(c);
  // 画面更新
  for(var y = 0; y < 25; y++) {
    if(scrmod[y])
      refresh(y);
  }
}

function putc(c) {
  switch(c) {
  case "\b":  // Back Space
    if(cur_x) {
      scrbuf[cur_y] = scrbuf[cur_y].slice(0, -1);
      cur_x--;
      scrmod[cur_y] = true;
    }
    else {
      if(cur_y) {
        cur_y--;
        scrbuf[cur_y] = (scrbuf[cur_y].length == 80)
                          ? scrbuf[cur_y].substr(0, 79) : scrbuf[cur_y].padEnd(79, " ");
        cur_x = 79;
        scrmod[cur_y] = scrmod[cur_y + 1] = true;
      }
    }
    break;

  case "\t":  // Tab
    for(; ; ) {
      putc(" ");
      if(!(cur_x % 8))
        break;
    }
    break;

  case "\f":  // Form Feed
    scrbuf.fill("");
    scrmod.fill(true);
    cur_x = cur_y = 0;
    break;

  default:
    if(c == "\r")
      cur_x = 79;
    else
      scrbuf[cur_y] += c;
    if(cur_x == 79) {
      cur_x = 0;
      if(cur_y == 24) {
        // スクロール
        scrbuf.shift();
        scrbuf.push("");
        scrmod.fill(true);
      }
      else {
        cur_y++;
        scrmod[cur_y - 1] = scrmod[cur_y] = true;
      }
    }
    else {
      cur_x++;
      scrmod[cur_y] = true;
    }
  }
}

// 画面更新
function refresh(y) {
  var scr = scrbuf[y].replace(/ /g, nbsp);
  if(scr_focused) {
    if(y == cur_y)
      // カーソル
      scr += "<SPAN STYLE=\"background-color:white\">&nbsp;</SPAN>";
  }
  scrary[y].innerHTML = scr;
  scrmod[y] = false;
}

// リスタート
function restart(init) {
  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;
}

// プログラム編集/ダイレクト コマンド
function edit_direct() {
  text = buffer;
  var num;
  if(isNaN(num = get_num())) {
    restart(true);
    return;
  }
  if(num > 0) {
    // プログラム編集
    ignore_blanks();

    var i;
    var f = false;
    for(i = 0; i < prog.length; i++) {
      if(prog[i].num >= num) {
        if(prog[i].num == num)
          f = true;
        break;
      }
    }
    if(f) {
      if(text.length)
        // 置換
        prog[i].text = text;
      else
        // 削除
        prog.splice(i, 1);
    }
    else {
      if(text.length)
        // 挿入
        prog.splice(i, 0, {num:num, text:text});
    }

    buffer = "";
    putstr(">");
  }
  else {
    exec(false);
  }
}

// コマンド実行
function exec(cont) {
  to_id = undefined;

  for(var cnt = 10000; cnt; cnt--) {
    var sts;
    if(cont) {
      sts = 1;
    }
    else {
      var i;
      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]();
      }
    }
    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 == prog.length) {  // プログラムの終わり
        restart(false);
        return;
      }
      // fall thru
    case 3:  // 新しい行
      text = prog[i_current].text;
      break;

    case 4:  // 続行
      break;

    case 5:  // 実行中(LIST,INPUT)
      return;
    }

    if(brk) {
      restart(true);
      return;
    }

    cont = false;
  }

  to_id = setTimeout(exec, 0, false);
}

//-------- コマンド --------------------

// LIST
function com_list() {
  var num, lines;
  if(isNaN(num = get_num()))
    return -1;
  if(test_char(",")) {
    if(isNaN(lines = get_num()))
      return -1;
    if(lines < 0)  // 数値でない
      lines = 0;
  }
  else {
    lines = Infinity;
  }
  if(check_eol())
    return -1;

  var i = 0;
  if(num > 0) {
    for(; i < prog.length; i++) {
      if(prog[i].num >= num)
        break;
    }
  }

  list = true;
  com_list2(i, lines);
  return 5;
}

function com_list2(i, lines) {
  if(!lines || i == prog.length || brk) {
    restart(false);
    return;
  }

  putstr(prog[i].num.toString().padStart(4, " ") + " " + prog[i].text + "\r");

  setTimeout(com_list2, 0, i + 1, lines - 1);
}

// NEW
function com_new() {
  if(check_eol())
    return -1;
  prog.length = 0;  // クリア
  elem_dl.download = "untitled.bas";
  return -1;
}

// RUN
function com_run() {
  if(check_eol())
    return -1;
  if(prog.length) {
    i_current = 0;
    return 3;
  }
  return 0;
}

// NEXT
function com_next() {
  var i_var;
  if((i_var = test_var()) < 0)
    return -1;
  if(i_var == 0x10000) {  // 変数でない
    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 = 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_line:i_current, text:text} = for_info);
      return 1;
    }
  }
  for_info = for_stack.pop();
  return 1;
}

// LET
function com_let() {
  for(; ; ) {
    if(assign_val() < 0)
      return -1;
    if(!test_char(","))
      return 1;
  }
}

// IF
function com_if() {
  var val;
  if(isNaN(val = expr()))
    return -1;
  if(val)
    return 4;
  return 2;
}

// GOTO
function com_goto() {
  // 行番号
  var num;
  if(isNaN(num = expr()))
    return -1;
  if(check_eol())
    return -1;
  var i;
  if((i = find_line(num)) < 0)
    return -1;

  i_current = i;

  return 3;
}

// GOSUB
function com_gosub() {
  // 行番号
  var num;
  if(isNaN(num = expr()))
    return -1;
  var i;
  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
function com_return() {
  if(check_eol())
    return -1;
  if(!gosub_stack.length) {
    error_what();
    return -1;
  }
  ({for_info:for_info, i_line:i_current, text:text} = gosub_stack.pop());
  return 1;
}

// REM
function com_rem() {
  return 2;
}

// FOR
function com_for() {
  for_stack.push(for_info);  // 前の FOR 情報保存
  for_info = {};
  // 初期値
  if((for_info.i_var = assign_val()) < 0)
    return -1;
  // 終値
  if(get_token(["TO"]) < 0) {
    // アンマッチ
    error_what();
    return -1;
  }
  if(isNaN(for_info.lmt = expr()))
    return -1;
  // 増分
  if(get_token(["STEP"]) < 0) {
    // アンマッチ
    for_info.inc = 1;
  }
  else {
    if(isNaN(for_info.inc = expr()))
      return -1;
  }
  // 現在位置
  for_info.i_line = i_current;
  for_info.text = text;

  // 同じ変数が使われている?
  for(var i = 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
function com_input() {
  if(com_input2())
    return -1;
  return 5;
}

function com_input2() {
  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()) < 0)
      return -1;
  }
  else {
    if((input_i_var = test_var()) < 0)
      return -1;
    if(input_i_var == 0x10000) {  // 変数でない
      error_what();
      return -1;
    }
    putstr(input_text.substr(0, input_text.length - text.length));
  }
  if(input_i_var != 0x10000) {  // 変数
    buffer = "";
    putstr(":");
    input = true;

    return 0;
  }
  to_id = setTimeout(com_input3, 0);
  return 0;
}

function com_input3() {
  to_id = undefined;

  if(input_i_var != 0x10000) {
    var save_text = text;
    text = buffer;
    var f;
    var val;
    if(!(f = isNaN(val = expr()))) {
      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
function com_print() {
  ignore_blanks();
  if(!text.length) {  // EOL
    putstr("\r");
    return 1;
  }
  if(text.charAt(0) == ";") {
    putstr("\r");
    return 1;
  }

  var width = 8;
  for(; ; ) {
    if(test_char("#")) {  // フォーマット
      if(isNaN(width = expr()))
        return -1;
      if(width >= 64) {
        error_how();
        return -1;
      }
    }
    else {
      if(!quoted_str()) {
        var val;
        if(isNaN(val = expr()))
          return -1;
        putstr((val + 0).toString().padStart(width, " "));  // "+ 0": "-0" を回避する
      }
    }

    if(test_char(",")) {
      while(test_char(","))
        putstr(" ");
      if(!text.length)  // EOL
        return 1;
      if(text.charAt(0) == ";")
        return 1;
    }
    else {
      putstr("\r");
      return 1;
    }
  }
}

// STOP
function com_stop() {
  if(check_eol())
    return -1;
  return 0;
}

//-------- 関数 ------------------------

// RND
function fnc_rnd() {
  var n;
  if(isNaN(n = paren()))
    return NaN;
  if(n < 0) {
    error_how();
    return NaN;
  }
  return Math.floor(Math.random() * n) + 1;
}

// ABS
function fnc_abs() {
  var n;
  if(isNaN(n = paren()))
    return NaN;
  if(n == -32768) {
    error_how();
    return NaN;
  }
  return Math.abs(n);
}

// SIZE
function fnc_size() {
  return 32766;
}

//--------------------------------------

// トークンを取得する
// 戻り値 - トークン テーブルのインデックス(>=0),-1: アンマッチ
function get_token(token_tbl) {
  ignore_blanks();
  for(var i_token = 0; i_token < token_tbl.length; i_token++) {
    var token = token_tbl[i_token];
    var f = true;
    for(var i = 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),-1: エラー
function assign_val() {
  var i_var;
  if((i_var = test_var()) < 0)
    return -1;
  if(i_var == 0x10000) {  // 変数でない
    error_what();
    return -1;
  }
  if(!test_char("=")) {
    error_what();
    return -1;
  }
  var val;
  if(isNaN(val = expr()))
    return -1;
  tb_vars[i_var] = val;
  return i_var;
}

// 変数であるか調べる
// 戻り値 - tb_vars のインデックス(>=0),0x10000: 変数でない,-1: エラー
function test_var() {
  ignore_blanks();
  if(!text.length)  // EOL
    return 0x10000;
  var c = text.charCodeAt();
  if(c >= 64 && c <= 90) {  // @,A 〜 Z
    text = text.substr(1);
    if(c == 64) {  // 配列
      var i;
      if(isNaN(i = paren()))
        return -1;
      if(i < 0 || i > 16383) {
        error_how();
        return -1;
      }
      return i + 26;
    }
    return c - 65;
  }
  return 0x10000;
}

// 引用符で括られた文字列/制御コードを印字する
// 戻り値 - true: 引用符で括られた文字列/制御コードが見つかった,false: 見つからない
function quoted_str() {
  var quot;
  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 = text.split(quot, 1)[0];
  putstr(str);
  text = text.substr(str.length + 1);
  return true;
}

// 式
// 戻り値 - 式の結果,NaN: エラー
function expr() {
  var val1, val2;
  if(isNaN(val1 = expr1()))
    return NaN;
  var op;
  if((op = get_token([">=", "#", ">", "=", "<=", "<"])) < 0)  // 比較演算子
    // 比較演算子以外
    return val1;
  if(isNaN(val2 = expr1()))
    return NaN;
  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;
  }
}

// 戻り値 - 式の結果,NaN: エラー
function expr1() {
  var val1, val2;

  if(test_char("-+") == 1) {  // 負符号
    if(isNaN(val1 = expr2()))
      return NaN;
    val1 = - val1;
  }
  else {  // 正符号 または 符号なし
    if(isNaN(val1 = expr2()))
      return NaN;
  }

  for(; ; ) {
    switch(test_char("+-")) {
    case 1:  // 加算
      if(isNaN(val2 = expr2()))
        return NaN;
      val1 += val2;
      break;

    case 2:  // 減算
      if(isNaN(val2 = expr2()))
        return NaN;
      val1 -= val2;
      break;

    default:
      return val1;
    }
    if(val1 > 32767 || val1 < -32768) {
      error_how();
      return NaN;
    }
  }
}

// 戻り値 - 式の結果,NaN: エラー
function expr2() {
  var val1, val2;

  if(isNaN(val1 = expr3()))
    return NaN;
  if(val1 == -32768) {
    error_how();
    return NaN;
  }

  for(; ; ) {
    var op;
    if(!(op = test_char("*/")))
      return val1;
    if(isNaN(val2 = expr3()))
      return NaN;
    if(val2 == -32768) {
      error_how();
      return NaN;
    }
    if(op == 1) {  // 乗算
      val1 *= val2;
      if(val1 > 32767 || val1 < -32767) {
        error_how();
        return NaN;
      }
    }
    else {  // 除算
      if(!val2) {  // ゼロによる除算
        error_how();
        return NaN;
      }
      val1 = Math.trunc(val1 / val2);
    }
  }
}

// 戻り値 - 式の結果,NaN: エラー
function expr3() {
  var i;
  if((i = get_token(token3)) >= 0)  // 関数
    return func3[i]();

  // 関数でない
  var i_var;
  if((i_var = test_var()) < 0)
    return NaN;
  if(i_var != 0x10000) {  // 変数
    if(tb_vars[i_var] == undefined)
      return 0;
    return tb_vars[i_var];
  }
  var val;
  if(isNaN(val = get_num()))
    return NaN;
  if(val >= 0)  // 数値
    return val;
  if(isNaN(val = paren()))
    return NaN;
  return val;  // (式)
}

// 関数引数/配列インデックス/(式)
// 戻り値 - 式の結果,NaN: エラー
function paren() {
  if(test_char("(")) {
    var val;
    if(isNaN(val = expr()))
      return NaN;
    if(test_char(")"))
      return val;
  }
  error_what();
  return NaN;
}

// 数値を取得する
// 戻り値 - 数値(>=0),-1: 数値でない,NaN: エラー
function get_num() {
  ignore_blanks();
  var w = text.match(/^(\d*)(.*)/);
  if(w[1].length) {  // 数値
    text = w[2];
    var val = parseInt(w[1], 10);
    if(val > 32767) {
      error_how();
      return NaN;
    }
    return val;
  }
  return -1;
}

// 文字を調べる
// 引数 - 調べたい任意個の文字を連結した文字列,たとえば test_char("ABC")
// 戻り値 - 0: マッチしない,1: 1番目の文字にマッチ,2: 2番目の文字にマッチ,…
function test_char(char) {
  ignore_blanks();
  if(!text.length)
    return 0;
  var i = char.indexOf(text.charAt(0));
  if(i < 0)
    return 0;
  text = text.substr(1);
  return i + 1;
}

// 行の終わりか調べる
// 戻り値 - 0: OK(EOL),-1: EOL でない
function check_eol() {
  ignore_blanks();
  if(text.length) {
    // EOL でない
    error_what();
    return -1;
  }
  return 0;
}

// 先頭の空白を無視する
function ignore_blanks() {
  if(text.charAt(0) == " ")
    text = text.replace(/^ */, "");
}

// 目的の行を探す
// 戻り値 - 行インデックス(>=0),-1: 見つからない
function find_line(num) {
  for(var i = 0; i < prog.length; i++) {
    if(prog[i].num >= num) {
      if(prog[i].num == num)
        return i;
      break;
    }
  }
  error_how();
  return -1;
}

// エラー WHAT?
function error_what() {
  put_error("WHAT?");
}

// エラー HOW?
function error_how() {
  put_error("HOW?");
}

// エラー メッセージ
function put_error(msg) {
  putstr("\r" + msg + "\r");
  if(i_current >= 0 && !input)  // ダイレクト モードと INPUT 以外
    putstr(prog[i_current].num.toString().padStart(4, " ") + " "
             + prog[i_current].text.substr(0, prog[i_current].text.length - text.length)
             + "?" + text + "\r");
}

//----------------------------------------------------------

  // ステートメント コマンド
  token2 = ["NEXT", "LET", "IF", "GOTO", "GOSUB", "RETURN", "REM", "FOR", "INPUT", "PRINT", "STOP"];
  func2 = [com_next, com_let, com_if, com_goto, com_gosub, com_return, com_rem, com_for, com_input, com_print, com_stop];
  // ダイレクト コマンド
  token1 = ["LIST", "NEW", "RUN", ...token2];
  func1 = [com_list, com_new, com_run, ...func2];
  // 関数
  token3 = ["RND", "ABS", "SIZE"];
  func3 = [fnc_rnd, fnc_abs, fnc_size];

  nbsp = String.fromCharCode(160);

  scrbuf = new Array();  // 画面バッファ
  scrbuf.length = 25;
  scrbuf.fill("");
  scrmod = new Array();  // 画面バッファ更新フラグ
  scrmod.length = 25;
  scrmod.fill(false);
  cur_x = cur_y = 0;  // 画面 現在位置

  scr_focused = false;

  prog = [];  // プログラム格納領域

  var i_current;  // 現在行インデックス(-1: ダイレクト モード)
  var list, input;  // LIST,INPUT 実行中フラグ
  tb_vars = new Array();  // 変数  [0] 〜 [25]: A 〜 Z,[26] 〜: @ 配列
  tb_vars.length = 26;
  var text;  // コマンド テキスト
  var for_info;  // FOR ループ情報
  for_stack = [];  // FOR ループ情報スタック
  gosub_stack = [];  // GOSUB 情報スタック
  var input_text;   // INPUT テキスト保存領域
  var input_i_var;  // INPUT 変数インデックス
  var buffer;  // 入力バッファ
  var brk;  // ブレーク フラグ
  var to_id;  // タイムアウト ID

  var scrary = Array.from(document.getElementsByName("scr"));

  elem_scr = document.getElementById("scr");
  elem_load = document.getElementById("load");
  elem_le = document.getElementById("le");
  elem_save = document.getElementById("save");

  elem_le.selectedIndex = 0;

  elem_scr.onfocus = scr_focus;
  elem_scr.onblur = scr_blur;
  onkeydown = keydown;
  onmouseup = mouseup;

  elem_file = document.createElement("INPUT");
  elem_file.type = "FILE";
  elem_file.accept = ".bas";
  elem_file.onchange = load_source2;
  var file_reader;
  elem_dl = document.createElement("A");
  elem_dl.download = "untitled.bas";

  elem_scr.focus();

  restart(false);

//-->
</SCRIPT>

</BODY>

</HTML>