esbasic0011
の編集
https://essen.osask.jp/?esbasic0011
[
トップ
] [
編集
|
差分
|
バックアップ
|
添付
|
リロード
] [
新規
|
一覧
|
単語検索
|
最終更新
|
ヘルプ
]
-- 雛形とするページ --
BracketName
EssenRev4
FormattingRules
FrontPage
Help
InterWiki
InterWikiName
InterWikiSandBox
K
MenuBar
PHP
PukiWiki
PukiWiki/1.4
PukiWiki/1.4/Manual
PukiWiki/1.4/Manual/Plugin
PukiWiki/1.4/Manual/Plugin/A-D
PukiWiki/1.4/Manual/Plugin/E-G
PukiWiki/1.4/Manual/Plugin/H-K
PukiWiki/1.4/Manual/Plugin/L-N
PukiWiki/1.4/Manual/Plugin/O-R
PukiWiki/1.4/Manual/Plugin/S-U
PukiWiki/1.4/Manual/Plugin/V-Z
RecentDeleted
SDL2_01
SandBox
WikiEngines
WikiName
WikiWikiWeb
YukiWiki
a21
a21_acl01
a21_bbs01
a21_challengers
a21_count
a21_edu01
a21_edu02
a21_edu03
a21_edu04
a21_edu05
a21_edu06
a21_edu07
a21_edu08
a21_edu09
a21_edu10
a21_edu11
a21_hlx000
a21_hlx001
a21_hlx001_1
a21_hlx001_2
a21_hlx001_3
a21_hlx002
a21_hlx002_1
a21_hlx003
a21_hlx003_1
a21_hlx004_1
a21_memo01
a21_opt
a21_opt02
a21_opt03
a21_p01
a21_special
a21_tl9a
a21_todo
a21_txt01
a21_txt01_10
a21_txt01_1a
a21_txt01_2
a21_txt01_2a
a21_txt01_2b
a21_txt01_3
a21_txt01_4
a21_txt01_5
a21_txt01_6
a21_txt01_6a
a21_txt01_7
a21_txt01_8
a21_txt01_8a
a21_txt01_9
a21_txt01_9a
a21_txt02
a21_txt02_10
a21_txt02_10a
a21_txt02_10b
a21_txt02_11
a21_txt02_11a
a21_txt02_12
a21_txt02_12a
a21_txt02_12b
a21_txt02_1a
a21_txt02_1b
a21_txt02_2
a21_txt02_2a
a21_txt02_3
a21_txt02_3a
a21_txt02_4
a21_txt02_4a
a21_txt02_5
a21_txt02_5a
a21_txt02_6
a21_txt02_6a
a21_txt02_6b
a21_txt02_6b_rev0
a21_txt02_6x
a21_txt02_7
a21_txt02_7a
a21_txt02_8
a21_txt02_8a
a21_txt02_9
a21_txt02_9a
a22_acl2_01
a22_acl2_02
a22_edu12
a22_intro01
a22_intro02
a22_intro03
a22_memman01
a22_memman02
a22_memman03
a22_memman04
a22_memman05
a22_memman06
a22_memman07
a22_memo01
a22_mingw_debug
a22_txt03
a22_txt03_1a
a22_txt03_1b
a22_txt03_2
a22_txt03_2a
a22_ufcs01
a23_bbs
a23_ec001
a23_ec002
a23_intro00
a23_intro000
a23_intro01
a23_intro02
a23_intro03
a23_intro04
a23_intro05
a23_intro06
a23_intro07
a23_intro08
a23_intro09
a23_intro10
a23_intro10wk1
a23_intro10wk2
a23_intro10wk3
a23_intro11
a23_intro12
a23_intro13
a23_intro13wk1
a23_intro14
a23_intro15
a23_intro16
a23_intro90
a23_intro91
a23_neopixel1
a23_os01
a23_useSelfMade
a23_usm001
a23_usm002
a23_usm003
a23_usm004
a23_usm005
a23_usm006
a23_usm007
a23_usm008
a23_usm009
aclib00
aclib01
aclib02
aclib03
aclib04
aclib05
aclib06
aclib07
aclib08
aclib09
aclib10
aclib11
aclib12
aclib13
aclib14
aclib15
aclib16
aclib17
aclib18
aclib19
aclib20
aclib21
aclib22
aclib23
aclib24
aclib25
aclib_bbs
arm64_01
avm0001
edu0001
edu0002
edu0003
esb02b_hrb
esb_dbg
esbasic0001
esbasic0002
esbasic0003
esbasic0004
esbasic0005
esbasic0006
esbasic0007
esbasic0008
esbasic0009
esbasic0010
esbasic0011
esbasic0012
esbasic0013
esbasic0014
esbasic0015
esbasic0016
esbasic0017
esbasic02a
esc0001
escm0001
essen_hist
esvm0001
esvm0002
esvm0003
esvm0004
esvm0005
esvm0006
esvm_i0
hh4a
idea0001
idea0002
idea0003
impressions
jck_0000
jck_0001
kawai
kbcl0_0000
kbcl0_0001
kbcl0_0002
kbcl0_0003
kbcl0_0004
kbcl0_0005
kbcl0_0006
kbcl0_0007
kclib1_0000
kclib1_0001
kclib1_0002
kclib1_0003
kclib1_0004
kclib1_0005
kclib1_0006
kclib1_0007
kclib1_0008
kclib1_0009
kclib1_0010
kpap0001
members
memo0001
osask4g
osask4g_r2
p20200311a
p20200610a
p20200610b
p20200624a
p20200711a
p20200716a
page0001
page0002
page0003
page0004
page0005
page0006
page0007
page0008
page0009
page0010
page0011
page0012
page0013
page0014
page0015
page0016
page0017
page0018
page0019
page0020
page0021
page0022
page0023
populars
seccamp
seccamp2019
sechack
sechack2019
seclang01
sh3_2020
sh3_2020_kw
sh3_2020_nk
sh3_2021_kw
sh3_2021_nk
sh3_2022_kw
sh3_2023_kw
sh3_kw_hist
termux001
termux002
text0001
text0001a
text0002
text0002a
text0003
text0004
text0005
text0006
text0006a
text0007
text0008
text0010
text0011
text0012
text0013
text0014
text0015
text0016
text0017
text0018
text0019
text0020
text0021
tl1c
tl2c
tl3c
tl3d
* ES-BASIC #11 -(by [[K]], 2019.11.19) ** (13) ES-BASIC ver.0.1b のソースコード規模 -全体(すべてC言語で書かれています) |ファイル名|行数|説明| |''kcl03'' [川合用汎用ライブラリ]|RIGHT:1360行|高速なmalloc/free, 可変長配列, ハッシュテーブル, ソートなど| |''kll00'' [川合用汎用言語ライブラリ]|RIGHT:890行|ストリングID, 簡易レキサー(テキストをトークン列に切り分ける), トークン列比較など| |''bla'' [川合用汎用グラフィックライブラリ]|RIGHT:719行|小規模なグラフィックライブラリ| |''bla2'' [川合用汎用グラフィックライブラリ]|RIGHT:105行|blaの追加ライブラリ(グラフィックキャラクタなど)| |''chr''|RIGHT:105行|bla2用のキャラクタデータ| |''esbasic''|RIGHT:1748行|''ES-BASIC本体''(今回は主にここを開発)| |(合計)|RIGHT:4927行|実行ファイルサイズは64bit版が102.0KBで, 32bit版が102.5KB| -esbasicの内訳(主要なもののみ) |関数名|行数|説明| |sub_~|RIGHT:245行|生成した機械語から呼ばれるための関数群。| |loadReg|RIGHT:257行|式の記述から機械語を生成するための関数(結果をレジスタに格納)。以下の「サポートしている演算子」に対応している。| |||演算対象がメモリかレジスタか定数なのかで機械語を作り分けて多少は高速化しているので、ここはサポートしている演算子の種類の割には長めになっている。| |OpCall_put|RIGHT:64行|sub_~を呼び出すためのコードを生成。主にパラメータの評価と受け渡し処理。| |eval0|RIGHT:60行|機械語のエントリ部分のコード生成。| |evalAdd|RIGHT:408行|指定された1行を機械語に翻訳。以下の「サポートしている命令」に対応している。結構たくさんあるのでそれなりには長い。| |eval1|RIGHT:50行|機械語のジャンプ先の確定処理や、実行処理など。| |eval2|RIGHT:26行|機械語生成のために使用したメモリ域などを解放。| |cmplTimeRun0|RIGHT:26行|@cmplTimeやCONST()の実現のための記述。| |sub_run|RIGHT:77行|上記の関数群を使ってプログラムを機械語に翻訳して実行する。デバッグのためのコードも混ぜ込む。| |cmdlineLoop|RIGHT:115行|REPL(Read-Exec-Print-Loop)のための記述。REGS命令、LOAD命令、RESUME命令は例外的にここで処理される。| |bla_main|RIGHT:74行|ちょっとblaライブラリの都合で名前が変だけど、要するにmain関数。主に初期化処理をしている。| |(小計)|RIGHT:1402行|これで全体の80.2%を説明できたことになる。| -このセクションでアピールしたいことは、ES-BASICが小規模であるということです。 -高速なスクリプト言語を作るのは、世間で思われているほどには難しくないのです。・・・っていうか難しかったら私にはできません。 ** (14) ES-BASIC ver.0.1b の主な機能 -[1]かなり高速、コンパクト --gccに近い速度が簡単に出ます。スクリプト言語(インタプリタ)としては''「規格外」''の速度です(下記のベンチマーク欄を見てください)。 --たぶんこの点が最大の特徴です。 --言語処理系は''100KB程度''であり、この規模の言語としてはコンパクトなほうだと思われます。 --もちろんスクリプト言語なので、実行ファイルを作るなどの作業を事前にする必要はなく、RUNするだけですぐに実行できます。 --なお、内部でJITコンパイラを持つことで、高速な実行と言語処理系の小ささを両立させています(JIT = Just In Time)。 -[2]基本構文 --すべての行に行番号を付けなければいけないタイプのBASIC言語です(私が好きな1980年くらいのBASICはみんなそんな感じでした)。 --一つの行に複数の文を書くことができます。その場合は、セミコロンで区切ります。 --テキストエディタなどがなくても、コンソールだけでプログラムを作ることができます。 --言語としては、静的型付き言語です(そもそもBASICはそういう言語です)。動的型付き言語だと、私の力ではスピードが出せそうにないので今回はあきらめました -[3]サポートしている演算子 |+|-|*|/|%|<<|>>|&|||^|array[i]| |加算|減算|乗算|除算|剰余|左シフト|右シフト|ビットAND|ビットOR|ビットXOR|配列| --このほかにも A*:B>>C のような演算子も持っていますが、説明がめんどくさいのでひとまず省略します。 --配列アクセスは添え字が宣言の範囲外になると、実行時エラーで止まります。 -[4]サポートしている命令 |命令|ver|説明| |A=B|01b|単純な代入文です。Bの部分は式になっていてもOKです。| |PRINT ...|01b|式の値をコンソールに表示します。| |FOR A=B,C ... NEXT|01b|定番の繰り返し命令です。終値の後ろに増分値を指定することもできます。| |FORNE A=B,C ... NEXT|01b|FOR I=0,10 ... NEXT は、I=10も回りますが、FORNEだとI=9までしか回りません。| |DOLOOP ... ENDDO 条件|01b|do~while相当のループです。条件を省略すると無限ループになります。| |IF 条件 THEN ... ELSE ... FI|01b|定番のIF文です。ELSE節は省略可能です。| |LABEL ラベル名|01b|GOTOやGOSUBの飛び先のためにラベルを宣言できます。| |IF 条件 GOTO 行番号orラベル|01b|条件ジャンプです。| |GOTO 行番号orラベル|01b|無条件ジャンプです。| |GOSUB 行番号orラベル|01b|サブルーチンを呼び出します。| |RETURN|01b|サブルーチンから復帰します。| |NORETURN|01b|サブルーチンから復帰しますが、呼び出し元には帰りません。行番号orラベルを追加指定することもできて、その場合はGOTO動作も行います。| |PUSH 式|01b|式の値をスタックに保存します。| |POP 変数名|01b|スタックから値を取り出します。今のES-BASICにはローカル変数がないので、PUSH/POPとGOSUB/RETURNを使うと、再帰処理ができるようになります。| |LIST|01b|プログラムを表示します。| |RUN|01b|プログラムを実行します。| |RENUM|01b|行番号を付け直します。BASICが好きな人には定番の命令です(笑)。| |NEW|01b|プログラムを全行消去します。ただし変数の値は消えません。| |EXIT|01b|ES-BASICを終了します。| |END|01b|プログラムを正常終了します。| |STOP|01b|プログラムを中断します。どの行で中断したのかも表示されます。再開はできません。主にエラー終了のために使います。| |PAUSE|01b|プログラムを一時停止します。どの行で中断したのかも表示されます。RESUME命令で再開可能です。停止中に変数を見たり変更したりすることが可能です。| |LOAD ファイル名|01b|ファイルからプログラムを読み込みます。プログラムではなくコマンド列でもOKです。| |ALIAS|01b|変数名や予約語や演算子に別名を与えます。| |RAND|01b|0~32767の乱数を生成します。たいていは%で適当な範囲に丸めてから使います。| |INPUT|01b|コンソールから数値を入力します。| |OPENWIN|01b|グラフィックウィンドウのサイズを指定します。| |A=RGBCOL(red,green,blue)|01b|RGB値からカラーコードを取得します。r/g/bはそれぞれ0~255です。| |GCLS|01b|グラフィックウィンドウ全体を一色で塗りつぶします。| |SETPIX|01b|グラフィックウィンドウに点を描画します。| |FILLRECT|01b|グラフィックウィンドウに塗りつぶした長方形を描画します。| |DRAWRECT|01b|グラフィックウィンドウに塗りつぶさない長方形を描画します。| |FILLOVAL|01b|グラフィックウィンドウに塗りつぶした楕円形を描画します。| |DRAWOVAL|01b|グラフィックウィンドウに塗りつぶさない楕円形を描画します。| |DRAWLINE|01b|グラフィックウィンドウに直線を描画します。| |FILL|01b|グラフィックウィンドウの任意の領域を塗りつぶします。| |GPRINTI|01b|グラフィックウィンドウ内に数値を描画します。| |GPRINTS|01d|グラフィックウィンドウ内に文字列を描画します。| |EWAIT|01b|指定した時間だけ待機します(sleep)。単位はミリ秒です。| |EINKEY|01b|グラフィックウィンドウへのキー入力を得ます。何も入力がないときは0を返します。| |FLUSHWIN|01b|描画結果を確実に画面に反映させます。たいていはこの命令を使いませんが、負荷が高いときでも画面に反映させたいときは、時々使います。| |ECHR|01b|グラフィックウィンドウ内にキャラクタを描画します。| |GETCHR|01b|グラフィックウィンドウ内の指定された座標に表示されているキャラクタの番号を取得します。| |GETCOL|01b|グラフィックウィンドウ内の指定された座標に表示されているキャラクタの色を取得します。| |CHRBOX|01b|ECHRを繰り返してキャラクタで長方形を描画します。| |ARY INT NEW A[B]|01b|B個の要素を持つ配列Aを準備します。Bは定数式でなくても構いません。内部的には配列のリサイズもできるようになっているのですが、まだリサイズ命令を作っていません。| |ARY INT DEL A|01b|配列Aを破棄します。| |FF16SIN|01b|三角関数の値を計算します。角度はラジアンではなく65536が1周になるような単位で渡します。また結果は小数ではなく65536倍した結果(=固定小数点)で渡されます。| |FF16COS|01b|FF16SINのコサイン版です。| |REGS|01b|現在のレジスタの値を簡単に確認できます。| |CODE|01b|これは必殺技みたいな命令で、16進数列を書くとそれを機械語として実行します。インライン機械語みたいなものです。| |||試してみたい機械語があったとき、これで試して、REGSで結果を見たりして遊びます。それ以外ではめったに使いません。機械語を間違えればすぐにES-BASICが落ちます(笑)。| |LWAIT|01b|実行速度を指定します。-1が一番高速でいくつかのデバッグ機能は無効になります。0はデバッグ機能がすべて有効になっている中では最も高速、1以上は適当にウェイトがかかるようになります。| |TIMEMODE|01b|1を指定すると、コマンドやプログラムの実行に要した時間が表示されるようになります。高速化のために試行錯誤したいとき、これはかなり重宝します。| |CODEDUMP|01b|1を指定すると、コマンドやプログラムがどんな機械語になったのかを確認することができます。| |LINECOUNTER|01b|プログラムを実行した後にこの命令を実行すると、どの行が何度実行されたのかを簡単に確認することができます。| |||これで「ここは通ったかな?」などとPRINTデバッグする必要はなくなるし、どこが負荷が重いのかを簡単に確認することもできます。| |NODEBUG|01b|これはプログラム内で使う命令で、1を指定すると以降の行はLWAITが0以上でもデバッグ対象にはならなくなります。0を指定すると、以降の行はまたLWAITに応じてデバッグ対象になるようになります。| |||これで怪しくない部分を高速に実行できるようになります。| |DEBUGTRAP 行番号orラベル|01b|この命令で指定されたサブルーチンは、デバッグトラップルーチンだと認識されるようになります。0を指定すると設定が解除されます。| |||デバッグトラップルーチンは、各行を実行する前に必ず呼び出されるサブルーチンで、変数$LINEにこれから実行しようとする行番号が入ります。| |||これにより、変数の値を監視したり、ある行の実行の前だけ別の処理を追加したり、NORETURNを使ってある行の実行をスキップしたり、そのほかいろいろな応用ができます。| |||デバッグトラップルーチンの中にデバッグ対象行があると、呼び出しが無限に止まらなくなってしまうので(笑)、NODEBUG命令で対象外に指定しておく必要があります。| |PROGSEL|01b|実はES-BASICは複数のプログラムを同時に管理することもできます。これはプログラムスロットを切り替える命令です。| |||PAUSE中に変数をいろいろ表示したいのなら、別スロットに変数を表示するためのプログラムを書いておいて、それをRUNすればいいのです。| |PROGRUN スロット番号|01b|PROGSELでアクティブなスロット番号を切り替えずにRUNする命令です。| |PROGNEW スロット番号|01b|PROGSELでアクティブなスロット番号を切り替えずにNEWする命令です。| |PROGLIST|01b|現在プログラムが格納されているスロット番号の一覧と、そこに入っているプログラムの行数を表示します。| |PROGDEL スロット番号|01b|指定されたプログラムスロットを削除します。| |CONST(式)|01b|式の内容をコンパイル時に計算して定数として埋め込みます。| |||したがってCONSTの中で変数を参照していて、その変数の値をあとから変更しても、ここの値はコンパイル時(=実行開始時)から不変のままになります。| |@CMPLTIME ~ @EXECTIME|01b|@CMPLTIMEのあとに書かれた行は、実行時ではなくコンパイル時に実行されます。@EXECTIMEまでがコンパイル時実行の対象です。| |||その演算結果を$RETVALに格納して、以降のプログラムでCONST($RETVAL)として参照して使うのが普通です。こうすることで、C++でのconstexprみたいなことができます。| |||ES-BASICはスクリプト言語なので、プログラムを実行して値を得ることは基本的に朝飯前です。だからこんな機能も簡単に実現できています。| |||これにより、複雑な計算結果を定数として使いたいときに、計算を言語に任せることができます。| |FF16SQRT|01d|固定小数点での平方根を計算します。| |CLRKEYBUF|01d|キーバッファをクリアします。| |KWAIT|01d|指定されたキーが押されるまで待ちます。| |SETGMODE|01d|描画モードを指定します。| |GETPIX|01d|画面上の点の色を取得します。| |BITBLT|01d|配列データを画面の矩形範囲に転送します。| ** (15) 実プログラム例 -[[esbasic0010]]を見てください。 ** (16) ベンチマーク -詳細は[[esbasic0013]]を見てください。 |言語|処理系のインストールサイズ|タイプ|実行時間|GCC x64 -O3比|備考| |GCC x64 8.1.0 -O3|RIGHT:449256KB|コンパイラ言語|RIGHT:1.115秒|RIGHT:1.00倍|これはコンパイル時間を含んでいません。ちなみにコンパイル時間は0.37秒でした(これは複数回試した最小値です)。| |ES-BASIC ver.0.1b 64bit版|RIGHT:102KB|スクリプト言語|RIGHT:1.270秒|RIGHT:1.14倍|これはJITコンパイル時間を含んでいます。GCCに対して、1.14倍くらいの時間がかかっていることになります。| |Ruby 2.6.4p104 x64|RIGHT:75323KB|スクリプト言語|RIGHT:76.102秒|RIGHT:68.2倍|[[esbasic0013]]にテストに使ったRubyのコードがあります。| |Python 3.7.4|RIGHT:85771KB|スクリプト言語|RIGHT:344.891秒|RIGHT:309倍|[[esbasic0013]]にテストに使ったPythonのコードがあります。| |Python+Numba||スクリプト言語||RIGHT:2倍?|以前類似のテストをしたことがあり、その時の感触から推定(後日測定して更新します)。| |JavaScript(Node.js)||スクリプト言語||RIGHT:15倍?|以前類似のテストをしたことがあり、その時の感触から推定(後日測定して更新します)。| -一般に、Rubyはこの手の計算だとGCCの30倍程度の時間がかかります。Pythonはさらにもっと遅いですが、NumbaというJITコンパイラを使うとGCCの2倍くらいまで一気に高速化されます。 --mrubyのJITコンパイラもあるので、これもいつかやってみたいです。 https://qiita.com/miura1729/items/a1828849ec8fec596e74 -Node.jsを使えばJavaScriptでも同様の計算をすることができて、GCCの15倍くらいの時間で実行できます。 ** (17) どうしてそんなに速いの? -まず、世間で有名な言語がいろいろ最適を頑張っているのは知っていますが、それらはES-BASICでは基本的に一切やりません。それをやったらコンパイル時間が長くなってしまいます。ES-BASICでは実行時間だけではなくJITコンパイル時間も短くしたいので、そんな方法は取れません。・・・そして世間の優秀なJITコンパイラたちは「Tracing JIT」みたいな技術を併用して、時間をかけて最適化しても元が取れそうな場所を「自動で」発見し、そこにたくさんの技法を適用して高速化しています。でもそんな難しいプログラムは私には書けないというか、私じゃない人が書いた方がうまいと思うので、私はそれはやりません。 -ES-BASICでは最適化をがんばらない代わりに、レジスタを直接使ってもよいことにしました。つまり、「i=1」と書けばメモリ上の変数iに1が代入されますが、「RAX=1」と書けばRAXレジスタに1が代入されるわけです。またレジスタ名がむき出しだと可読性に難があったので、ALIAS命令でレジスタ名を隠せるようにしました。「ALIAS i:RAX」と書けば、以降は「i=1」でRAX=1になるわけです。 -どの変数にどのレジスタを割り当てればいいのか、どの変数はメモリのままでいいのか、それらについては基本的に人間が勘で決めることになります。とはいえ、LINECOUNTER命令を使えば、どこをメインに考えればよいかすぐにわかりますし、ALIAS命令で割り当ては簡単に変えられて試せるので、それほど時間をかけずとも結構な速さになります。TIMEMODE命令があるので試行錯誤も効果のほどもすぐに確認できます。 -つまり言語が自動であれこれとやってくれる言語ではなく、人間が高速なプログラムを書くのを支援することに特化したのがES-BASICなのです。・・・人間は最適化のために高負荷な部分をあれこれといじっているうちに不要な変数の存在に気づいたり、ループ内で不変な演算をループの外で計算するようにしたり、そういうことを自然にやります。それはもちろんコンパイラが自動でやっていることそのものなのですが、しかし人間のセンスはコンパイラよりも上なので、最後は人間が勝つと私は思っています。つまりES-BASICは最終的には最強の言語になるはずなのです。私はそう思っているのです。 ** (18) デバッグ支援機能 -ES-BASICではデバッグ支援も大事な要素です(SecHack365の教材でもあるので)。 -そもそも、一般に開発時間の4~7割はデバッグの時間になっているとも言われています。そうであるならば、言語がデバッグの支援のためにできることはないかとあれこれ試みることは、十分に理にかなっていると私は思います。 -プログラムをRUNしてみて、動作が怪しいと思ったらグラフィックウィンドウで「Shift+Home」を押します。するとすぐにPAUSEがかかるので、今どこを走っているか、変数はどうなっているかなどをPRINT命令をダイレクトコマンドで実行して確認します。納得できて続行してもいいと思えたら、RESUMEで再開します。 -そうではなく、デバッグ中にもう怪しいところの目星がついているのなら、そこにPAUSEコマンドを書いておくこともできます。そうするとブレークポイント的に機能します。 -配列で添え字を間違えて対象の変数以外を書き換えて大惨事になる(=もはやどこに原因があるのかわからなくなる)というのは、まあC言語とかではたまにやらかしてしまうことですが(ようするにポインタ系のバグ)、ES-BASICは配列アクセス時に添え字の範囲をチェックしているので、そんなミスは絶対におきません。やらかす直前にちゃんとどこでやってしまいそうになったかも含めて報告してくれます。 ---- -そしてES-BASICのデバッグにおける目玉機能はなんといっても「DebugTrap」です。 -原理はとても単純で、各行を実行する直前に、指定しておいたサブルーチンを呼び出すというだけです。しかしこれにより、もし変数iが20以上になったら即座にPAUSEする、みたいなことが簡単にできます。 9000 NODEBUG 1 9010 LABEL TRAP 9020 IF I>=20 THEN 9030 PRINT "I>=20 IN ",$LINE 9040 PAUSE 9050 FI 9060 RETURN -たったこれだけで、変数iをすべての行で監視できるようになるわけです。実際、配列アクセスなどで変数がおかしな値になっているということが指摘されてわかっても、その原因がどこなのかを突き止めるのはそれなりに面倒なことです。でもこの機能を使えば簡単に見つけられるわけです。 -今回は簡単な条件でしたが、もっとややこしい条件でももちろん見つけられます。IF文を書くだけのことです。 -プログラム中に出てきた変数$LINEは特別な変数で、どこの行からGOSUBしてきたのかを確認するためのものです。これを使うと、 9051 IF $LINE==2340 THEN PRINT "[2340] "; FI -みたいなこともできます。これは2340行の行頭に PRINT "[2340] "; を書き足したのと同じ効果があります。 -また、 9052 IF $LINE==3450 THEN NORETURN 3460; FI -ということもできます。これは3450行を実行せずに3460行へ帰ることになるので、3450行をコメントアウトしたのと同じことになります。 -こうしてプログラム本体には一切手を入れずに、デバッグのための一時的な変更を記述することができるのです。 * こめんと欄 #comment
タイムスタンプを変更しない
* ES-BASIC #11 -(by [[K]], 2019.11.19) ** (13) ES-BASIC ver.0.1b のソースコード規模 -全体(すべてC言語で書かれています) |ファイル名|行数|説明| |''kcl03'' [川合用汎用ライブラリ]|RIGHT:1360行|高速なmalloc/free, 可変長配列, ハッシュテーブル, ソートなど| |''kll00'' [川合用汎用言語ライブラリ]|RIGHT:890行|ストリングID, 簡易レキサー(テキストをトークン列に切り分ける), トークン列比較など| |''bla'' [川合用汎用グラフィックライブラリ]|RIGHT:719行|小規模なグラフィックライブラリ| |''bla2'' [川合用汎用グラフィックライブラリ]|RIGHT:105行|blaの追加ライブラリ(グラフィックキャラクタなど)| |''chr''|RIGHT:105行|bla2用のキャラクタデータ| |''esbasic''|RIGHT:1748行|''ES-BASIC本体''(今回は主にここを開発)| |(合計)|RIGHT:4927行|実行ファイルサイズは64bit版が102.0KBで, 32bit版が102.5KB| -esbasicの内訳(主要なもののみ) |関数名|行数|説明| |sub_~|RIGHT:245行|生成した機械語から呼ばれるための関数群。| |loadReg|RIGHT:257行|式の記述から機械語を生成するための関数(結果をレジスタに格納)。以下の「サポートしている演算子」に対応している。| |||演算対象がメモリかレジスタか定数なのかで機械語を作り分けて多少は高速化しているので、ここはサポートしている演算子の種類の割には長めになっている。| |OpCall_put|RIGHT:64行|sub_~を呼び出すためのコードを生成。主にパラメータの評価と受け渡し処理。| |eval0|RIGHT:60行|機械語のエントリ部分のコード生成。| |evalAdd|RIGHT:408行|指定された1行を機械語に翻訳。以下の「サポートしている命令」に対応している。結構たくさんあるのでそれなりには長い。| |eval1|RIGHT:50行|機械語のジャンプ先の確定処理や、実行処理など。| |eval2|RIGHT:26行|機械語生成のために使用したメモリ域などを解放。| |cmplTimeRun0|RIGHT:26行|@cmplTimeやCONST()の実現のための記述。| |sub_run|RIGHT:77行|上記の関数群を使ってプログラムを機械語に翻訳して実行する。デバッグのためのコードも混ぜ込む。| |cmdlineLoop|RIGHT:115行|REPL(Read-Exec-Print-Loop)のための記述。REGS命令、LOAD命令、RESUME命令は例外的にここで処理される。| |bla_main|RIGHT:74行|ちょっとblaライブラリの都合で名前が変だけど、要するにmain関数。主に初期化処理をしている。| |(小計)|RIGHT:1402行|これで全体の80.2%を説明できたことになる。| -このセクションでアピールしたいことは、ES-BASICが小規模であるということです。 -高速なスクリプト言語を作るのは、世間で思われているほどには難しくないのです。・・・っていうか難しかったら私にはできません。 ** (14) ES-BASIC ver.0.1b の主な機能 -[1]かなり高速、コンパクト --gccに近い速度が簡単に出ます。スクリプト言語(インタプリタ)としては''「規格外」''の速度です(下記のベンチマーク欄を見てください)。 --たぶんこの点が最大の特徴です。 --言語処理系は''100KB程度''であり、この規模の言語としてはコンパクトなほうだと思われます。 --もちろんスクリプト言語なので、実行ファイルを作るなどの作業を事前にする必要はなく、RUNするだけですぐに実行できます。 --なお、内部でJITコンパイラを持つことで、高速な実行と言語処理系の小ささを両立させています(JIT = Just In Time)。 -[2]基本構文 --すべての行に行番号を付けなければいけないタイプのBASIC言語です(私が好きな1980年くらいのBASICはみんなそんな感じでした)。 --一つの行に複数の文を書くことができます。その場合は、セミコロンで区切ります。 --テキストエディタなどがなくても、コンソールだけでプログラムを作ることができます。 --言語としては、静的型付き言語です(そもそもBASICはそういう言語です)。動的型付き言語だと、私の力ではスピードが出せそうにないので今回はあきらめました -[3]サポートしている演算子 |+|-|*|/|%|<<|>>|&|||^|array[i]| |加算|減算|乗算|除算|剰余|左シフト|右シフト|ビットAND|ビットOR|ビットXOR|配列| --このほかにも A*:B>>C のような演算子も持っていますが、説明がめんどくさいのでひとまず省略します。 --配列アクセスは添え字が宣言の範囲外になると、実行時エラーで止まります。 -[4]サポートしている命令 |命令|ver|説明| |A=B|01b|単純な代入文です。Bの部分は式になっていてもOKです。| |PRINT ...|01b|式の値をコンソールに表示します。| |FOR A=B,C ... NEXT|01b|定番の繰り返し命令です。終値の後ろに増分値を指定することもできます。| |FORNE A=B,C ... NEXT|01b|FOR I=0,10 ... NEXT は、I=10も回りますが、FORNEだとI=9までしか回りません。| |DOLOOP ... ENDDO 条件|01b|do~while相当のループです。条件を省略すると無限ループになります。| |IF 条件 THEN ... ELSE ... FI|01b|定番のIF文です。ELSE節は省略可能です。| |LABEL ラベル名|01b|GOTOやGOSUBの飛び先のためにラベルを宣言できます。| |IF 条件 GOTO 行番号orラベル|01b|条件ジャンプです。| |GOTO 行番号orラベル|01b|無条件ジャンプです。| |GOSUB 行番号orラベル|01b|サブルーチンを呼び出します。| |RETURN|01b|サブルーチンから復帰します。| |NORETURN|01b|サブルーチンから復帰しますが、呼び出し元には帰りません。行番号orラベルを追加指定することもできて、その場合はGOTO動作も行います。| |PUSH 式|01b|式の値をスタックに保存します。| |POP 変数名|01b|スタックから値を取り出します。今のES-BASICにはローカル変数がないので、PUSH/POPとGOSUB/RETURNを使うと、再帰処理ができるようになります。| |LIST|01b|プログラムを表示します。| |RUN|01b|プログラムを実行します。| |RENUM|01b|行番号を付け直します。BASICが好きな人には定番の命令です(笑)。| |NEW|01b|プログラムを全行消去します。ただし変数の値は消えません。| |EXIT|01b|ES-BASICを終了します。| |END|01b|プログラムを正常終了します。| |STOP|01b|プログラムを中断します。どの行で中断したのかも表示されます。再開はできません。主にエラー終了のために使います。| |PAUSE|01b|プログラムを一時停止します。どの行で中断したのかも表示されます。RESUME命令で再開可能です。停止中に変数を見たり変更したりすることが可能です。| |LOAD ファイル名|01b|ファイルからプログラムを読み込みます。プログラムではなくコマンド列でもOKです。| |ALIAS|01b|変数名や予約語や演算子に別名を与えます。| |RAND|01b|0~32767の乱数を生成します。たいていは%で適当な範囲に丸めてから使います。| |INPUT|01b|コンソールから数値を入力します。| |OPENWIN|01b|グラフィックウィンドウのサイズを指定します。| |A=RGBCOL(red,green,blue)|01b|RGB値からカラーコードを取得します。r/g/bはそれぞれ0~255です。| |GCLS|01b|グラフィックウィンドウ全体を一色で塗りつぶします。| |SETPIX|01b|グラフィックウィンドウに点を描画します。| |FILLRECT|01b|グラフィックウィンドウに塗りつぶした長方形を描画します。| |DRAWRECT|01b|グラフィックウィンドウに塗りつぶさない長方形を描画します。| |FILLOVAL|01b|グラフィックウィンドウに塗りつぶした楕円形を描画します。| |DRAWOVAL|01b|グラフィックウィンドウに塗りつぶさない楕円形を描画します。| |DRAWLINE|01b|グラフィックウィンドウに直線を描画します。| |FILL|01b|グラフィックウィンドウの任意の領域を塗りつぶします。| |GPRINTI|01b|グラフィックウィンドウ内に数値を描画します。| |GPRINTS|01d|グラフィックウィンドウ内に文字列を描画します。| |EWAIT|01b|指定した時間だけ待機します(sleep)。単位はミリ秒です。| |EINKEY|01b|グラフィックウィンドウへのキー入力を得ます。何も入力がないときは0を返します。| |FLUSHWIN|01b|描画結果を確実に画面に反映させます。たいていはこの命令を使いませんが、負荷が高いときでも画面に反映させたいときは、時々使います。| |ECHR|01b|グラフィックウィンドウ内にキャラクタを描画します。| |GETCHR|01b|グラフィックウィンドウ内の指定された座標に表示されているキャラクタの番号を取得します。| |GETCOL|01b|グラフィックウィンドウ内の指定された座標に表示されているキャラクタの色を取得します。| |CHRBOX|01b|ECHRを繰り返してキャラクタで長方形を描画します。| |ARY INT NEW A[B]|01b|B個の要素を持つ配列Aを準備します。Bは定数式でなくても構いません。内部的には配列のリサイズもできるようになっているのですが、まだリサイズ命令を作っていません。| |ARY INT DEL A|01b|配列Aを破棄します。| |FF16SIN|01b|三角関数の値を計算します。角度はラジアンではなく65536が1周になるような単位で渡します。また結果は小数ではなく65536倍した結果(=固定小数点)で渡されます。| |FF16COS|01b|FF16SINのコサイン版です。| |REGS|01b|現在のレジスタの値を簡単に確認できます。| |CODE|01b|これは必殺技みたいな命令で、16進数列を書くとそれを機械語として実行します。インライン機械語みたいなものです。| |||試してみたい機械語があったとき、これで試して、REGSで結果を見たりして遊びます。それ以外ではめったに使いません。機械語を間違えればすぐにES-BASICが落ちます(笑)。| |LWAIT|01b|実行速度を指定します。-1が一番高速でいくつかのデバッグ機能は無効になります。0はデバッグ機能がすべて有効になっている中では最も高速、1以上は適当にウェイトがかかるようになります。| |TIMEMODE|01b|1を指定すると、コマンドやプログラムの実行に要した時間が表示されるようになります。高速化のために試行錯誤したいとき、これはかなり重宝します。| |CODEDUMP|01b|1を指定すると、コマンドやプログラムがどんな機械語になったのかを確認することができます。| |LINECOUNTER|01b|プログラムを実行した後にこの命令を実行すると、どの行が何度実行されたのかを簡単に確認することができます。| |||これで「ここは通ったかな?」などとPRINTデバッグする必要はなくなるし、どこが負荷が重いのかを簡単に確認することもできます。| |NODEBUG|01b|これはプログラム内で使う命令で、1を指定すると以降の行はLWAITが0以上でもデバッグ対象にはならなくなります。0を指定すると、以降の行はまたLWAITに応じてデバッグ対象になるようになります。| |||これで怪しくない部分を高速に実行できるようになります。| |DEBUGTRAP 行番号orラベル|01b|この命令で指定されたサブルーチンは、デバッグトラップルーチンだと認識されるようになります。0を指定すると設定が解除されます。| |||デバッグトラップルーチンは、各行を実行する前に必ず呼び出されるサブルーチンで、変数$LINEにこれから実行しようとする行番号が入ります。| |||これにより、変数の値を監視したり、ある行の実行の前だけ別の処理を追加したり、NORETURNを使ってある行の実行をスキップしたり、そのほかいろいろな応用ができます。| |||デバッグトラップルーチンの中にデバッグ対象行があると、呼び出しが無限に止まらなくなってしまうので(笑)、NODEBUG命令で対象外に指定しておく必要があります。| |PROGSEL|01b|実はES-BASICは複数のプログラムを同時に管理することもできます。これはプログラムスロットを切り替える命令です。| |||PAUSE中に変数をいろいろ表示したいのなら、別スロットに変数を表示するためのプログラムを書いておいて、それをRUNすればいいのです。| |PROGRUN スロット番号|01b|PROGSELでアクティブなスロット番号を切り替えずにRUNする命令です。| |PROGNEW スロット番号|01b|PROGSELでアクティブなスロット番号を切り替えずにNEWする命令です。| |PROGLIST|01b|現在プログラムが格納されているスロット番号の一覧と、そこに入っているプログラムの行数を表示します。| |PROGDEL スロット番号|01b|指定されたプログラムスロットを削除します。| |CONST(式)|01b|式の内容をコンパイル時に計算して定数として埋め込みます。| |||したがってCONSTの中で変数を参照していて、その変数の値をあとから変更しても、ここの値はコンパイル時(=実行開始時)から不変のままになります。| |@CMPLTIME ~ @EXECTIME|01b|@CMPLTIMEのあとに書かれた行は、実行時ではなくコンパイル時に実行されます。@EXECTIMEまでがコンパイル時実行の対象です。| |||その演算結果を$RETVALに格納して、以降のプログラムでCONST($RETVAL)として参照して使うのが普通です。こうすることで、C++でのconstexprみたいなことができます。| |||ES-BASICはスクリプト言語なので、プログラムを実行して値を得ることは基本的に朝飯前です。だからこんな機能も簡単に実現できています。| |||これにより、複雑な計算結果を定数として使いたいときに、計算を言語に任せることができます。| |FF16SQRT|01d|固定小数点での平方根を計算します。| |CLRKEYBUF|01d|キーバッファをクリアします。| |KWAIT|01d|指定されたキーが押されるまで待ちます。| |SETGMODE|01d|描画モードを指定します。| |GETPIX|01d|画面上の点の色を取得します。| |BITBLT|01d|配列データを画面の矩形範囲に転送します。| ** (15) 実プログラム例 -[[esbasic0010]]を見てください。 ** (16) ベンチマーク -詳細は[[esbasic0013]]を見てください。 |言語|処理系のインストールサイズ|タイプ|実行時間|GCC x64 -O3比|備考| |GCC x64 8.1.0 -O3|RIGHT:449256KB|コンパイラ言語|RIGHT:1.115秒|RIGHT:1.00倍|これはコンパイル時間を含んでいません。ちなみにコンパイル時間は0.37秒でした(これは複数回試した最小値です)。| |ES-BASIC ver.0.1b 64bit版|RIGHT:102KB|スクリプト言語|RIGHT:1.270秒|RIGHT:1.14倍|これはJITコンパイル時間を含んでいます。GCCに対して、1.14倍くらいの時間がかかっていることになります。| |Ruby 2.6.4p104 x64|RIGHT:75323KB|スクリプト言語|RIGHT:76.102秒|RIGHT:68.2倍|[[esbasic0013]]にテストに使ったRubyのコードがあります。| |Python 3.7.4|RIGHT:85771KB|スクリプト言語|RIGHT:344.891秒|RIGHT:309倍|[[esbasic0013]]にテストに使ったPythonのコードがあります。| |Python+Numba||スクリプト言語||RIGHT:2倍?|以前類似のテストをしたことがあり、その時の感触から推定(後日測定して更新します)。| |JavaScript(Node.js)||スクリプト言語||RIGHT:15倍?|以前類似のテストをしたことがあり、その時の感触から推定(後日測定して更新します)。| -一般に、Rubyはこの手の計算だとGCCの30倍程度の時間がかかります。Pythonはさらにもっと遅いですが、NumbaというJITコンパイラを使うとGCCの2倍くらいまで一気に高速化されます。 --mrubyのJITコンパイラもあるので、これもいつかやってみたいです。 https://qiita.com/miura1729/items/a1828849ec8fec596e74 -Node.jsを使えばJavaScriptでも同様の計算をすることができて、GCCの15倍くらいの時間で実行できます。 ** (17) どうしてそんなに速いの? -まず、世間で有名な言語がいろいろ最適を頑張っているのは知っていますが、それらはES-BASICでは基本的に一切やりません。それをやったらコンパイル時間が長くなってしまいます。ES-BASICでは実行時間だけではなくJITコンパイル時間も短くしたいので、そんな方法は取れません。・・・そして世間の優秀なJITコンパイラたちは「Tracing JIT」みたいな技術を併用して、時間をかけて最適化しても元が取れそうな場所を「自動で」発見し、そこにたくさんの技法を適用して高速化しています。でもそんな難しいプログラムは私には書けないというか、私じゃない人が書いた方がうまいと思うので、私はそれはやりません。 -ES-BASICでは最適化をがんばらない代わりに、レジスタを直接使ってもよいことにしました。つまり、「i=1」と書けばメモリ上の変数iに1が代入されますが、「RAX=1」と書けばRAXレジスタに1が代入されるわけです。またレジスタ名がむき出しだと可読性に難があったので、ALIAS命令でレジスタ名を隠せるようにしました。「ALIAS i:RAX」と書けば、以降は「i=1」でRAX=1になるわけです。 -どの変数にどのレジスタを割り当てればいいのか、どの変数はメモリのままでいいのか、それらについては基本的に人間が勘で決めることになります。とはいえ、LINECOUNTER命令を使えば、どこをメインに考えればよいかすぐにわかりますし、ALIAS命令で割り当ては簡単に変えられて試せるので、それほど時間をかけずとも結構な速さになります。TIMEMODE命令があるので試行錯誤も効果のほどもすぐに確認できます。 -つまり言語が自動であれこれとやってくれる言語ではなく、人間が高速なプログラムを書くのを支援することに特化したのがES-BASICなのです。・・・人間は最適化のために高負荷な部分をあれこれといじっているうちに不要な変数の存在に気づいたり、ループ内で不変な演算をループの外で計算するようにしたり、そういうことを自然にやります。それはもちろんコンパイラが自動でやっていることそのものなのですが、しかし人間のセンスはコンパイラよりも上なので、最後は人間が勝つと私は思っています。つまりES-BASICは最終的には最強の言語になるはずなのです。私はそう思っているのです。 ** (18) デバッグ支援機能 -ES-BASICではデバッグ支援も大事な要素です(SecHack365の教材でもあるので)。 -そもそも、一般に開発時間の4~7割はデバッグの時間になっているとも言われています。そうであるならば、言語がデバッグの支援のためにできることはないかとあれこれ試みることは、十分に理にかなっていると私は思います。 -プログラムをRUNしてみて、動作が怪しいと思ったらグラフィックウィンドウで「Shift+Home」を押します。するとすぐにPAUSEがかかるので、今どこを走っているか、変数はどうなっているかなどをPRINT命令をダイレクトコマンドで実行して確認します。納得できて続行してもいいと思えたら、RESUMEで再開します。 -そうではなく、デバッグ中にもう怪しいところの目星がついているのなら、そこにPAUSEコマンドを書いておくこともできます。そうするとブレークポイント的に機能します。 -配列で添え字を間違えて対象の変数以外を書き換えて大惨事になる(=もはやどこに原因があるのかわからなくなる)というのは、まあC言語とかではたまにやらかしてしまうことですが(ようするにポインタ系のバグ)、ES-BASICは配列アクセス時に添え字の範囲をチェックしているので、そんなミスは絶対におきません。やらかす直前にちゃんとどこでやってしまいそうになったかも含めて報告してくれます。 ---- -そしてES-BASICのデバッグにおける目玉機能はなんといっても「DebugTrap」です。 -原理はとても単純で、各行を実行する直前に、指定しておいたサブルーチンを呼び出すというだけです。しかしこれにより、もし変数iが20以上になったら即座にPAUSEする、みたいなことが簡単にできます。 9000 NODEBUG 1 9010 LABEL TRAP 9020 IF I>=20 THEN 9030 PRINT "I>=20 IN ",$LINE 9040 PAUSE 9050 FI 9060 RETURN -たったこれだけで、変数iをすべての行で監視できるようになるわけです。実際、配列アクセスなどで変数がおかしな値になっているということが指摘されてわかっても、その原因がどこなのかを突き止めるのはそれなりに面倒なことです。でもこの機能を使えば簡単に見つけられるわけです。 -今回は簡単な条件でしたが、もっとややこしい条件でももちろん見つけられます。IF文を書くだけのことです。 -プログラム中に出てきた変数$LINEは特別な変数で、どこの行からGOSUBしてきたのかを確認するためのものです。これを使うと、 9051 IF $LINE==2340 THEN PRINT "[2340] "; FI -みたいなこともできます。これは2340行の行頭に PRINT "[2340] "; を書き足したのと同じ効果があります。 -また、 9052 IF $LINE==3450 THEN NORETURN 3460; FI -ということもできます。これは3450行を実行せずに3460行へ帰ることになるので、3450行をコメントアウトしたのと同じことになります。 -こうしてプログラム本体には一切手を入れずに、デバッグのための一時的な変更を記述することができるのです。 * こめんと欄 #comment
テキスト整形のルールを表示する