🐍

なでしこ(v1)の小テク集

2020/11/30に公開

はじめに

この記事は 日本語プログラミング言語「なでしこ」に ある程度 親しんでいるユーザーを想定しています。
v1.577 をベースに書いていますが、これより数年くらい前の古いバージョンでも動作確認をしています。
ここに掲載しているコードには なでしこ用の色分け表示がないので なでしこエディタ (nakopad) で読むことを推奨します。


Windows 標準のOKキャンセル二択を表示する

なでしこの 二択 命令は はい / いいえ から選択しますが、これを OK / キャンセル にしたい場合。
さらに言えば、内容の種類を示すアイコンもほしいですね。

「OKキャンセル」関数の作成

OKキャンセル.nako
!「windows.nako」を取り込む
!変数宣言が不要
!ダイアログアイコン一覧とはハッシュ = 「情報={MB_ICONINFORMATION}
確認={MB_ICONQUESTION}{~}警告={MB_ICONEXCLAMATION}{~}エラー={MB_ICONERROR}」
●コンソールハンドル取得 = DLL("kernel32.dll", "int GetConsoleWindow()")
●モーダル用窓ハンドル
 呼び出し元にアプリケーションハンドルを代入
 もし呼び出し元が0なら
  母艦ハンドルを呼び出し元に代入
  もし呼び出し元が0ならコンソールハンドル取得して呼び出し元に代入
 呼び出し元で戻る
●OKキャンセル({文字列=?}Sで|Sと|Sの|{文字列=?}Tが|Tから|{文字列=?}ICONとして)
 モーダル用窓ハンドルを呼び出し元に代入
 もしTが空なら呼び出し元から窓ハンドルテキスト取得してTに代入
 MessageBox(呼び出し元, S, T, MB_OKCANCEL || ダイアログアイコン一覧@ICON)

解説

vnako / gnako / cnako どの実行形式でもモーダル表示になるようにしています。
「~」が または 「~」から で二択表示のタイトルを変更できます。
また、 「情報|確認|警告|エラー」として でアイコンの指定も可能です。

母艦が存在しない cnako では 母艦のハンドル と書くとエラーになってしまいます。
代わりに 母艦ハンドル を使うことで回避していますが、コンソールウィンドウのハンドルはこれでは取得できないので GetConsoleWindow API を使います。

使用例

OKキャンセルテスト.nako
!「OKキャンセル.nako」を取り込む
もし「夕方までひとやすみしますか?」のOKキャンセルがOKなら
 「元気になった」という
「ゲームの進行状況」から「確認」として「セーブしますか?」とOKキャンセル
条件分岐
 OKなら「セーブしました」という
 キャンセルなら「セーブせずに続行します」という

変数 OK / キャンセル は、それぞれ windows.nakoIDOK / IDCANCEL に相当します。

文字列と数値の比較

存在しない変数は参照される際に変数名と同じ文字列が割り当てられます。
例として、OKキャンセル関数を gnako / cnako モードで実行した場合、1行目の アプリケーションハンドル は存在しない変数なのでそのまま アプリケーションハンドル という文字列が割り当てられた後、変数に代入されることになります。
すると、直後の もし呼び出し元が0なら と判定されるように思われますが になります。
これは左辺の文字列が整数と比較するときに自動で整数変換が行われ 0 となるためです。


メモ部品に「一行追加」で追記できるようにする

メモ グループをコピーして新たに 拡張メモ グループを作り、 一行追加 関数を実装します。

「拡張メモ」グループの作成

拡張メモ.nako
■拡張メモ +メモ
 ・入力位置最後~選択位置は$7FFFFFFF。
 ・追加({参照渡し}Sの|Sを|Sと|Sって)~入力位置最後。選択文字列はS。
 ・一行追加({参照渡し}Sの|Sを|Sと|Sって)~入力位置最後。選択文字列はS。改行追加。
 ・改行追加~選択文字列は改行。
 ・挿入({参照渡し}Sを)~選択文字列はS。

一行追加 関数では、メモ部品の入力位置を最後に移動して 選択文字列 に追加したい文字列を代入しています。

使用例

拡張メモテスト.nako
!「拡張メモ.nako」を取り込む
ログとは拡張メモ
ログの幅は300
ログに「怪獣さんがログインしました」と一行追加
ログに「怪獣: がおーん!」を一行追加
ログに「怪獣さんがログアウトしました」って一行追加

ログは「{ログ}{今日} {今} 完了しました{改行}」 というように書くよりも直感的に、かつ処理的にも高速に文字列を追加することができます。


リストボックスを手軽に操作できるようにする

なでしこでは、リストボックスに表示したい項目をあらかじめ配列で用意して アイテム に設定して使います。
ただ、項目を1個だけ追加したいときや削除または入れ替えを行いたいときもあると思うので、
リスト に項目を操作する関数を追加した 拡張リスト グループを作ることにします。

「拡張リスト」グループの作成

