NScripterの深淵

NScripter/NSLua/Lua5.1/関連プログラム

▼メニュー

ゴブリンでもわかるNSLua/Lua5.1 第5回:luasub命令を作ろう

そろそろ座学に飽きたでしょ?

予備知識 関数の「もう一つの」記述方法

関数を作成(・代入)する方法については前々回で学んだ。

hoge=function()
    --ここに中身を書く
     end

local hoge=function()
    --ここに中身を書く
      end

実は関数の代入に限り、以下のような書き方も許されている。

function hoge()
    --ここに中身を書く
end

local function hoge()
    --ここに中身を書く
end

これは一般的なプログラミング言語の文法に見た目を合わせるための特例的ルールであり、両者の解釈は(ほぼ)一緒、すなわち共に「関数の作成」と「指定した名前の変数への代入」となる。

前々回では「関数も変数に放り込む値である(型の一つに過ぎない)」ことを強調する目的で前者の表記を用いていたが、今後は前者を用いる特別な理由が無い限り後者の表記に統一する。こっちの方が見やすいからね。

こうした「見やすくするために特別に許されている別表記」のことをプログラミングの世界ではシンタックスシュガー(糖衣構文)と呼ぶが、名前は別に覚えなくてよい。覚えておくべきは「関数は型の一つに過ぎない」という前提である。

luasub命令を作ってみよう

一つ実践を挟もう。

NScripter本体では実装が難しく、Luaではあっさり作れてしまう命令」のデモンストレーションとして最適な例、

「『渡した文字列』に『特定のキーワード』が含まれるかチェックし、判定結果を返す自作命令」

を作ってゆく。名前はsfindとしよう。

;「$1"あいうえお"が含まれていたら"あいうえお"、
;含まれていなかったら""を$0に代入」
;するNScripter(luasub)命令
sfind $0,$1,"あいうえお"

まずは「NScripterから呼び出す命令(Lua関数)」のガワを作る。

----system.lua-------------
--luasub命令を実行しておく
--命令の名前は"sfind"とする
NSExec("luasub sfind")
--関数を用意(定義)する
function NSCOM_sfind()
    --ここに中身を書く
end

できた。そして中身。「関数」の機能である

①引数を値として受け取る

②受け取った値を使って処理をする

③処理結果を返す

それぞれを順番に作っていこう。

luasub命令:引数を受け取る

Luaにおける通常の関数と異なり、luasub命令がNScripterから引数を受け取るには専用の関数を呼び出さなければならない。

sfind $0,$1,"あいうえお"

このようなluasub命令が呼び出されると、引数である

「$0,$1,"あいうえお"

が(文字列として)内部メモリに一時保存される。

この文字列を先頭から順に解析し、値へ変換するために用意された機能が以下のNSLua関数群である。

--数値を1つ読み込む。変数なら中身を参照する
local num=NSPopInt()
--文字列を1つ読み込む。変数なら中身を参照する
local str=NSPopStr()
--数値変数の番号(%0なら0)を1つ読み込む
local intref=NSPopIntRef()
--文字列変数の番号($1なら1)を1つ読み込む
local strref=NSPopStrRef()

--idを1つ読み込む。結果は文字列として格納される
local id=NSPopId()
--ラベルを1つ読み込む。結果は"*hoge"のような文字列として格納される
local str=NSPopLabel()

--もし直後の文字がカンマなら、
if(NSCheckComma())then
    --カンマ','を読み飛ばす
    NSPopComma()
end

NScripterの命令には、ふつう「引数1,引数2,引数3,引数4,...」 とカンマ区切りの引数が渡されている。つまり、

①受け取るべき値の型に対応した読み込み関数

②カンマを読み飛ばすNSPopComma()

を繰り返し呼び出せばよい。

sfind $0,$1,"あいうえお"

--system.lua------------
function NSCOM_sfind()
    --文字変数の番号を受け取る
    -->【$0】【,$1,"あいうえお"】
    local ref=NSPopStr()

    --カンマを飛ばす
    -->【,】【$1,"あいうえお"】
    NSCheckComma()

    --文字列を受け取る
    -->【$1】【,"あいうえお"】
    local str=NSPopStr()

    --カンマを飛ばす
    -->【,】【"あいうえお"】
    NSCheckComma()

    --文字列を受け取る
    -->【"あいうえお"】【】
    local checkstr=NSPopStr()

    -->ref=0,str="※$1の中身",checkstr="あいうえお"
end

luasub命令:受け取った値を処理する

「文字列Aに文字列Bの値が含まれていれば判定に成功、違ったら失敗」という関数は、都合よくLuaの標準ライブラリに用意されている。

