ラベル Natural Tiny Basic の投稿を表示しています。 すべての投稿を表示
ラベル Natural Tiny Basic の投稿を表示しています。 すべての投稿を表示

2014年11月30日日曜日

lcdtoolにスクリプト機能を追加しようと思ってNT-Basicの方を改良する事になる話

割と地味なlcdtoolの地味な改良を考える(だけ)」でlcdtoolにスクリプト機能を追加する事を考え、手っ取り早くNatural Tiny Basic (NT-Basic)を使ってやっつける事にしたのですが、NT-Basicのウェブの説明が、最新の機能と説明が食い違うなど、まったく酷い感じでした。自分で作っていながら、「あぁ、何だこりゃ」という感じ。

lcdtoolに組み込み作業を始めた時点で「あぁ、抽象化層に外部オブジェクトへの参照を渡したいなぁ」とか「ウェブをもっと綺麗にしたいなぁ」とか、本来のlcdtoolの機能追加とは本来関係ない事を考え始めました。

そもそもNT-Basicは、Windows、Mac OS、Linux、組み込みシステム、様々な環境で動作させる事ができるように設計してあるのですが、あらゆる環境に組み込む時の理解のしやすさも重要なポイントになります。

でも、従来のウェブには理解の助けになる情報が殆ど存在せず、実際に組み込みたい気持ちになるには何らかの積極的な動機が必要でした。そして、ただでさえ「いまさらBASIC感」が溢れているのに、積極的な動機を見つけるのは難しいものです。

とはいえ、教材としての面白さもあるので、NT-Basicの小さな改良とウェブの更新を先に行なう事にしました。


新しくリリースしたNatural Tiny Basic (NT-Basic)のVersion 0.3.0では、NT-Basicの初期化時にextobjに渡したポインタをhal.c内部で受け取る事ができます。これにより、NT-Basicのインスタンス毎に別々の処理を実行させる事も可能です。

また、新しくなったウェブには従来よりもわかり易い説明を追加しました。
ちょっと遊んでみたい人には参考になると思います。

2014年9月30日火曜日

割と地味なlcdtoolの地味な改良を考える(だけ)

lcdtoolとは、テキストLCDの表示内容を模擬してBMPファイルを生成してくれるツールです。
LCDプロファイルとLCDフォントファイルを作れば、様々なタイプのテキストLCDを模擬できます。





「リアルな操作画面のキャプチャが欲しい! ~ドキュメント制作時に便利なLCD画像生成ツールを作ってみた~」で述べたように、取り扱い説明書やらで便利に使えるツールなのですが、割と色々なところに使い始めて、スクリプトのような機能を内包したくなってきました。

というのも、世の中には文字列だけの表示器だけではなく、記号や図形を組み合わせた表示器が存在します。このような場合、従来のlcdtoolではうまく対応できません。

そこでアレコレ考えた結果、lcdtoolNT-Basicを組み込んでBasicで描画処理を記述できるようにしようと思い立ちました。(なんでBasicなんですか?とか聞かないで下さい・・・)

NT-Basicを使った描画処理は、従来から存在するLCDプロファイルにスクリプトファイル指定を追加し、外部のNT-Basicスクリプトを実行する形式で実現する予定です。この機能追加が実現すれば、lcdtoolを使って更に柔軟な表現が可能になります。

この機能追加を行なったlcdtoolは、遅くとも年内にリリースする予定。
現在、GUI化も検討しており、楽しみな改良計画です。

2012年9月23日日曜日

小規模組み込みシステムで使えるBASICインタプリタをKOZOSと同時に楽しむ! (NT-Basic on KOZOS)

先の記事「NT-Basic on KOZOSのために・・・ (NT-Basicの改良)」では、小規模組み込みシステムで使用可能なBASICインタプリタであるNT-Basicの改良を行ないました。

やっとこさ使えそうな物になってきたので、NT-Shellと組み合わせてKOZOS上で動作させてみました。

[フレーム]

デモの中で流れている音楽は、KOZOS EXPBRD上に搭載されたマイクロSDカードから再生しています。
と同時に、シリアルコンソールからBASICインタプリタを楽しむ事ができるというのが今回のデモです。

何かオペレーティングシステムを使っているからこその動作が欲しかったので作ってみました。

端末の処理はNatural Tiny Shell (NT-Shell)の処理系を使用しています。
自然な入力が可能で、入力処理が破綻せずスムーズにプログラムの編集ができます。