拡張リスト.nako
!「windows.nako」を取り込む
!変数宣言が不要
■拡張リスト +リスト
 ・項目数~windows:SendMessage(ハンドル, LB_GETCOUNT, 0, 0)
 ・消去~windows:SendMessage(ハンドル, LB_RESETCONTENT, 0, 0)
 ・更新中~windows:SendMessage(ハンドル, WM_SETREDRAW, 0, 0)
 ・更新完了~windows:SendMessage(ハンドル, WM_SETREDRAW, 1, 0)
 ・追加({文字列}Sを|Sの)~
  windows:SendMessage(ハンドル, LB_ADDSTRING, 0, POINTER(S))
 ・一括追加({文字列}Sを|Sの)~
  Sを反復
   windows:SendMessage(ハンドル, LB_ADDSTRING, 0, POINTER(対象))
 ・挿入({文字列}Sを|{=$7FFFFFFF}Iに)~
  もしIが0未満ならIは0。違えば、Cは項目数。もしIが(C)以上ならIはC。
  windows:SendMessage(ハンドル, LB_INSERTSTRING, I, POINTER(S))
 ・一括挿入({文字列}Sを|{=$7FFFFFFF}Iに)~
  もしIが0未満ならIは0。違えば、Cは項目数。もしIが(C)以上ならIはC。
  Sを反復
   windows:SendMessage(ハンドル, LB_INSERTSTRING, I + 回数 - 1, POINTER(対象))
 ・削除({整数}Iを)~windows:SendMessage(ハンドル, LB_DELETESTRING, I, 0)
 ・選択({参照渡し}ITEMを|ITEMの)~
  ITEMの変数型確認。もしそれが『整数』なら値はITEM。
  違えば、ITEMを完全一致検索。それ || 0を選択。
 ・検索({文字列}Sを|{=$FFFFFFFF}Iから)~
  windows:SendMessage(ハンドル, /* LB_FINDSTRING */ $018F, I, POINTER(S))
 ・完全一致検索({文字列}Sを|Sの{=$FFFFFFFF}Iから)~
  windows:SendMessage(ハンドル, /* LB_FINDSTRINGEXACT */ $01A2, I, POINTER(S))
 ・番目文字列長({整数}I)~windows:SendMessage(ハンドル, LB_GETTEXTLEN, I, 0)
 ・番目文字列取得({整数}I|{参照渡し}Sに)~
  Sに((I)番目文字列長 + 1)を確保
  windows:SendMessage(ハンドル, LB_GETTEXT, I, POINTER(S))
 ・番目交換({整数}Aと|{整数}B)~
  Cは項目数。Eは空。
  もし(A >= C)または(A < 0)ならEはA。違えば、もし(B >= C)または(B < 0)ならEはB。
  もしEが空でなければ「存在しない項目 ({E}番目) が指定されています。」とエラー発生。
  Iは値。ASTRに(A)番目文字列取得してBSTRに(B)番目文字列取得。
  Aを削除してBSTRをAに挿入。Bを削除してASTRをBに挿入。ASTRは空。BSTRは空。
  もしIがAならBを選択。違えば、もしIがBならAを選択。

解説

基本的な機能を SendMessage で ごりごり。ここで windows: と付けている部分は 名前空間指定 で、
windows.nako で宣言されている SendMessage を明示的に指定しています。
名前空間指定無しで呼び出せる SendMessage も存在しますが、これはなぜか値を返さない仕様になっています。

使用例

拡張リストテスト.nako
!「拡張リスト.nako」を取り込む
単語リストとは拡張リスト
単語リストのレイアウトは「全体」
単語リストのアイテムは「どらごん{~}もぐもぐ{~}ばなな」
単語リストに「おいしい」を追加
単語リストの「もぐもぐ」を選択

「1番目と3番目を交換します。」という
単語リストの0と2番目交換

配列を扱うときと同様に添字は 1 からではなく 0 から始まります。


「」と『』と文字列変数の処理速度比較

結論としては、 「」 > 『』 > 文字列変数 の順に時間が掛かります。

通常のカギカッコ 「」{…} で囲まれた式を評価して展開しますが、
この { があるかどうかを1文字ずつ走査する処理と、展開した文字列を連結していく処理が遅さの要因になっています。

一方、二重カギカッコ 『』 は文字列の展開を行わないため 「」 よりも速いです。
また、両者に共通する点としては、実行するたびに文字列の生成とコピーが発生するので文字列の長さも速度に影響します。

そして、すでに生成されていて内容を参照するだけの 文字列変数 は3つの中で最速ということになります。
何万回と繰り返し実行するような処理等では、これらを使い分けることで処理速度の最適化を図ることができます。

測定テスト

測定テスト.nako
!繰り返し回数は500000
!例文は『米も積もればおむすび』

3回
 開始時間にシステム時間を代入
 真評価回数に0を代入
 回数で条件分岐
  1ならカギカッコ比較
  2なら二重カギカッコ比較
  3なら文字列変数同士比較
 システム時間から開始時間を引いて所要時間に代入
 「真評価回数: {真評価回数}/{繰り返し回数}」と表示
 (所要時間 / 1000) & 『秒』を表示
『テストが完了しました。』と表示

●カギカッコ比較
 『カギカッコ内の文字列を例文と比較します。』と継続表示
 (繰り返し回数)回
  もし例文が「米も積もればおむすび」なら真評価回数に1を直接足す

●二重カギカッコ比較
 『二重カギカッコ内の文字列を例文と比較します。』と継続表示
 (繰り返し回数)回
  もし例文が『米も積もればおむすび』なら真評価回数に1を直接足す

●文字列変数同士比較
 『文字列変数同士で比較します。』と継続表示
 Sは『米も積もればおむすび』
 (繰り返し回数)回
  もし例文がSなら真評価回数に1を直接足す

実行結果

以下は Intel(R) Core(TM) i5-3450S CPU @ 2.80GHzcnako モード でテストした結果です。

カギカッコ内の文字列を例文と比較します。真評価回数: 500000/500000
1.997秒
二重カギカッコ内の文字列を例文と比較します。真評価回数: 500000/500000
1.186秒
文字列変数同士で比較します。真評価回数: 500000/500000
0.936秒
テストが完了しました。

Discussion