2.01.文法
基本的な文法を以下に示します。
(各種記号は基本的に半角です)
(1)区切り記号
半角スペース,タブ,改行が、区切り記号になります。
また、文法上の区切り記号として 命令の終端を意味するセミコロン(;)があります。
(x=60; y=sin(x); text(y,0,0,0); 等)
セミコロンは多くの場合省略可能です。しかし、以下のケースでは省略できません。
(a)returnの戻り値を省略する場合
(b)&&か||の演算子を使った式が()でくくられていない場合
(c)互換モード(spmode(1))で 3項演算子(?:)を使う場合、末尾にセミコロンが必要
(d)ポインタ的なもの(*)が文頭に来る場合、その前にセミコロンが必要
(e)forの()の中のセミコロンは省略不可
(2)コメント
シングルクォート(')でくくった部分はコメントとなり、実行時に無視されます。
('abcde' 等)
また、// を書いた行は、行末までコメントとなり、その部分は 実行時に無視されます。
(//abcde 等)
(オリジナルのSPALMには // はありません)
(3)名前
名前は、半角アルファベットまたはアンダースコア(_)で始まり、
2文字目以降は 半角アルファベット,アンダースコアまたは半角数字から 構成されます。
(abcde _abc abc123 等)
半角アルファベットの大文字小文字は区別されます(ABCとabcは別の名前と判断されます)。
名前は、関数名,変数名,定数名,ラベル名,制御命令名,モジュール名 になります。
組み込みの関数,変数,定数,制御命令と同じ名前は、
ユーザ定義の関数名,変数名,ラベル名としては使えません。
(オリジナルのSPALMではアンダースコア(_)は名前に使えません)
(ラベル名だけは、例外的に半角数字のみのものも許されます)
(4)数値
半角数字とドット(.)(小数点)を使って 数値を表します(1.2345 等)。
また、先頭に0xをつけることで16進数表記(0-9,A-F)で数値を記述できます。
(0x12AB 等) (16進数表記は整数のみです)
また、数値の後にさらに e と数値を付加して指数表記が可能です。
(1.2345e10 1e-1 等) (v8.03から) (16進数表記に指数表記は使えません)
(オリジナルのSPALMでは数値は整数のみです)
(オリジナルのSPALMには数値の指数表記はありません)
(5)文字列
ダブルクォート(")でくくることで 文字列を表します("abcde" 等)。
文字列中でダブルクォート自身を表すときは \ 記号でエスケープして \" と記述します。
また、文字列中で \ 記号自身を表すときは \\ と記述します。
また、複数の文字列を結合する場合には、ドット(.)を使います。
(例えば、s="aaa"."bbb" とすると s="aaabbb" となります)
(6)式と演算子
y=1+2 というように 式を記述できます。
以下の演算子が使用可能です。
レベル1: () ! ~ + - ++ --
レベル2: * / % \
レベル3: + . -
レベル4: << < <= >>> >> > >= == !=
レベル5: & && | || ^ ?:
レベル6: , = += -= *= /= %= \= .= &= |= ^= <<= >>>= >>=
()で計算の順番を明示しなければ、レベルの小さい演算子が優先して計算されます。
(オリジナルのSPALMには \ >>> += -= *= /= %= \= .= &= |= ^= <<=
>>>= >>= はありません)
(互換モード(spmode(1))では、3項演算子(?:)の末尾にセミコロン(;)が必要です)
各演算子の詳細については、「2.2.1.演算子の詳細」を参照ください。
(7)関数
名前の後に()をつけると 関数を表します。
関数は、関数ごとに定められた処理を実行します。
()内には関数の引数を カンマ(,)で区切って記述します(引数のない関数もあります)。
(例. cls() col(0xFF0000) text("Hello",0,0,0) 等)
関数には 組み込みの関数とユーザ定義の関数が存在します。
組み込みの関数はさらに、値を返す関数と値を返さない関数に分かれます。
値を返さない(戻り値がない)関数は、式の中では使えません。
ユーザ定義の関数は、func命令を使って定義します。
例えば 値を2倍にして返すユーザ定義の関数を定義する場合、
func f(x) { return 2*x }
のように記述します。そして y=f(2) として呼び出せば、y=4 という計算結果を得ます。
(8)変数
未使用の名前を使って 変数を作成できます。
変数には、数値や文字列を代入できます。また、演算子による計算が行えます。
(x=100 s="abcde" y=x+1 等)
変数は、特に宣言せずに式の中で使えます(後述のローカル変数を除く)。
代入しなければ初期値は0になります。
また、組み込みの変数があり、これには ユーザが値を代入することはできません。
(width height 等)
また、画像を格納するImage変数があり、
これはグラフィックス系の組み込み関数(loadimg() drawimg() 等)でのみ使用可能です。
また、*と&を変数の前につけると、ポインタとアドレス的な使い方ができます。
例えば、a=&hoge ;*a=5 とすると hoge=5 の意味になります。
(詳細は(12)を参照)
また、変数に[ ]をつけると、配列変数になります。
a[0] a["abc"] a[i] a[i+1] のように
[ ]の中に式を書いて 配列の要素を指定できます。
また、a[0][0] のように 多次元の配列を使うこともできます。
また、@(配列変数名,値1,値2,値3, ...) と書くことで、
配列変数の要素0~ に値を一括代入できます。
(例えば @(arr,100,200,300) を実行すると、
arr[0]=100 arr[1]=200 arr[2]=300 となります)
変数には、グローバル変数とローカル変数の区別があります。
グローバル変数は、プログラム内のどこからでも利用可能な変数です。
一方、ローカル変数は、ユーザ定義の関数内でのみ利用可能な変数です(関数を抜けると消滅します)。
ローカル変数を使用するには、ユーザ定義の関数内で、locまたはlocalをつけて
変数を宣言する必要があります。
グローバル変数については、特に宣言は不要ですが、glbまたはglobalをつけて
明示的に宣言することも可能です。
(local x loc x global x glb x 等)
また、組み込みの変数とImage変数は、常にグローバル変数になります。
(オリジナルのSPALMにはローカル変数はありません。すべてグローバル変数になります)
(onlocal(),offlocal()によってローカル変数の有効/無効を切り換えることができます。
また 互換モード(spmode(1))にした場合も ローカル変数は無効になります)
(v6.00から、ローカル変数はloc(またはlocal)による宣言が必要になりました)
ローカル変数の注意点1:
例えば、
func f(x) { y=100*x return y }
というユーザ定義の関数 f() があった場合に、y はグローバル変数として解釈されます。
グローバル変数の変更は予期しない不具合を起こしやすいため、
もし f() 内の y が一時的な計算用の変数ならば、
func f(x) { loc y=100*x return y }
のように明示的にloc(またはlocal)をつけて y をローカル変数にしてください。
(この場合、先頭で1回locをつけておけば、その後は、同一関数内では常に
ローカル変数として解釈されます)
ローカル変数の注意点2:
例えば、loc y とすると、同名の配列変数(y[0] y[1] y[1][2] 等)も
すべてローカル変数になります。
また、loc y[0] とすると、変数 y および同名の配列変数(y[1] y[1][2] 等)も
すべてローカル変数になります。
(v6.00から)
ローカル変数の注意点3:
v3.38から、ユーザ定義関数の仮引数は、デフォルトでローカル変数になりました。
例えば、
func f(x) { return 2*x }
における仮引数 x は、(loc(またはlocal)をつけなくても) ローカル変数になります。
(ただし、
func f(glb x) { return 2*x }
のように明示的にglb(またはglobal)をつけてグローバル変数にした場合や、
offlocal()やspmode(1)によってローカル変数を無効にした場合には、
x はグローバル変数になります)
変数の一括宣言:
v3.70から、変数の一括宣言が可能になりました。
loc,local,glb,global のいずれかの後に、変数名をカンマ区切りで並べて記述できます。
例えば、
loc a,b,c
のように記述すると、ローカル変数a,b,cが作成されます。
また、例えば、
loc a=1,b=2,c=3
のように記述すると、ローカル変数a,b,cが作成されて、a=1,b=2,c=3で初期化されます。
(変数の一括宣言は、式の中では使えません (例えば y=loc a,b,c 等は不可))
(9)定数
いくつかの組み込みの定数が存在します(HCENTER key1 green 等)。
定数は 実行時に値に置換されます。
v3.34から定数の定義/削除が行えるようになりました。
defconst()によって新しい定数を定義できます。
disconst()によって定数を削除できます。
(オリジナルのSPALMにはこれらの命令は存在しません)
(10)ラベル
ラベルは、
label ラベル名
によって定義します。
ラベルは、gotoかgosubのジャンプ先になります。
ラベル名には、半角数字のみのものも指定できます。
また、ラベル名は、ダブルクォートでくくったものも認識されます。
(過去との互換性維持のため)
(11)制御命令
以下の制御命令が存在します。そして それぞれの構文を持ちます。
switch, if, for, while, do, break, continue, end,
label, goto, gosub, func, return, module
例.
for (i=0;i<10;i++) {
text( i , width/2 , sthigh*row++ , 1 )
}
(オリジナルのSPALMには continue と module はありません)
(オリジナルのSPALMにある try ~ catch には未対応です(エラーになります))
(オリジナルのSPALMにある 分割ロード(#ソース#) には未対応です(エラーになります))
(オリジナルのSPALMにある goto,gosubのジャンプ先に計算式を指定する機能には非対応です(v6.00から))
(現状の SPALM Web インタープリターには、
「ループのプログラムで固まる」という制限事項があります。
(アプリケーション説明の「1.3.制限事項」参照))
各制御命令の詳細については、「2.3.1.制御命令の詳細」を参照ください。
(12)ポインタとアドレス的なもの
*と&を変数の前につけると、ポインタとアドレス的な使い方ができます。
(オリジナルのSPALMには存在しません)
ポインタとは、他の変数を指す変数のことです。
ポインタには、他の変数のアドレス(変数の前に&をつけて取得)を代入します。
そしてポインタの前に*をつけることで、ポインタの指す変数の実体にアクセスします。
例えば、a=&hoge ;*a=5 とすると hoge=5 の意味になります。
注意点として 文頭の*の前にはセミコロン(;)が必要です。
(乗算の*と区別するため。これを忘れると 相当分かりにくい不具合のもとになります)
(ただし、行頭の*の前にはセミコロン(;)が自動挿入されるため、
行頭に関してだけはセミコロン(;)が不要です(v11.00から))
本機能を利用して、例えばユーザ定義の関数で 複数の戻り値を返すことができます。
例1.
func f(a,b,*ret1,*ret2) {
*ret1=a+b
*ret2=a-b
}
f(1,2,&r1,&r2)
この例を実行すると、
r1=3
r2=-1
となります。(複数の戻り値を返す例です)
また、配列変数を返すこともできます。
例2.
func f(a,b,*arr) {
*(arr)[0]=a+b
*(arr)[1]=a-b
}
f(1,2,&r)
この例を実行すると、
r[0]=3
r[1]=-1
となります。(配列変数を返す例です)
(上記例の *(arr)[0] は、arrが指す変数に[0]を付けて配列化するという意味です。
括弧の位置に注意してください。
(これを単に *arr[0] と書くと、arr[0] が指す変数という異なった意味になります))
(13)関数ポインタ的なもの
(12)と似た記述で、関数ポインタ的なものを使うことができます。
すなわち、関数のアドレスを変数に代入して、それを呼び出すことができます。
(オリジナルのSPALMには存在しません)
(注意点として、現状、組み込みの関数をポインタ経由で呼び出すことはできません)
例1.
func f(x) { return x*2 }
g=&f
y=*g(2)
この例を実行すると、
y=4
となります。
例2.
func gm() { return "おはよう" }
func hl() { return "こんにちは" }
func ge() { return "こんばんは" }
@(arr,&gm,&hl,&ge)
sh=sthigh; row=0
for (i=0;i<3;i++) {
text( *arr[i]() , 0 , sh*row++ , 0 )
}
この例を実行すると、
おはよう
こんにちは
こんばんは
と表示されます。(配列変数に格納した関数を、順番に実行する例です)
(14)構造体的なもの
配列変数の[ ]内には文字列を指定することができます。
これを利用して、構造体的なものを記述できます。
すなわち、内部に複数のデータを持つようなデータ構造を表現できます。
また、(12)を利用して、構造体的なものを使用する関数を定義できます。
例.
// 構造体のひな型
// Person["name"]="NoName"
// Person["age"]=0
// 構造体を初期化する関数
func Person_init(*p,name,age) {
*(p)["name"]=name
*(p)["age"]=age
}
// 構造体を使用する関数
func Person_info(*p) {
return *(p)["name"]." is ".*(p)["age"]." years old."
}
// 構造体を初期化して使用する
Person_init(&p1,"John",19)
Person_init(&p2,"Mike",20)
sh=sthigh; row=0
text( Person_info(&p1) , 5 , 5+sh*row++ , 0 )
text( Person_info(&p2) , 5 , 5+sh*row++ , 0 )
この例を実行すると、
John is 19 years old.
Mike is 20 years old.
と表示されます。
(15)簡易モジュール機能
module命令により、簡易モジュール機能を使用できます。
モジュールを使用すると、グローバル変数名等がバッティングするリスクを
減らすことができます。
(オリジナルのSPALMには存在しません)
(現状、モジュールを別のファイルに分離することはできません)
(本命令は、v14.00で追加され、v18.00で制御命令になりました。
記述方法が module(MZ) から module MZ { } に変わったため、
v18.00より前のバージョンとは互換性がなくなっています)
例えば、module MZ { 命令 } とすると、命令の部分は モジュール MZ に所属します。
そして、モジュール内では、グローバル変数名,ユーザ定義関数名,ラベル名 の前に
'MZ#' が自動的に付加されます。
これらの要素には、同一モジュール内であれば 'MZ#' を付けなくてもアクセス可能です。
しかし、モジュールの外部からは 'MZ#' を付けてアクセスする必要があります (MZ#x 等)。
また、逆にモジュール内から、
モジュールに所属しない グローバル変数名,ユーザ定義関数名,ラベル名 に
アクセスする場合には、先頭に # を付けてアクセスする必要があります (#x 等)。
(現状、定数名は、モジュールの影響を受けません)
例1.
module MX {
glb x=100
func f() { text("x=".x,0,0,0) }
}
module MY {
glb x=200
func f() { text("x=".x,0,20,0) }
}
MX#f()
MY#f()
この例を実行すると、
x=100
x=200
と表示されます。(モジュールによって、同名のグローバル変数xが区別されています)
モジュールは、入れ子にすることができます(v18.00から)。
例えば、module MX { module MY { 命令 } } とすると、
命令の部分の グローバル変数名,ユーザ定義関数名,ラベル名 の前には
'MX#MY#' が付加されます。
例2.
module MX {
glb x=1000
module MY {
glb x=2000
}
}
text("x=".MX#x,0,0,0)
text("x=".MX#MY#x,0,20,0)
この例を実行すると、
x=1000
x=2000
と表示されます。(入れ子にしたモジュール内で、同名のグローバル変数xが区別されています)
(2018-8-5)