KOZOS EXPBRDには、グラフィックLCDや入力スイッチが搭載されています。
NT-Basicに命令を追加してグラフィック制御などもできるようにするとゲームも作れそう。

NT-Basicの拡張でKOZOSも使うからKZ-Basicなんていうのも洒落ています。
KZ-Basic (拡張NT-Basic)という名称で拡張していく事を考えています。

今回のソースコードは「茶室で楽しむKOZOS拡張基板」でもお渡しする予定です。

2012年9月22日土曜日

NT-Basic on KOZOSのために・・・ (NT-Basicの改良)

NT-Basic on KOZOS(NT-Basicの改良)

SWEST14での刺激を受けた結果、インタプリタの実装に興味が出てきてNT-Basicなるものを出力しました。

それではという事でKOZOSと組み合わせて使おうと思ったのですが、実際にあまり使い勝手が良くなかったのでNT-Basicの改良から行うことにしました。

エディタが必要だ

KOZOS上で実際に動作させる場合、単にファイルシステムからコードを読み込んで動作させるだけでは面白くありません。ターミナルエミュレータから実際にコードを入力して実行するようなインタラクティブな動作を実現したいのです。

この時に必要になるのが実用的なエディタです。入力を間違えても再編集できて、昔ながらのBasicシステムに似たような動作を提供できるのが理想です。viのように本格的なエディタを設計する気分にはなれなかったので、以下のような戦略をとることにしました。
  1. 行編集機能はNT-Shellを用いて機能提供する。
  2. プログラムは新たに作るtexteditモジュールが保持する。
  3. ターミナルにrunコマンドを与えた時点でイタンプリタを動作させる。
こうする事でtexteditモジュールのみを設計実装すれば済みます。
texteditモジュールのインターフェースは以下のようにしました。

int textedit_init(textedit_t *p);
int textedit_clear(textedit_t *p);
int textedit_insert(textedit_t *p, const int line_number, const char *text);
int textedit_delete(textedit_t *p, const int line_number);
int textedit_fetch(textedit_t *p, const int line_number, char *text, const int maxsize);

NT-Basicはプログラムを文字列として受け取ります。
エディタを設計するならば、内部データは双方向リンクリストで構成する行データの集合として表現したいのですが、NT-Basic側の都合に合わせて単純な一つの文字列で複数の行編集が可能なtexteditモジュールとして設計しました。

これでプログラムの編集は実現できます。

プログラムエラー発生時の挙動修正

初版のNT-Basicでは、プログラムエラーが発生した時に最終的にハードウェア抽象化層のhal_halt関数を呼び出していました。この関数は無限に処理を返さない関数として実装する事を想定しており、「プログラムエラー=処理を永遠に返さない」というあんまりな挙動を提供する結果を生んでいました。

「それはないだろう」という事で、エラーを検出した時点で、そこから最終的にntbasic_executeまでエラーを伝搬させるようにしました。HAL層にhaltがあると意味がわからないので、hal_haltは削除しました。ちなみに、c89で上位にエラーを伝搬させるのは丁寧に実装するしかない、みたいな感じの実装になっています。

上位でエラー発生時の行番号の取得も可能です。

NT-Basicの実行方法

初版の実装でもう一つ致命的に使いにくいのが、実行方法でした。ntbasic_executeは、実行したらプログラム終了まで処理を返さないという仕様だったのです。が、これではベアメタルシステムで使いにくくて仕方ありません。NT-Basicでチョコチョコ処理をしながら、別の作業をさせたい事もあるわけです。

実際に使ってみると、先ほどのエラー時の挙動と合わせて組み込みシステムで実質使えないような挙動になっていました。

そこで、改良したNT-Basicでは、ntbasic_executeを呼ぶ度に1ステートメントのみ処理するように仕様を変更しました。こうする事でベアメタルシステムでもNT-Basicを導入しやすくなります。

 ntbasic_t ntbasic;
 ntbasic_error_t error;
 /*
 * Setup and execute.
 */
 error = ntbasic_setup(&ntbasic, program);
 while (error == NoError) {
 error = ntbasic_execute(&ntbasic);
 }
 /*
 * Check the termination code.
 */
 if (error != EndOfTheProgram) {
 core_error_message(&ntbasic, error);
 }

上記のような簡単なコードを上位に加えれば従来のような使い方もできます。

