ORANGE-light
プログラミング作法(Ver2)

  ORANGE-lightはいろいろなデバイス上で動作するように仮想マシン上で動作し、わかりやすい記述ができることを目指した軽量言語です。
BASIC系の言語ですので、初心者向けの直感的な記述ができ、導入も容易です。
しかしながら、BASICが根底にあるため、オペレーティングシステムやコンパイラーを作成するのは困難です。
Ver2からは、システムプログラミングを可能にするよう改善いたしました。
本サイトでは、Ver1の言語仕様を理解している方向けに、Ver2からの仕様とオススメのプログラミング作法をご紹介いたします。
 
 
変数は宣言してから使おう
  いわゆるTiny BASIC系の言語では1文字の変数名しか使えません。
ORANGE-lightでは長い変数名が使えますので可読性はいいのですが、スペルミスがあっても別の変数と認識してコンパイルに成功してしまいます。
Ver2から宣言されていない変数はエラーとします。宣言はvar文で行います。var文は従来からありましたが、関数の外でも使用できるようになりました。

var i
for i = 1 to 10
  print i
next
     
グローバル変数はやめよう   グローバル変数をなくすのは難しいですが、極力使用をやめましょう。上の例のvar文も簡単なテストプログラム以外では使いません。
プログラムのスタート部分を次のように記述すれば、グローバル変数は使用しないですみます。

main()
end

def main() {
  var i
  for i = 1 to 10
    print i
  next
}

ORANGE-lightで使用するグローバル変数はmstruct文(後述)で宣言したものだけにするのがオススメです。
     
if文の条件式は()で囲み、実行する部分は{}で囲もう   Ver1では、if文の条件式にマッチした場合に複数の文を記述するときには、thenの後に文をコロン(:)で区切って記述していました。

if x = 99 then y = func(x) : print x: print y

Ver2からは複数の文を{と}の間に記述できるようになりました。(この場合、thenは不要)
こうなると、C言語のように記述したくなります。
条件式は()で囲み、比較は代入と区別するために=ではなく==を使います。
(これらはVer1からも使用できました。)

if (x == 99) {
  y = func(x)
  print x
  print y
}
     
文字列定数内にエスケープ文字が使用できるようになりました
  文字列定数内で次のエスケープ文字が使用できます。
表記 文字コード(16進数)
\b 08
\t 07
\n 0A
\f 0C
\r 0D
\' 27
\" 22
\\ 5C
\xHH HH
     
文字定数が使用できるようになりました   if (c == 0x41)などと記述するとわかりづらいので、
文字定数を用いて
if (c == 'A')と記述できるようになりました。
また、文字定数にも上記のエスケープ文字が使用できます。
     
文字列定数を連結できるようになりました   長い文字列を複数行に分けて書けるように、複数の文字列定数を+演算子で連結できるようになりました。

print "The quick brown fox jumps over the lazy dog."
は次のように記述できます。

print "The quick brown fox" +
  "jumps over" +
  "the lazy dog."
 
mstruct文と配列を使おう   ORANGE-lightの最大の特長は、メモリー配列という考え方です。
メモリー配列とは、実行時に確保済みの汎用のメモリーのことで、以下の文または関数を利用してアクセスします。

mpoke文、mpokew文、mptr文、mdata文、mdataw文、mstr文、mpeek関数、mpeekw関数、getmptr関数

グラフィクス表示で利用するスプライトの定義なども、mdata文やmdataw文を使用します。
Ver2でも上記の命令はすべて使用可能ですが、以下で説明するmstruct文と配列式の利用をオススメします。

mstruct文は、メモリー配列を初期化すると同時にグローバル変数の宣言を行います。

mstruct test1 {
  DATA db 1, 2, 3, 4, 5, 6, 7, 8, 9, 10
}

と記述すれば、メモリー配列の0番地から順に1~10のバイトデータが格納され、同時にtest1.DATAというグローバル変数が宣言されます。さらにtest1.DATAという変数には0が格納されます。

続けて
mstruct test2 {
  DATA db 11, 12, 13, 14, 15, 16, 17, 18, 19, 20
}
と記述すれば、メモリー配列の11番地から順に11~20のバイトデータが格納され、同時にtest2.DATAという変数が定義されます。同様にtest2.DATAという変数には11が格納されます。

print test1.DATA
を実行すれば0が表示されます。(test1.DATAの中身が0)
print test2.DATA
を実行すれば11が表示されます。(test2.DATAの中身が0)

