14. FAQ: 見た目は同じコードだけど結果が違う

里々のつもりで書いたコードが、エラーも出ずに、違う結果になる例を集めました。上から、影響が大きく気づきにくい順に並べています。

#症状原因
1セリフが丸ごと消える英数.英数 が変数参照になる
2代入や関数呼び出しが、セリフとして出るセリフ直後の行は続き行になる
3ループの中で改行が入らない改行はセリフ行が連続したときだけ
4足し算が文字列の連結になる+ は文字列があると連結
5"0" の条件が真になる文字列の "0" は真
6未設定の変数が == 0 で偽になるnull は 0 と等しくない
7全角の数字が計算できない全角数字は数値にならない
8文字列の大小比較が効かない< > は数値比較
9if の中で作った変数が外で消えるlet はブロックの中だけ
10他のトークの変数が見える裸の代入は起動中グローバル
11再起動すると値が消えるsave の下だけが保存される
12OnAITalk などが呼ばれない湊が内部で消費する
13config.toml の間隔が効かないsave.json の値が優先される
14話者を間違えても喋る未登録の名前は \0 になる
15変数名を間違えてもエラーにならない未定義は空文字列
162つのトークが交互に出る一巡の規則
17曜日の数字が違う湊は0が月曜
18foreach で要素でなく番号が出る変数が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が日曜
YAYAGETTIME[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. 三方式の比較に進んでください。