プリプロセッサ(前半)
手始めにプリプロセッサ命令を解説したいと思います。
HSP2.55編でプロプロセッサの説明はしていなかったと思いますので、まず2.55編を説明。
まずは馴染み深い#define命令から行きましょう。
その前に説明しておくことがありますね。
プリプロセッサ命令というのは「コンパイルの時にソース(スクリプト)に対して行われる命令」のことで
プリプロセッサ命令には初めに「#(シャープ)」を付ける必要があります。
また、プリプロセッサ命令はスクリプトで使用したい位置より上(前)に書く必要があります。
	mes A ; この段階で変数
#define A "123" ; 「123」の別名称
	stop
このようにしてしまうと「Error during define command」とエラーが表示されることと思います。 2.6ではエラーでなくなっていますがココではまだ2.55の話です。 aを表示した段階ではまだ定義されてないので変数であるのに、 mesで表示した後に定義しているとコレは一体変数なのか何なのかわからないからですね。 では改めて#define命令を…。 書式は「#define 新規名称 旧名称」で、新規名称で書くと旧名称と同じことと解釈されます。 旧名称に指定できるものは、数値・文字列の他に命令名,変数といったものまでOKです。 数値や文字列なんか別の名称にしてどうするの、となりそうですけど結構使えます。 例えば自作ソフトの現在のバージョンの時,パラメータを変数ではダメで数値で与えなければならない時。
#define VERSION "1.00"
	title "自作ソフト ver" + VERSION
	button "about",about
	stop

*about
	dialog "VERSION" + VERSION
	stop
こういったように様々な場所に数値・文字列で書いていると修正が大変です。 …かといって変数にすると、その分メモリを消費します。たくさん使うと消費量もバカにできません。 #defineは置き換えているだけですので、定義によるメモリは消費しません。 命令を新名称に置き換えるのは何の処理をする命令か分かりやすく変更するためのものかと思います。 新規名称には半角英数だけとは決められていませんので2バイト文字も使用できます。 つまり…日本語でプログラムができるという「ひ●わり」のようにもできるということですね(笑
#define たす +
#define は =
#define 表示 mes
#define 停止 stop

	変数 は 1 たす 2 たす 3
	表示 変数
	停止
変数を別の名称に置き換えるのはどういったときでしょうか…?
	dim var, 5
#define TEMPORARY var.4 ; varの要素4は一時変数用に使用する
	TEMPORARY = 100
	mes TEMPORARY
	stop
上記のように配列変数内の一要素を別名に置き換えると何に使うものだったかを明確にできると思います。 ゲームを例にすると、配列変数の要素1は体力、2は魔力、3は敏捷性と決めているものを、 「変数.2 = 10」とするより要素2を魔力と決めておき「魔力 = 10」としている方がわかりやすいですよね? 長くなりましたけど#defineの説明は以上です。 次に#include命令の説明に入ります。 「#include "結合ファイル名"」という書式です。 結合ファイル名というのはHSPで扱う「*.as」の形をしたファイルのこと。 プラグイン(DLL)を利用する時、なぜ「*.dll」と書かないのかと思ったことはないでしょうか? それは、この#include命令でDLLを使用できるようにしているわけではないからですね。 HSP標準付属のDLLならcommonフォルダにそのDLLにほぼ同じ名前のasファイルがあると思います。 #includeはそのファイルを自分の書いたスクリプトの初め(ヘッダー)に結合(連結)しているだけです。 なので#includeを書かず、commonフォルダのasファイルを自分のスクリプト先頭に書けば同じことです。 Windows9X系ではエディタで編集できる上限(約64KB?)があり超えるとエラーが出ます。 それ以上を扱いたい時は、この#includeで分割させるというルールみたいなものがありました。 Aファイルの初めにBファイルを結合すると、スクリプトの流れはB→Aとなります。 モジュール関係を再度説明します。 「#module」と「#global」セットから。 #moduleの書式は「#module "モジュール名"」ですが、パラメータはなくても動きます。 つける場合は最大で半角20バイトまでとなっています。 #globalはもともとパラメータが存在しません。
#module "module"
  :