メモリー配列にアクセスするには配列式を利用します。
print test1.DATA[0]
を実行すれば1が表示されます。(メモリー配列のtest1.DATA + 0番地が1)
print test1.DATA[1]
を実行すれば2が表示されます。(メモリー配列のtest1.DATA + 1番地が2)
print test2.DATA[1]
を実行すれば12が表示されます。(メモリー配列のtest2.DATA + 1番地が12)

次のプログラムを実行すれば、1から20までの数字が表示されます。

for i = 0 to 19
 print test1.DATA[i]
next

配列式を左辺に書けば、メモリー配列の内容を変更することもできます。
test1.DATA[9] = 0
を実行すれば、メモリー配列のtest1.DATA + 9番地のデータが0に書き換わります。
     
mstruct文の書き方   先の二つのmstruct文は次のように一つのmstruct文にまとめることができます。

mstruct test {
  DATA1 db 1, 2, 3, 4, 5, 6, 7, 8, 9, 10
  DATA2 db 11, 12, 13, 14, 15, 16, 17, 18, 19, 20
}

ただし、メモリー配列中の中身は同じものになりますが、宣言される変数はtest.DATA1とtest.DATA2になります。

mstruct文のデータ定義にはdbの他にdw、ds、rb、rwが使用でき、これらを混在することができます。

mstruct test {
  BYTE_DATA db 100、200
  WORD_DATA dw 1000, 2000
  RESERVED_WORD rw 1
  RESERVED_BYTE rb 1
  STRING_DATA ds "ABC"
}
を実行すれば、次のように変数とメモリー配列が設定されます。
宣言される変数 変数の値 メモリー配列のアドレス メモリー配列の中身
test.BYTE_DATA 0 0番地 64 C8
test.WORD_DATA 2 2番地 03 E8 07 D0
test.RESERVE_WORD 6番地 00 00
test.RESERVED_BYTE 8番地 00  
test.STRING_DATA  9 9番地 41 42 43 00

     
mstruct文で宣言した変数の値は変えない   mstruct文で宣言と同時に初期化されたtest.STRING_DATAのような変数は、単純に宣言した変数と変わりがありません。
var test.STRING_DATA
test.STRING_DATA = 9
と同じです。
しかしながら、mstruct文で宣言した変数の値は変更しない方がよいでしょう。
たとえば、次のプログラムは"ABC"を表示しますが、これは悪い例です。test.STRING_DATAの最初の値がわからなくなってしまいます。

mstruct test {
  BYTE_DATA db 100、200
  WORD_DATA dw 1000, 2000
  RESERVED_WORD rw 1
  RESERVED_BYTE rb 1
  STRING_DATA ds "ABC"
}
while (test.STRING_DATA[0] != 0)
  print chr$(test.STRING_DATA[0]);
  test.STRING_DATA = test.STRING_DATA + 1
wend

後半部分は次のようにします。
var i
i = 0
while (test.STRING_DATA[0] != 0)
  print chr$(test.STRING_DATA[i]);
  i = i + 1
wend

C言語のポインターのような記述をしたい場合でも、別の変数に代入してから変更しましょう。

var p
p = test.STRING_DATA
while (p[0] != 0)
  print chr$(p[0]);
  p = p + 1
wend
     
よくやる間違い   次のプログラムを実行すると、0、1、2を表示します。10、20、30とは表示されません。

mstruct data {
  A db 10
  B db 20
  C db 30
}
print data.A
print data.B
print data.C

mstruct文で宣言した変数、メモリー配列のアドレスを保持しています。つまり、ポインターです。if (data.A == 10)が真になることはありません。if (data.A[0] == 10)ならば真になります。
  
   
文字列操作はメモリー配列操作で実現する   ORANGE-lightには、文字列変数はありません。
先の例でメモリー配列中の文字列を表示していますが、

mprint test.STRING_DATA

で同様のことができます。
この他にも、メモリー配列中のアドレスを保持している変数を渡せば、強力な文字列操作ができるような関数が用意されています。

mstrcmp関数、mstrlen関数、mstrcpy関数、mstrcat関数、mstrchr関数、mstrrchr関数、mstrf関数、mmove関数、mset関数が利用できます。
     
文字列操作における儚くも最強の関数   ORANGE-lightの文字列定数が使用できる場面は限られています。たとえば、print文の引数やmstrcut文の中で使用できます。
mtrcmp関数は、メモリー配列の中の文字列を比較する関数です。引数にはメモリー配列のアドレスを指定します。ある文字列が"true"と等しいかどうか判断するには、次のように、あらかじめメモリー配列中に"true"という文字列を用意しておかなければなりませんでした。

mstruct data {
  STR rb 128
  TRUE ds "true"
}
if (mstrcmp(data.STR, data.TRUE) == 0) {
  print "true"
}