string.find(
    "チェック対象の文字列",
    "キーワード文字列",
    (何文字目を先頭としてチェックするか,)
    (正規表現を使用しないフラグ)
)

今は最も単純な文字列検索を作っているので、判定は先頭1文字目から行い、正規表現は不使用としておく。

local head,tail=string.find(str,checkstr,1,true)

trueという値が急に出てきたが、これについては次回触れる(今まで意図的に紹介していなかった型の一種で、真偽値(ブーリアン)型と呼ばれる。まだ気にしなくていいよ)。

そして正規表現を用いない場合、resultに入る値(すなわちstring.findの戻り値)は以下のようになっている。

①検索に成功した場合、「マッチした文字列=検索に使用した文字列が何文字目〜何文字目に存在するか」を示す2つの数値が返る

②検索に失敗した場合、nilが返る

今度はnilという値が急に出てきた。これも型の一つ…というより「値なし」を示す特殊な値で、真偽値型と同様に次回触れる。

無事判定に成功したなら、返ってきた結果を用いて文字列を切り出しておこう。文字列の切り出しにはstring.sub関数を用いる。

local result
if(head==nil)then
    result=""
else
    --head文字目からtail文字目までstrを切り出す関数
    --(半角換算)
    result=string.sub(str,head,tail)
end

上記コードを統合すると、以下のようになる。

NSExec("luasub sfind")
function NSCOM_sfind()
    --文字変数の番号を受け取る
    local refnum=NSPopStr()
    --カンマを飛ばす
    NSCheckComma()
    --文字列を受け取る
    local str=NSPopStr()
    --カンマを飛ばす
    NSCheckComma()
    --文字列を受け取る
    local checkstr=NSPopStr()

    --判定
    local head,tail=string.find(str,checkstr,1,true)

    --判定に成功していたら文字列を切り出す
    local result
    if(head==nil)then
        result=""
    else
        --head文字目からtail文字目までstrを切り出す関数
        result=string.sub(str,head,tail)
    end

    --以下で判定結果をrefnum番の文字変数に代入したい
    --★★
end

引数を受け取って判定する処理までは仕上がった。あとは結果を返せばよい。

LuaからNScripter変数に値を代入する/変数の中身を覗く

luasub命令における引数読み取りと比べると代入処理は驚くほど単純であり、専用の関数を呼ぶだけでよい。

--NSSetIntValue(num,val)
-- 数値変数num番に数値valを代入する
NSSetIntValue(0,123)

--NSSetStrValue(num,val)
-- 文字列変数num番に文字列valを代入する
-- NScripter本体からは代入困難な文字(「"」など)も代入できる
NSSetStrValue(0,"abc")
NSSetStrValue(0,' " ')

現在作ろうとしていたsfind命令ではrefnum番に文字列resultを代入するのだから、

NSSetStrValue(refnum,result)

目的は果たされた。両関数はluasub命令に限らず、Luaのどこから呼んでも構わない。

反対に、LuaからNScripter変数の中身を取得する関数も提供されている。

--NSGetIntValue(num)
-- 数値変数num番の値を返す
local num=NSGetIntValue(0)

--NSGetStrValue(num)
-- 文字列変数num番の値を返す
local str=NSGetStrValue(0)

まとめ

完成したsfind命令は以下の通り。

---------------------------
--system.lua

NSExec("luasub sfind")
function NSCOM_sfind()
    --文字変数の番号を受け取る
    local refnum=NSPopStr()
    --カンマを飛ばす
    NSCheckComma()
    --文字列を受け取る
    local str=NSPopStr()
    --カンマを飛ばす
    NSCheckComma()
    --文字列を受け取る
    local checkstr=NSPopStr()

    --判定
    local head,tail=string.find(str,checkstr,1,true)

    --判定に成功していたら文字列を切り出す
    local result
    if(head==nil)then
        result=""
    else
        --head文字目からtail文字目までstrを切り出す関数
        result=string.sub(str,head,tail)
    end

    --判定結果をrefnum番の文字変数に代入
    NSSetStrValue(refnum,result)
end

---------------------------------

;00.txt
*define
game
*start

sfind $0,"吾輩は猫である","猫"
;「猫」
「$0」@

sfind $0,"吾輩はダチョウである","猫"
;「」
「$0」@

正規表現文字コード絡みの問題など考慮すべき要素を置き去りにしてはいるものの、とりあえず動作している。

次回は処理の後半に出現した「nil」および「真偽値型」について触れ、Luaの主要な型を掌握してしまおう。