Edited at

【追記あり】使いやすいけど使いづらいプログラミング言語「HSP」について

More than 1 year has passed since last update.

※姉妹記事:HSPで本格的にプログラムを書きたい人向けのtips

※当記事は追記が施されました。具体的には、vain0さんの指摘を元に、各所を修正しました。


概要

 私が初めてHSP(Hot Soup Processor)に触れたのは中学生の頃でした。当時はまだVer.2.Xでしたが、それがVer.3.Xになって浮動小数点も扱えるようになり、様々な関数・命令も追加されたことで格段に使いやすくなりました。その手軽さから初心者向け言語として人気で、毎年夏休みにはゲームプログラミングの講習会を開くなど、普及にも力を入れている印象があります。ただ、前から思っていたことがあります。それは、

  この言語、使いやすいはずなのに使いづらいんだよ!!!

 一体どういう意味なのか、それについて説明します。


エディタについて

 HSPの場合、「HSPスクリプトエディタ」(タイトルバーだと何故か全角英字なのが気持ち悪い)を使ってコードを書き、実行します。厳密には他のエディタを使えないこともないのですが、あまり調べない人はこのエディタをそのまま使ってプログラムを書くことになるでしょうね。

 ただこのエディタが曲者で、矩形選択できない・grepできない・ファイルタブを入れ替えられない・関数や処理ブロックなどを折りたたみ表示できない・未だに未実装な部分が多いという有様です。真っ当なテキストエディタを一度でも使ったことがある人ならば、二度とこれでプログラムを書きたくなくなるのではないでしょうか?

 もっとも、HSPについてのヘルプはhdl.exe(HSP Document Library)という実行ファイルを起動して使えるのですが、このエディタでプログラムを書くと、調べたいワードにカーソルを合わせてF1キーを押すだけで自動的にhdl.exeが起動して、そのワードの意味が分かります。なので、覚えてない関数・命令が多すぎる初心者にとっては手放せない存在だったりします。……まあマクロを組めば他のエディタでも使えますが。

 また、HSPで実行する際には○○、このエディタを使えばF5キーを押すだけでそのまま実行できます。マクロで実行処理を行えるエディタを使っている場合ならともかく、そうでない場合は「他のエディタで書いてから、HSPスクリプトエディタで実行」するか、「他のエディタ上でマクロを定義して実行したい際に使用する」といった手間が掛かることになります。HSPスクリプトエディタを使わずにコンパイルする方法は無いこともないのですが、若干面倒臭いのは否定できませんね……。


デバッガについて

 えーと、まずはこちらの画面をご覧下さい。

デバッグ画面

 assert命令などで処理を止めた/止まっている際に表示されるデバッガですが、標準だと見ての通りなかなかに見づらいです。なにせ変数名がズラーッと出てくるので、大規模なプログラムになると変数名の羅列をシークするのが大変です。しかも、「多次元配列だと1次元分しか表示されない」「長い変数名だと左側のリストからはみ出て見づらい」「ソートとモジュールの両方にチェックを入れるとソートが優先させて余計にごちゃごちゃする」など、控えめに言って手抜き、極論すれば作者の悪意を感じるダメダメ仕様になってしまっています。作者はHSPでプログラミングする人にデバッグしてほしくないのでしょうか?

 ただ、HSPには一応オープンに公開されているソースコードが存在し、そこからこのデバッガのソースを読むことが出来ます。これを改造してコンパイルし直せばオリジナルなデバッガを生み出せるわけです(例:knowbug)。DLLを差し替えてくださいとかどう考えても「初心者向け」じゃないけどな!


コンパイラについて



  • 実行ファイルのサイズがデカい……HSPはその仕様上、ソースコードを中間コード(*.axファイル)に変換してランタイム上で実行されます。そしてランタイム部分は生成したexeファイルにくっついてくるので、何も書かない空っぽなコードでも実行ファイルのサイズが200KBを超えます。……でもまあ、ライブラリをstaticに含ませるかdynamicに呼ばせるかで実行ファイルのサイズが相当変わるものなので、まだ瑣末な問題ですね。


  • それなのに基本的にWindows上でしか動かない……一応Mac版Linux版DS版が無いわけではありませんし、Javaに変換するとかVC++に変換するとかスマホで動くように変換するとか互換性改善のために尽力してくださっているのは認めます。ただ、使える関数・命令の成約が厳しかったり、WinAPIを直叩きしているようなコードは移植できなかったりするので、移植する際には往年のBASIC以上の方言に苦労することになります。でもまあ、動かすOSを限定すればまだ重大な問題ではありません。


  • ただでさえ遅い上にロクに最適化しない……HSPは中間コードを解釈して動作するインタープリタ言語ですので、素の速度は遅いです。ライブラリ関数・命令はDLL(=C++)で動作するのが大半なので速いのですが、複雑なロジックだとスクラッチせざるを得ないことも多いので、この欠点が重くのしかかってきます。また、中間コードに翻訳される過程でコードとしては殆ど最適化されていない(参考1参考2)のもポイントです。


  • あまりにも逆コンパイルされやすい……大きな声では言えませんが、前述の中間コード(*.axファイル)および実行ファイル(*.exeファイル)は割と簡単に逆コンパイルできます。axファイルの仕様は公開されているのでそっちが逆コンパイルできるのはまだいいのですが、暗号化しているはずの実行ファイルの方も簡単に割られるのはどうかと思います。有志による難読化ツールがありますので、不安な方はそちらを利用しましょう。