これは多少面倒なので、mstrf関数を利用して次のように書けます。

if (mstrcmp(data.STR, mstrf("true")) == 0) {
  print "true"
} else if (mstrcmp(data.STR, mstrf("false")) == 0) {
  print "false"
}

mstrf関数の1番目の引数は文字列定数で、指定された文字列をメモリー配列中に確保し、そのアドレスを返します。
メモリー配列のどこに確保しますかというと、常にgetmptr関数の返す位置に確保します。しかも、確保した後もメモリーポインターの値を変えません。
つまり、mstrf関数を使用するたびにメモリー配列中の最後の中身が入れ替わってしまいます。ですが、mstrcmpのように一時的に文字列比較したい場合には何の問題もありません。

mstrf関数はC言語のsprintf関数相当です。引数の数は可変個です。上記のように1番目の引数だけ使用するのは特殊な使い方です。普通には、次のように使えば、強力な文字列編集ができます。

mprint mstrf("%4d/%2d/%2d %2d:%2:%2d", y, m ,d, h, m, s)
     
多次元配列   メモリー配列には1次元配列の形で記述してアクセスしますが、多次元配列を使用したいときは、アクセス用の関数を作成することで代用できます。

10×20の2次元配列の例を示します。

const N = 10
const M = 20
mstruct array {
  A rb N * M
}

これで、10×20(バイト)の配列を確保します。
この配列をアクセスする関数は次のようになります。

def set(p, x, y, v) {
  p[y * M + x] = v
}

def get(x, y) {
  return p[y * M + x]
}

アクセルするときは、次のように書きます。

for y = 0 to N
  for x = 0 to M
    print get(array.A, x, y)
  next
next

すべての要素を同じ値で初期化したいときは、2重ループの中でset関数を呼んでもいいですが、mset関数を使えば一発で初期化できます。

mset(array.A, 0, N * M)
     
メモリー配列のワードアクセス   メモリー配列へのアクセスはバイト単位のアクセスが基本ですが、変数名が".w"で終わっている場合はワードアクセスになります。

mstruct test {
  BYTE_DATA db 100、200
  WORD_DATA dw 1000, 2000
  RESERVED_WORD rw 1
  RESERVED_BYTE rb 1
  STRING_DATA ds "ABC"
}

print test.WORD_DATA[0]

ではバイトアクセスになって3が表示されてしまいますが、

var p.w
p.w = test.WORD_DATA
print p.w[0]

のように".w"で終わる名前の変数にコピーすれば、ワードアクセスになって1000が表示されます。
あるいは、mstruct文での変数をWORD_DATA.wなどと ".w"で終わる名前にしておいて(こちらが普通の方法)、

print test.WOPD_DATA.w[0]

を実行すれば1000が表示されます。

また、alias文で別名を定義すれば、変数のコピーをしなくてもワードアクセスできます。

alias p.w, test.WORD_DATA
print pw[0]
   
ファイルアクセス関連   fopen関数、fclose関数、fread関数、fwrite関数、fstat関数、fseek関数、remove関数、rename関数、mkdir関数、dir関数が利用できます。

これらの関数に完全パス名を渡すときは、ドライブ名に注意してください。ドライブ名は小文字で"f:"か"s:"のいずれかです。OSソースのgetFullPath関数などを参照してください。
     
実数演算   ORANGE-lightの変数は2バイトの整数型変数のみで、文字列変数や実数型変数はありません。実数演算は文字列操作と同様に、メモリー配列を利用することによって実現します。

まず、メモリー配列の中に実数を割り当てます。ただし、整数や文字列のように確保と同時に初期化する命令はありませんので、確保のみを行います。扱える実数は単精度浮動小数点数なので、一つにつき4バイト分を確保します。次の例では、3個の浮動小数点数を確保しています。

mstruct float {
  V1 rb 4
  V2 rb 4
  V3 rb 4
}

メモリー配列中のアドレスを保持している変数(float.V1~float.V3)を実数演算用関数に渡せば、実数演算が可能になります。しかし、その関数は言語仕様では規定されていません。

ORANGE-ESPer用のORANGE-insideでは、仮想マシン内に実数演算用の関数が用意されていますので、それらを利用できるようにextern文で宣言しておく必要があります。(extern文自体はユーザーがCで記述した関数を呼び出す機能でVer1からあります。)

実数演算を行うプログラムを書くときは、最初に以下の宣言を記述しておきます。(これらの関数はORANGE-insideのバージョンアップによって増える可能性がありますので、最新版リファレンスマニュアルを参照してください。)