辛かった変数名の制限

最初の実装では変数名はAからZまでの26文字という小さなBasicではありがちの制限を持っていました。

実際にプログラムを組んでみるとわかるのですが、これがかなり辛い制限です。
「あれ?この変数は何を入れてるんだっけ?」みたいにすぐに迷子になれます。
「えーと、Rはアレの略にしたから・・・、で、Aは・・・何だっけ?」みたいに脳内記号表を使う羽目になるわけです。本来、これは機械にやらせるべき作業内容です。

そこで、自由に変数名を付けられるような仕様変更を行いました。


内部設計と実装は至ってシンプルです。
小さな記号表モジュールを作って対応しました。

記号表の考え方は非常に面白いものです。
ここでハッシュ法を使った動作を簡単なモデルで紹介しましょう。

ハッシュ法を用いた記号表の実現

記号表は、変数名や型、長さやその他属性を管理するテーブルです。
NT-Basicの場合、変数は内部的にintしか持たないので型や長さは存在しません。
記号表における一番シンプルな例という事になります。

で、与えられた変数名から実際の値を取りだす場合、テーブルを探索する事になります。
線形探索した場合、最悪の場合テーブルのサイズ分の文字列比較作業が必要になります。


上記の図でGHIという変数を探索した場合、先頭から3番目で探索完了になりますが、実際にテーブルの最後に存在した場合、テーブルサイズ分の探索作業を行なう事になります。

これでは変数にアクセスする度に膨大な時間が必要になるので効率的とは言えません。
そこで考えられたのがハッシュ法です。

基本的な考えは非常にシンプルです。

まず、与えられた文字列から導出可能なハッシュ値を得ます。
このハッシュ値は文字列から得られる値で、同じ文字列からは毎回同じ値が得られます。


次に、この得られたハッシュ値を元にテーブルの参照先インデックスを決定します。
例えば、参照先インデックスとして28という数値が得られたら、記号表のインデックス28を参照するという仕組みです。

有限長のテーブルの場合、「参照先インデックス % テーブルサイズ」などとして参照先インデックスを決定します。


整理すると「文字列からハッシュ値」、「ハッシュ値からテーブル参照先インデックス」という過程で、変数名を与えるだけで一意に決まる(ように見える)インデックス番号が求まりました。

さて、ここで疑問が沸いてきます。
仮にテーブルのサイズが16だったとして、ハッシュ値0と16の結果はどうなるでしょうか?

先ほどの例では「参照先インデックス % テーブルサイズ」なのでテーブルの参照先がどちらも0になってしまいます。

変数名として与えられた文字列が異なるものなので、理想的には得られたハッシュ値は異なる結果となります。それにも関わらずテーブルの参照先が同じになってしまいました。

安直な実装の場合、参照先インデックスに格納されている変数名と照合して、合致すれば期待する変数が格納されているものとして処理します。

そして、期待する変数名でなければ近い位置のテーブルを探します。
これにより、線形探索よりも遥に少ない処理で期待する変数値を得ることができます。

実際には、ハッシュ値自身の衝突も考える必要があります。
ハッシュ値自身の衝突の場合も、上記のように期待する変数でなければ近い位置のテーブルを探索するという方法を取ります。

ちなみに、流石に上限なしで対応させるとメモリが幾らあっても足りないので、デフォルトの定義では変数名8文字、変数32個までにしてあります。これはヘッダファイルを書き換えるだけで大きくする事ができます。組み込むシステムに合わせて定義を修正すれば良いでしょう。

この改良によって、以下のようなプログラムが見違えるように判りやすく書けるようになります。
以下はアリスとボブとキャロルの年齢を総計として出力する例です。(ちょっと無理矢理な例)

改良前
INPUT "A=",A
INPUT "B=",B
INPUT "C=",C
T = A + B + C
PRINT "T=",T

改良後
INPUT "Alice=",Alice
INPUT "Bob=",Bob
INPUT "Carol=",Carol
Total = Alice + Bob + Carol
PRINT "Total=",Total

まとめ

今回は小規模組み込みシステムで楽しむためのNT-Basicの改良として、実行方法、エラー時の挙動、変数名の制限について改良した内容について示しました。

ちなみに、NT-Basicは総行数が約2000行とコンパクト。
1人で簡単に読み切れるサイズです。

