14. FAQ: 見た目は同じコードだけど結果が違う
里々のつもりで書いたコードが、エラーも出ずに、違う結果になる例を集めました。上から、影響が大きく気づきにくい順に並べています。
| # | 症状 | 原因 |
|---|---|---|
| 1 | セリフが丸ごと消える | 英数.英数 が変数参照になる |
| 2 | 代入や関数呼び出しが、セリフとして出る | セリフ直後の行は続き行になる |
| 3 | ループの中で改行が入らない | 改行はセリフ行が連続したときだけ |
| 4 | 足し算が文字列の連結になる | + は文字列があると連結 |
| 5 | "0" の条件が真になる | 文字列の "0" は真 |
| 6 | 未設定の変数が == 0 で偽になる | null は 0 と等しくない |
| 7 | 全角の数字が計算できない | 全角数字は数値にならない |
| 8 | 文字列の大小比較が効かない | < > は数値比較 |
| 9 | if の中で作った変数が外で消える | let はブロックの中だけ |
| 10 | 他のトークの変数が見える | 裸の代入は起動中グローバル |
| 11 | 再起動すると値が消える | save の下だけが保存される |
| 12 | OnAITalk などが呼ばれない | 湊が内部で消費する |
| 13 | config.toml の間隔が効かない | save.json の値が優先される |
| 14 | 話者を間違えても喋る | 未登録の名前は \0 になる |
| 15 | 変数名を間違えてもエラーにならない | 未定義は空文字列 |
| 16 | 2つのトークが交互に出る | 一巡の規則 |
| 17 | 曜日の数字が違う | 湊は0が月曜 |
| 18 | foreach で要素でなく番号が出る | 変数が1つだと添字になる |
| 19 | ${ でパースエラー | ${ は } で閉じる |
| 20 | トークが選ばれたのに何も喋らない | 出力が空だと 204 |
1. セリフが丸ごと消える
里々では出ていたセリフが、湊では消えます。
OnBoot => {
湊: 体重は57.8kgです。
湊: バージョンは1.0です。
湊: 体重は 57.8kgです。
湊: ver 1.0です。
湊: 「foo.bar」って何?
湊: ${1}.5です。
}
\0\n\n体重は 57.8kgです。\nver 1.0です。\n\n1.5です。\e
1、2、5行目が空になっています(3行目と4行目、6行目は出ています)。
原因: セリフの行頭(と、${…} の直後)から、半角の空白や記号が現れるまでの文字の並びの中に . があると、変数.キー という変数の参照として読まれます。存在しない変数なので、空文字列になります。エラーにはなりませんが、応答には警告が付きます(SSPのエラーログに出ます)。日本語の文字と句読点は、すべて「区切られていない文字」として数えられるので、体重は57.8kgです。 のように文全体が1つの参照になります。
対策:
.を含む語(小数、バージョン、URL、ファイル名)を、行頭や${…}の直後の「最初の語」に入れない。- 直前に半角の空白を入れると、区切られて避けられます(3行目、4行目のように
体重は 57.8kgと書く)。 - 数字だけを式として書く方法もあります。
体重は${57.8}kgです。と書くと、57.8は数値の式として展開されます。
2. セリフの次の行が喋られる
代入や関数呼び出しが、セリフになります。
OnBoot => {
湊: 一行目
counter = 5
湊: 二行目
}
\0一行目\ncounter = 5\n二行目\e
原因: セリフの直後の行は、let、global、call、if などのキーワードで始まっていない限り、続き行として読まれます。
対策: let や global を付ける、; だけの行を挟む、call を使う(10. 改行のルール)。
3. ループの中で改行されない
OnBoot => {
let a = ["x", "y"]
foreach a as i, v {
湊: ${v}
}
}
\0xy\e
原因: 改行が入るのは、セリフ行が連続しているときだけです。ループの1回ごとに文が区切られます。
対策: 末尾に \n を書く。
4. 数のつもりが連結される
OnMouseClick => {
湊: ${reference["0"] + 1}
}
\031\e
原因: reference、SAORIの戻り値、移行した変数は文字列です。文字列に対する + は連結です。
対策: to_num() で数値にする(5.)。
5. "0" が真になる
OnBoot => {
global save.flag = "0"
if (save.flag) {
湊: 真
} else {
湊: 偽
}
}
\0真\e
原因: 里々では 0 は偽でしたが、湊で偽になるのは、false、数値の 0、空文字列、null、空の配列・マップです。
6. 未設定の変数 == 0 が偽になる
OnBoot => {
湊: ${save.n == 0}|${(save.n ?? 0) == 0}
}
\0false|true\e
原因: 未設定の変数は null で、空文字列として表示されます。数値の 0(表示は "0")とは一致しません。
対策: ?= で初期化するか、(save.n ?? 0) == 0 と書く。
7. 全角の数字が 0 になる
OnBoot => {
let z = "3"
湊: ${z >= 3}|${to_num(z)}
}
\0false|0\e
対策: 全角の数字は、半角にしてから数値に直す(9. セーブデータ)。
8. 文字列の大小比較ができない
OnBoot => {
湊: ${"a" < "b"}|${"b" < "a"}
}
\0false|false\e
原因: < > <= >= は、両辺を数値に変換します。数値にならない文字列は 0 なので、どちらも 0 < 0 で偽になります。辞書順で並べたいときは、sort(配列, "kana") などを使います。
9. let の変数が外で消える
OnBoot => {
if (true) {
let b = 3
}
湊: [${b}]
}
\0[]\e
原因: let の変数は、宣言した { } の中だけで有効です(5.)。
10. 代入した覚えのない変数が他のトークで見える
x = 5 は、let がなければ、起動中グローバルを作ります(5.)。一時変数は必ず let で宣言してください。
11. let で作った変数がセーブされない
保存されるのは、global save.… だけです。let も、global work.… も、終了すると消えます(5.)。
12. OnAITalk や OnMinuteChange が呼ばれない
湊がこの2つのイベントを内部で使うため、自分で OnAITalk => と書いても呼ばれません(OnAITalk は OnRandomTalk として実行され、OnMinuteChange はランダムトークの発火に使われます。3.)。
13. config の間隔を変えても効かない
初回の起動のあと、talk_interval_secs などは save.json の値が優先されます(9.)。
14. 話者の名前を間違えても喋る
config.toml に登録していない名前は、エラーにならず \0 として扱われます(2.)。
15. 未定義の変数がエラーにならない
${x} の x を書き間違えても、警告なしに空文字列になります。里々では括弧ごとそのまま表示されたので気づけました(5.)。
16. 2つのトークが交互に出る
同名のトークがちょうど2つのとき、最初だけランダムで、あとは交互になります(4.)。
17. 曜日の数字が違う
| 曜日の値 | |
|---|---|
| 里々 | (現在曜日) は「日」「月」…の文字 |
| 湊 | now.曜日 は数値。0が月曜、6が日曜 |
| YAYA | GETTIME[3] は数値。0が日曜、6が土曜 |
18. foreach で要素が取れない
OnBoot => {
let a = ["x", "y"]
foreach a as v {
湊: ${v}\n
}
}
\00\n1\n\e
foreach 配列 as 名前 の名前は、添字です。要素も欲しいときは foreach 配列 as i, v と2つ書きます。
19. 記号の入ったセリフでパースエラー
OnBoot => {
湊: 100$です。価格は${です
}
\b[2]\0パースエラー:\nmain.mntの2行目: 認識できない文です: 「${です」\e
$ だけなら、そのまま文字として出ます(100$です)。ただし ${ は展開の始まりなので、} で閉じないとパースエラーになります。${ という文字を出したいときは、${'${'} と書きます。
20. トークが選ばれたのに無言になる
OnRandomTalk などで、if の分岐の結果として出力がすべて空になると、湊は何も返しません(204)。SSPからは、イベントが何も返されなかったように見えます。作者向けには「出力が空でした」という通知が残ります。
OnRandomTalk => {
let z = 1
}
(204)
その他、確認しておきたいこと
- 応答の末尾に
\![get,property,OnGotVirtualTime,…]が付くのは、OnBootとOnMinuteChangeだけです(3.)。 - 湊は、
funcと同じ名前のトークがあるとき、funcを優先します。 - 半角と全角の違い(
AとA、1と1)は、湊では別のものとして扱われます。
次は15. 三方式の比較に進んでください。