#global
この#moduleから#globalまでが1モジュールで他のスクリプトは別空間となります。 同じ名前の変数が存在してもモジュールが違えば全くの別物となります。 さて、独立した別の空間なんて必要なのでしょうか? それは自分で考えた命令を扱う時に必要となってくるでしょう。 自分で考えた命令とは定義するだけの#defineではなく、パラメータにより変化させられる関数です。 自分で考える新命令は#deffuncで、「#deffunc 命令名 パラメータ1,パラメータ2…」と定義します。 パラメータの最大数は8コで、数値以外は第1と第2パラメータの2つしか設定できません。 この#deffunc命令はモジュール内に書かずに使用できないことはありませんが、 命令用に使用する変数が予約語等とかぶるのを避けたり 新命令が使われてない時にこのreturnを通るとエラーとなってしまいます。 ですので、上から流れてこないようにスクリプトの書き方に気をつけなければなりません。
	goto *start ; 直接#deffunc命令を通るとエラー発生

#deffunc add val, val
	mref p1, 16  ; パラメータ1(数値型変数)
	mref p2, 17  ; パラメータ2(数値型変数)
	mref stt, 64 ; システム変数stat
	stt = p1 + p2
	return

*start
	a = 3
	b = 5
	stt = "別のことに使用されている変数"
	add a, b ; 変数1,2の値を足した数値をstatに返す命令
	mes stat
	mes stt
	stop
こんなわかりにくいことをしたり、変数が使われるよりモジュールに書くほうが楽でわかりやすいですね。
#include "calc"
#deffunc add val,val
	mref p1, 16  ; パラメータ1(数値型変数)
	mref p2, 17  ; パラメータ2(数値型変数)
	mref stt, 64 ; システム変数stat
	stt = p1 + p2
	return
#global

	a = 3
	b = 5
	stt = "モジュールとはまったく別に扱われる変数"
	add a, b ; 変数1,2の値を足した数値をstatに返す命令
	mes stat
	mes stt
	stop
次に「DLLを呼び出しているのは」と書こうとしていたDLLを呼び出す#uselib命令を説明します。 また、#func命令についての説明もしておく必要がありそうなので同時にします。 #uselib命令の書式は「#uselib "DLL名"」となっていて、ここでは実際のDLL名(*.dll)を指定します。 HSP2.6では標準命令と化しているsysinfo命令だけを#includeせずにver2.55で使いたいなら、
#uselib "hspext.dll"
#func sysinfo sysinfo 1 sysinfo memory, 34 memory = memory / 1048576 ; 単位をMBに変更 mes "全メモリ = " + memory + "MB" stop
とすることができますが…あまり意味がありませんね。 #func命令の書式は「#func 新規名称 関数名 タイプ」です。 コレでDLL内に定義されている命令(関数)を読み込み、新規命令の名前で使えるようになるわけです。 先ほどsysinfo命令のタイプ(パラメータ)を1と書きました。 タイプは、0〜7の8種類あります。普通にHSPを使うだけでは知る必要はないのでココでは割愛します。 DLLの作成者くらいしか見ることのない命令でしょう…。 …でsysinfo命令ですが、関数名さえ間違えなければ(大文字小文字も)新規名称は何でもよいわけです。 「#func info sysinfo 1」としてしまえばinfo命令でsysinfoと同じく呼び出せるわけですね。 さて2.55の最後として、使い方はわかるが用途が良くわかってない#fpbitについて。 書式は「#fpbit 小数値bit数」で、固定小数部のbit数を変更する命令です。 デフォルトで16bit、即ち整数16・小数16bitとなっています。 コレは本当の小数と呼べるものではありません。hspext.dllにあったemath命令のようなものです。 HSP2では32bit整数値しか扱えない、となっていました。 小数値用に1bit使うと、整数部が1bit減って1073741823までしか扱えなくなります。 上の説明でわかるかと思いますが、この命令は単に数値をビットシフトしているだけです。 小数は数値の後ろに「k」と書きます。例えば「0.25」と書きたい場合は「0.25k」といったように。
#fpbit 3 ; 3ビット左シフトさせる(最低0.125単位まで扱える)
	var = 1.0k
	mes var
	stop
ビットシフトを知っている方はお分かりいただけるかと思いますけど、 1bit左シフトで0.5、2bitで0.25、3bitで0.125、4bitで0.0625…を最小ビット(1)として扱います。 ですので先ほどの3bitを小数部とした場合に「0.124k」以下は「0」として扱われます。 ここで扱う固定小数は表示もできません(表示すると整数値)ので使い道がホントによくわからないです…。 無理矢理(?)表示させるとすれば、下記のようにすればいけます。
#fpbit 7 ; 最小単位は0.078125
	calc = 1
	repeat 7
		calc=calc*2 ; 7bit左シフトさせると何倍になるか計算
	loop
	msg = 1
	repeat 7
		msg = msg * 10 ; 10000000倍まででき小数第6桁までOK(整数は25bit幅なので最大値が16777215)
	loop
	num1 = 1.125k ; 掛け算の場合num2と掛けられるのは1〜7まで(8だと1bit増えてオーバーフローする)
	num2 = 0.234375k
	result = num1 + num2 ; 計算結果
	result = result * msg / calc ; 小数値を表示するために整数にして7bit右シフトで戻す
	pos 50, 50 : mes "1.125 + 0.234375 = "
	repeat 7
		answer=result/msg\10 ; 1の位から0.1の位、0.01の位…と取り出していく
		msg=msg/10
		pos cnt*20+200,50 : mes answer ; 1桁ずつ表示
		if cnt=0 : pos 210,50 : mes "." ; 初めは1の位なので0回目と1回目の間に小数点「.」を付ける
	loop
	stop