文法について

 話が長くなるのでカテゴリ毎に分けました。逆に言えばそれだけ突っ込みどころが多い言語なわけです。


(ほぼ)全部の変数がグローバル変数である・変数宣言が(ほぼ)不要・gotoやgosubでラベルにジャンプできる

 この辺は往年のBASICを彷彿とさせる仕様です。まあBASICライクで手軽な言語を目指したのがHSPなので仕方ないと言えなくもありませんが、前述のようにデバッガにズラッと変数名が並んで見づらいとかが起きるのもこのせいです。gotoはなるべく使わないようにすれば対処できますが、グローバル変数祭りは後述のシステム変数にも絡んでくるので回避不可能なのが辛いところです。


暗黙の型変換と自動拡張のせいでコードが読みづらい

 暗黙の型変換は他言語でも普通にありますが、HSPのそれはなかなかにトリッキーです。例として、この紹介ページのものを見てみましょう。


sample1.hsp

mes 1+"1"  ;2

mes "1"+1 ;11
mes 1+"1A" ;2
mes 1+"A1" ;1
mes 1+"1" ;1

 HSPのマニュアルには、暗黙の型変換をする際は「計算する最初の項(左結合なので左側)に合わせて変換される」といったことが書いてあります。C言語などと違い、8+4.5が12.5ではなく12になるわけです。ただ、上記のような結果になるのは次のような追加規則が働いています(マニュアルには未掲載)。


  • 文字列を数値化した場合は「数字から始まる連続部分」のみ評価する。つまり、整数にすると"12A"は12、"1A2"は1、"A12"は0になります。

  • また、数値化出来る文字列数字は半角のみです。よって、"1"は0になります。

 もちろん、int・double・str関数を適切に使用すれば暗黙の型変換をせずに済みますが、暗黙の型変換利用時と比べて冗長になりがちなのでケースバイケースですね。

 また、HSPには変数の自動拡張機能があります。具体的には、「a(2) = 5」などと代入文を宣言した場合、自動で「a(0)~a(2)」までの配列変数が確保されます。また、文字列についても+演算子で足し続ければ延々とサイズを大きくすることが可能です(ただし遅い)。楽ちんと言いたいところですが、変数および配列の型が分かりづらくなるのは果たして初心者向けと言えるのでしょうか……?


特殊な変数(システム変数)が多すぎる

「システム変数はシステム起動時、または特定の命令を実行した時に自動的に 値が代入される変数です。」(原文ママ)

 こういった変数が恐ろしいことに60個近く存在します。当然グローバル変数な上、(ほぼ)代入できないので同名の変数も作れません。頻繁に利用するのはcnt・stat・strsize・refstr辺りですが、これらは1つ関数・命令を処理するだけでコロコロ変わるので、値を後々利用したい際には他の変数にコピーする必要があります。「覚えればいい」だけの話なのですが、「覚えなくていい(意図しない副作用が少ない)」他言語と比べると隔世の感があります。


if文内を含む論理演算を短絡評価してくれない

 マニュアルに載ってない初見殺しです。HSPのif文内で使う「&」「|」は単なるビット演算なので、式全体を読み取らないと真偽が判断できません(C言語などのように、&&や||と区別されているわけではない)。つまり、次のようなコードはエラーになります。


sample2_1.hsp

dim a, 3

a(0) = 1 :a(1) = 2: a(2) = 3
flg = 0
if flg & a(4) > 0{ ;エラー!
mes "a"
}

「エラーになるような添字にアクセスするようなif文なんて書かない」と思うかもしれませんが、短絡評価できる言語では「if(A && B)」と書けばAが偽ならBの処理はそもそも実行されませんので負荷軽減に繋がります。なので、HSPの場合は強制的に短絡評価させるためにネストを深くする必要があったりするのです(実体験)。


sample2_2.hsp

dim a, 3

a(0) = 1 :a(1) = 2: a(2) = 3
flg = 0
if flg{
if a(4) > 0{
mes "a"
}
}


関数・命令の引数の扱いがややこしい

 例えば、instrという関数があります。やっていることは文字列の検索です。使用例はこちらをご覧ください。


sample3.hsp

string = "ABCDE"