extern fmov, 200 // 代入
extern fstr, 201 // 文字列変換
extern ffloor, 202 // 切り捨て
extern fceil, 203 // 切り上げ
extern fadd, 204 // 加算
extern fsub, 205 // 減算
extern fmul, 206 // 乗算
extern fdiv, 207 // 除算
extern fsin, 208 // 正弦
extern fcos, 209 // 余弦
extern ftan, 210 // 正接
extern fcmp, 211 // 比較
extern fsqrt, 212 // 平方根
extern fexp, 213 // 指数
extern flog, 214 // 自然対数
extern flog10, 215 // 常用対数

実数の代入は、文字列で指定して行います。

fmov(float.V1, mstrf("10.0")) // float.V1の指すメモリー配列に10.0を代入
fmov(float.V2, mstrf("0.01")) // float.V2の指すメモリー配列に0.1を代入

実数は、小数点形式または指数形式で指定した文字列を指定します。
fmov関数は、指定した文字列を単精度浮動小数点数に変換してメモリー配列に格納します。単精度浮動小数点数のサイズは4バイトで、絶対値の最大値は3.40283e+38で、最小値は1.175494e-38になります。

足し算を行うときは、fadd関数で次のように記述します。

fadd(float.V3, float.V1, float.V2) // float.V3の指すメモリー配列に加算結果を格納

加算結果も単精度浮動小数点数で、バイト単位で参照しても実際の値がわかりづらいので、次のように文字列に変換すれば表示もできます。

mstruct disp {
  RESULT rb 20
}
fstr(disp.STR, float.V3) // float.V3に格納されている浮動小数点数を文字列に変換
mprint disp.RESULT
     
WiFi関連   WiFi関連の関数も多数あり、ORANGE-insideのバージョンアップによって増える可能性がありますので、extern文で取り込む形になります。

extern wifiBegin, 2
extern wifiStatus, 3
extern wifiLocalIP, 4
extern wifiServerNew, 7
extern wifiClientNew, 8
extern wifiServerBegin, 9
extern wifiServerHasClient, 10
extern wifiServerAvailable, 11
extern wifiClientConnected, 12
extern wifiClientAvailable, 13
extern wifiClientRead, 14
extern wifiClientStop, 15
extern wifiClientPrintln, 16
extern wifiClientPrint, 17
extern wifiClientWrite, 18
extern wifiClientReadBytes, 19
extern wifiClean, 23
extern yield, 100
extern fstatG, 103
     
環境変数   環境変数を扱うために、setenv文とgetenv文を追加しました。setenv文は指定した「キーと値」のペアを仮想マシン内のハッシュマップに保存します。また、getenv文は指定した「キー」にマッチした「値」をメモリー配列に転送します。

mstruct work {
  VALUE1 ds "abc"
  VALUE2 rb 4
}
setenv "key", work.VALUE1 // 仮想マシンに保持
getenv "key", work.VALUE2 // 仮想マシンから取得

このプログラムを実行すると、単にwork.VALUE1の値がwork.VALUE2に転送されるだけです。

setenv文とgetenv文は、主にORANGE-OS内でプログラム連携をするために使用します。
まずは、ORANGE-OSのプロセス起動について説明します。
ORANGE-OSでは一つのプロセスしか動作しません。仮想マシン内のメモリーには一つのプログラムのみが専有されます。

リセット後、仮想マシンはboot.exeを起動します。
boot.exeは初回起動時は画面クリアしタイトルを表示した後に、setup.exeを起動します。
具体的にはchain命令によって、setup.exeをロードします。このときにはメモリー内のboot.exeはsetup.exeに入れ替わります。
setup.exeはWi-Fiの接続などを実行した後に終了します。仮想マシンは再びboot.exeを起動しますが、2回目の起動なので画面クリアやsetup.exeの起動を行いません。プロンプトを出して入力待ちになります。

コマンドラインから入力されたアプリケーション(たとえば、dir.exe)を実行するときも、同様の入れ替わりが発生し、仮想マシンのメモリー内の様子は次のようになります。
setup.exe   dir.exe  
boot.exe
boot.exe boot.exe

この一連の流れの中で、「2回目の起動」とか「次に何を起動するの?」とかの情報は環境変数に設定して制御しています。
ORANGE-OS内では、次のような環境変数を使用しています。
キー
RESTART  2回目以降の起動時は"true"
SD SDカードマウント時は"true"
DRIVE カレントドライブ、"f:"または"s:"
PATH_F ドライブf:のカレントパス
PATH_S ドライブs:のカレントパス
NEXT 次に起動するプログラムのフルパス(未設定時はf:/boot.exeが起動)
NEXTでで設定したプログラムが起動した後には、NEXTの値は仮想マシン側でクリアします。