ビットシフトを知らない方にはサッパリな内容だったことと思います。 ココまでが、2.55で扱えるプリプロセッサです。ココから下が2.6以上で扱うことができる命令です。 まず#defineの変更から入ります。 #defineは元の形を別の名称で定義する(置き換え)というものでした。 当然2.6になっても置き換えできますが、置き換えに付け加えて引数の付加ができるようになったのです。 今までモジュール(関数)でしていたようなことを下記のように#define1行だけでできるようになりました。
#define TAX(%1) %1 * 105 / 100 ; 指定数値に1.05を掛ける(消費税の計算)

	result = TAX 200 ; 200円
	mes result
	stop
#define部分を見ればわかりますが、今までは計算させることができず指定できるのは定数だけでした。 (%1)が引数で、()の中に%1から順番に%2,%3…と入れます。間には半角のコンマ『,』で区切ります。 また初期値を与えることができ、使用時に引数がない場合の初期値を入れることができるようになりました。 初期値の与え方は(%1=1)や(%1 = "abc",%2=var)のように『%●=××』でOKです。
#define DOGMES(%1 = "無言中だ") mes %1 + "ワン"

	DOGMES "DOGMESはmes命令の犬語バージョンだ"
	DOGMES ; 引数を入れずに初期値を入れさせる
	DOGMES "DOGMESを使用すると全文章がこのようになってしまうのだ"
	stop
式だけでなく命令も#defineに入れられますのでこのようなこともできてしまうわけです。
#define CURSOL(%1,%2=15) stick %1,%2,1 : wait 5 ; カーソル連続押しで八方向へ移動+ウエイト

	repeat
		redraw 0
		color 255, 255, 255 : boxf
		color 255 : boxf x * 32, y * 32, x * 32 + 32, y * 32 + 32
		redraw
		CURSOL key ; ループ内にはウェイト命令が入っていないようだが...!?
		if key & 1 : x-- : if x <  0 : x = 0
		if key & 2 : y-- : if y <  0 : y = 0
		if key & 4 : x++ : if x > 19 : x = 19
		if key & 8 : y++ : if y > 14 : y = 14
	loop
まだこの章では説明しませんがforwhileが命令ではなくマクロとして定義されています。 他言語のこう言ったfor等の関数は大体が引数をカッコでくくっています。 マクロに関数のようなカッコを使いたいと言う時は「ctype」と書く事で引数をカッコでくくれるます。 引数が数値1つの場合はctypeを付けずにカッコを付けてもエラーになりませんが付けておきましょう。 続きまして、特殊展開マクロについての解説をさせていただきたいと思います。 特殊展開マクロ(特殊キーワード)を設定することで、パラメータの共有や、スタックに積んだりできます。 %+英文字1文字+半角スペース+パラメータで、スペースまでを特殊展開マクロと判断します。 パーセントに続ける英文字は何でも良いと言うわけではありません。下記のものしか使用できません。
文字機能
%c改行する
%iユニークなラベル名の生成をしてスタックに積む
%nユニークなラベル名の生成
%oスタックに積んだ文字列の取り出し
%pスタックを戻さず文字列の取り出しだけ行う
%s引数をスタックに積む
%t共有タグ名の設定
「%c」の機能は表に書いたとおり、改行の代わりをします。 改行と言っても実行したファイル内の改行コード「\n」を表すわけではありません。 通常は複数行に書くようなことを1行に書けます。表示は1行でも内部的に%cで強制改行させているようです。 順番に書いていこうかと思いましたが「%t」が先に必要となりますので先に「%t」をします。 パラメータを共有させたりマクロ内にラベルを置いてそのラベルにジャンプさせたりするには それらのマクロに同じタグ名を付け共有することによってできます。 タグ名の付け方は「%t●●」というように「%t」と「●●(タグ名)」間に何も挟みません。(例 %tname) タグは最大半角15文字です(ヘルプに16字以内となってますが試したところ15が最大でした)。 …とタグ名はコレくらいで置いておいてまずは特殊展開マクロが持っているスタックから説明します。 スタックとは一般的に後入れ先出し(LIFO)形式に管理されているメモリ領域のことです。 平たく言うとデータを次々に記憶し記憶されたデータの最後から読み出していく処理をするものです。 「%s」でマクロの引数をスタックに積んで行きます。ラベルも積めますが後で話します。 %s1と書くと引数1(%1)を、%s2で引数2(%2)を…%s9で引数9(%9)を積みます。 タグ名が違えばスタックも別のものとなり、違うタグのスタックは取り出すことができません。 一方、取り出す特殊マクロは「%o」「%p」で入れたデータの最後から取り出していきます。 「%o」はスタック内のデータを取り出しますが、「%p」はスタックのデータを維持したまま取り出します。
#define PUSH(%1) %tmemory %s1 ; memoryタグを使って1番目の引数をスタックにに積む
#define POP %tmemory %o ; 後に積まれた物から順番に取り出す

	PUSH "A"
	PUSH "B"
	PUSH "C"
	mes POP
	mes POP
	mes POP
	stop