mes instr(string,,"A") ;0
mes instr(string,,"E") ;4
mes instr(string,,"F") ;-1
mes instr(string,1,"D") ;2
mes instr("FGH",,"G") ;エラー「変数名が指定されていません」

 ……ん? なんですかこのエラーは?

 実は、HSPの関数・命令では、引数における変数と値とは明確に区別されます。そして、変数指定のところに値を書くとエラーになります。これだけだと普通のこと(C言語だって引数にポインタを使って変数を強制可能)なのですが、HSPの場合は妙な部分に変数指定があるから面倒だったりします。それが顕著なのは文字列操作関数(instr・strmid・strtrimなど)ですが、全関数・命令を触ったことがあるわけではないので他にもややこしい箇所があるかもしれません。


関数・命令を実装する際の文法がややこしい

 HSPでは、モジュールと呼ばれる機能を使うことで関数・命令を独自に定義できます。とは言っても、他の言語のようにガンガン定義して使えるかというとそうでもないのがややこしいところです。この辺は非常にややこしいので、次のコードを読んで下さい。


sample4.hsp

// モジュールを定義する(testはモジュール名)

#module test
// xと(グローバル変数の)bを足してそれを表示する(命令)
#deffunc mes_ab int x
c = x + b@ ;ちなみに、こことその次の行のcをaにすると
mes c ;エラー「パラメータ引数名は使用されています」が出る
return
// aを2乗してその値を返す(関数)
#defcfunc square int a
b = a * a
return b
#global

b = 3
mes b ;3 言うまでもありませんね
mes square(5) ;25 これも問題ないはずです
mes b ;3 モジュール内の変数とは区別されます
mes b@test ;25 「変数名@モジュール名」とすれはアクセス可能
mes_ab 5 ;8 「変数名@」とすればグローバル変数側も参照可能


 要するに、全ての変数および関数が強制的にstaticかつpublicであり、インスタンス作成不可で、しかも引数名とメンバ変数の名前を被らせてはならないクラスです。ぶっちゃけ、恐ろしく使いづらいです。そもそも#deffuncと#defcfuncはモジュールの外でも定義できますので、モジュールはクラスというより名前空間と呼ぶのが適切でしょう(公式ヘルプにも似たような記述あり)。

 一応、再帰関数でローカル変数をどうしても使いたい人用に「local」というキーワードがあり、それを指定したメンバ変数は関数実行の度に毎回破棄されるので真っ当な再帰コードが書けます……が、配列はlocal指定できないので自前でスタックを積むしかありません。こんな仕様の割を食う処理の一例としてはDFSに代表される探索処理があるでしょう。

2017/01/13追記:

打ち消し線を引いた箇所を、以下の通り訂正します。


  • 「local」キーワードは、メンバ変数ではなく仮引数に指定するものです。

  • また、「関数実行の度に毎回破棄」ではなく「関数実行の度に確保」が正しいです。

  • 更に、配列もlocal指定できます。

2017/01/16追記:

 命令・関数を定義する際、「#deffunc local init」といった風にlocalキーワードを挟めば、同名の命令・関数を別々のモジュールで定義できるようです。ただしその際は、init@GUIinit@DBといった風に、必ず@モジュール名を付加する必要があるようです。また、変数と違い、たとえ当該モジュール内であっても@モジュール名を省略できないとのこと。


その他


  • 変数名の大文字と小文字が区別されない(デバッガでは全部小文字になる)

  • 構造体がないので、外部と連携する際に構造体データを投げるのが面倒(例:dim配列に数値を入れて先頭ポインタを渡す)

  • 配列が四次元までしか定義できない上、メモリの並び方がCとかと真逆

  • repeat~loop内からreturn文で脱出すると、ネストが積まれたままになる

  • 手続き型言語なのに、buttonやonclickなどのイベントドリブン要素があってややこしい

 主にこんなところでしょうか。エディタやデバッガやコンパイラが改善されたとしても、文法からしてトリッキーなのがHSPですので、本質的な書きづらさはなかなか解消されないでしょう。


まとめ

「プログラミング言語は、易しいとか難しいとかだろ! コレ、辛いんだよ!」

 ……で済むことですが少し補足を。

 ここまで書いておいてなんですが、HSPは(ちょっとしたツールを作る際には)書きやすい言語です。ライブラリは豊富な方なので、「Webカメラの画像を読み込んで画像認識して顔部分だけを検出してボカした後にウィンドウにリアルタイム表示するソフト」などと、一見大変そうな処理も結構アッサリと書くことができます。なぜかDLLを直接叩けますので、前述のようにWinAPIをガンガン使ったソフトサードパーティDLLを使用するソフトなんかも気軽に作成できます。要するにグルー言語として使う分には都合がいいということです。

 ただ、その枠を離れて大規模なソフトウェアを組もうとすると途端に辛くなります。もちろん設計理念からして大規模なコード向けじゃないのは既に触れましたが、せめてモジュール機能はなんとならなかったのかとは思いますね。きちんとスタック管理できて、配列もlocal指定できて、謎の変数名成約が無ければガンガン関数・命令を作成して殆どの処理をそっちに委ねる(グローバル空間にはmain関数程度の処理しか書かない)という選択肢もあったのですが。

HSPで本格的にプログラムを書きたい人向けのtips