--------------------
LinesFile
--------------------
438core.c
281expression.c
109hal.c
106main.c
157ntbasic.c
269ntlibc.c
443statement.c
111tinyrand.c
135variable.c
--------------------
2049Total

最新のリリースは以下からダウンロードできます。
http://shinta.main.jp/firmware/ntbasic/ntbasic_ja.html

2012年9月8日土曜日

Natural Tiny Basic (NT-Basic) ~WindowsでもLinuxでもMac OSでも組み込み機器でも楽しめるBASICインタプリタ~

まつもとゆきひろさんから得た刺激

先日のSWEST14でまつもとゆきひろさんの講演を聞いてからというものの、言語処理系にとても興味が湧いてきました。

何から手を付けて良いかわからなかったので、手始めに既存のBASICインタプリタに手を入れるところから始めました。
オーディオプラットフォームであるBlueTank BF592で動作させた時の様子が以下の動画です。

[フレーム]

NT-Basicのアプリケーションコード

NT-Basicを使用するアプリケーションコードは至ってシンプルです。

ntbasic_ ntbasic;
ntbasic_execute(&ntbasic, your_program);

上記のようにntbasicハンドラとプログラム文字列をntbasic_execute関数に渡してあげるだけです。
先ほどの動画の動作は以下のコードから実現しています。

#include "ntbasic.h"
#include "uart.h"
#include "lcd.h"
#include "bfin_util.h"
int main(void)
{
 ntbasic_t ntbasic;
 char *prog1 =
 "100 FOR I = 1 TO 8\n" \
 "110 C = 65 + I - 1\n" \
 "120 WRITE 1, C\n" \
 "130 NEXT\n" \
 "140 END\n";
 char *prog2 =
 "100 FOR I = 1 TO 8\n" \
 "110 C = 97 + I - 1\n" \
 "120 WRITE 1, C\n" \
 "130 NEXT\n" \
 "140 END\n";
 uart_init();
 lcd_init();
 while (1) {
 /*
 * プログラム1を実行する。
 */
 lcd_goto(0, 0);
 ntbasic_execute(&ntbasic, prog1);
 bfin_util_usleep(1000000);
 /*
 * プログラム1で書いた表示を消去する。
 */
 lcd_clear();
 bfin_util_usleep(1000000);
 /*
 * プログラム2を実行する。
 */
 lcd_goto(0, 1);
 ntbasic_execute(&ntbasic, prog2);
 bfin_util_usleep(1000000);
 /*
 * プログラム2で書いた表示を消去する。
 */
 lcd_clear();
 bfin_util_usleep(1000000);
 }
 return 0;
}

今回のターゲットは組み込み装置でしたが、実際にLinuxやWindowsやMac OSでもビルドできるようにしました。

なので、「パソコン上でインタプリタ側のプログラムを動作確認」して「実機で動作」という流れでインタプリタを楽しむ事ができます。

「いつでもどこでもインタプリタ」がキーワードです。

組み込み装置で使うためのHAL構造

この手のソフトウェアで重要なのは構造です。
I/Oは最下層のHAL(Hardware Abstract Layer)が実行するようにしてあります。



インタプリタ上のプログラムは、ハードウェア抽象化層を介し、実際のデバイスを制御します。
ターゲット依存部は単に小さなハードウェア抽象化層に対する実装を加えるのみで済みます。

int hal_getc(void);
int hal_putc(int c);
int hal_read(const int port, int *data);
int hal_write(const int port, const int data);
int hal_halt(void);

hal_getc(void)とhal_putc(int c)は標準入出力系を担います。
ポート番号を指定してデータをI/Oするのが、hal_read(const int port, int *data)とhal_write(const int port, const int data)です。

先ほどお見せした動画は、このHALの層で「ポート番号1に対する出力はLCD」という実装を追加したわけです。

int hal_write(const int port, const int data)
{
 switch (port) {
 case 0:
 /*
 * Port 0 is UART in this port.
 */
 hal_putc(data);
 return 0;
 case 1:
 /*
 * Port 1 is LCD in this port.
 */
 lcd_putc((char)data);
 return 0;
 }
 return -1;
}

実際に動作させてみると結構楽しいおもちゃです。
「茶室で楽しむKOZOS拡張基板」のように何か一つ企画をやりたいなぁと考えています。

リソース

ソースコードは以下からダウンロードできます。

AltStyle によって変換されたページ (->オリジナル) /