ココでひとつ注意していただきたいのは変数を積むという行為です。 別に変数を積むことができないと言うわけではありませんが、勘違いされる方がいますので書いておきます。 変数をスタックに積んだ後変数の内容を書きかえればスタックに積んである内容も更新されてしまいます。 恐らく、変数の中身をスタックに積むのではなく変数名やポインタを積んでいるのではないかと思われます。
#define EPUSH(%1) %tstack %s1
#define EPOP %tstack %o
	dim data, 5
	data = 1, 2, 3, 4, 5
	repeat 5
		EPUSH data data.cnt
	loop
	data.3 = -1 ; スタックも更新されてしまう!
	repeat 5
		mes EPOP
	loop
	stop
「%i」と「%n」はユニークなラベル名を生成します。 ユニークとはおかしいヘンテコリンなという意味ではありませんよ(笑 重複しないラベル名の生成、と書いたほうがわかりやすいですか…。 ラベル名は「_パラメータ名_0000」から順番に0001,0002…となっています。 ユニーク(重複しない)と書きましたが、HSP自らが生成するラベル名ではユニークであるだけで ユーザーがもしそのラベル名を使用している場合ではエラーとなってしまいます。下記が悪い例。
#define UNIQUECHK1 %tunique *%i
#define UNIQUECHK2 %tunique wait 1 goto *%o

*_unique_0000 ; 使用禁止!
	UNIQUECHK1
	UNIQUECHK2
「%i」はラベルを生成してスタックに積みます。「%n」は生成しますがスタックには積みません。 どういうときに使用するかというと、共有マクロ内をループしたり別ラベルに飛ばす時に必要となります。
#define RING(%1=1) %tring *%i %c wait %1 goto *%o ; %cでなくコロン「:」でもよい
	RING 1 ; 指定パラメータ分だけウェイトをかけるループ
それでは特殊展開マクロはコレくらいで終了しておいてこの章最後として#constを説明いたします。 #constがどういうプリプロセッサかと言うと「定数の置き換え」をする命令です。 書式は「#define 置き換え文字 定数,定数式」となっています。 コレだけ聞くと#defineと変わらないように感じますが、コチラは計算の結果を置き換えます。 違いは下のスクリプトを見ていただければわかるかと思われます。
#define NUM1 10*5 ; 「10*5」と置き換え
#const NUM2 10*5 ; 「10*5の結果=50」と置き換え

	ans1 = 1000 / NUM1 ; 1000÷10×5
	ans2 = 1000 / NUM2 ; 1000÷(10×5)
	mes ans1
	mes ans2
	stop
またこの#constでの計算はHSP特有のカッコ以外は左から演算するというわけではなく、 通常の([先]カッコ , × ÷ , + − , 比較演算子 ビットシフト , 否定 , AND OR XOR[後])です。
#const NUM 10 + 10 * 5 ; 通常の計算(5*10+10)

	mes NUM
	calc = 10 + 10 * 5 ; 従来どおり左から計算((10+10)*5)
	mes calc
	stop
はい、とりあえず#の付く命令(?)の前半をコレにて終了したいと思います。 多分入門講座としては相応しくないレベルだったであろうことをお詫び申しあげます。m(__)m 次章でもっと実用的なマクロ命令などを紹介していきたいと思っています。