マクロツイーター

はてダから移行した記事の表示が崩れてますが、そのうちに直せればいいのに(えっ)

LuaTeX の tex.skip の挙動がおかしい件について

LuaTeX では、Lua コード中で tex.count、tex.dimen、tex.skip といった(仮想的な)配列を用いて、 TeXレジスタにアクセスすることができる。

\newcount\countA
\newdimen\dimenA
\newskip\skipA
\directlua{
  tex.count.countA = 42 % \countA=42 と同じ
  tex.dimen.dimenA = 100 * 0x10000 % sp単位の整数で指定
  % glue_specノードを作る
  local gsp = node.new(node.id("glue_spec"))
  gsp.width = 3 * 0x10000 % sp単位
  tex.skip.skipA = gsp
}
\message{^^J%
  countA=\the\countA^^J%
  dimenA=\the\dimenA^^J%
  skipA=\the\skipA^^J%
}
\bye

寸法(dimen)レジスタやグルー(skip)レジスタが保持するのは長さである。上の例で解るように、寸法(伸縮なしの長さ)は sp 単位の整数として表し、また、グルー(伸縮付きの長さ)は glue_spec というノードオブジェクトを用いて表される(glue_spec gsp について、gsp.width がその自然長である)。

これらのアクセスは TeX における代入文と同じ性質をもち、グループ内で値の設定を行った場合はその効果は局所的である。

\newdimen\dimenA
\dimenA=50pt
\begingroup % グループに入ってLuaで値を設定
\message{^^J\the\dimenA}
%% \dimenA=100pt と同値
\directlua{ tex.dimen.dimenA = 100 * 0x10000 }
\message{^^J\the\dimenA}
\endgroup % グループを抜けると以前の値に戻る
\message{^^J\the\dimenA^^J}
\bye
50.0pt
100.0pt
50.0pt

……のはずである。ところが、グルー値の場合は何故か代入がローカルにならない。

\newskip\skipA
\skipA=50pt
\begingroup
\message{^^J\the\skipA}
%% ローカル代入 \skipA=100pt を行いたいが……
\directlua{
  local gsp = node.new(node.id("glue_spec"))
  gsp.width = 100 * 0x10000
  tex.skip.skipA = gsp
}
\message{^^J\the\skipA}
\endgroup % グループを抜けると……
\message{^^J\the\skipA^^J}
\bye
50.0pt
100.0pt
100.0pt      ←戻っていない

ところが、Lua での代入の前に、グループの中で一度 TeX で代入を行っておくと、想定通りにグループを抜けると以前の値に復帰する。

\newskip\skipA
\newskip\skipB
\skipA=50pt
\begingroup
\skipA=60pt  % グループ中で代入
\message{^^J\the\skipA}
\directlua{
  gsp = node.new(node.id("glue_spec"))
  gsp.width = 100 * 0x10000
  tex.skip.skipA = gsp
}
\message{^^J\the\skipA}
\endgroup
\message{^^J\the\skipA^^J}
\skipB=1pt
\directlua{
  print(gsp.width)
}
\bye
60.0pt
100.0pt
50.0pt      ←戻った
65536

果たしてこの挙動は想定仕様なのだろうか、それともバグなのだろうか。悩ましい……。

少し違う話。上のコードでは最後に gsp.width の値を出力させているが、その結果は「65536」(1pt に相当)になっている。gsp.width に代入した値は 6553600(100pt)のはずなのでこれは奇妙である。

推測であるが、次のような理由が考えられる。glue_spec は TeX のノードの一種なので、Lua の動的メモリ管理ではなく TeX のそれの下に存在する。もし、skipA への Lua での代入がないとすると、skipA がもっていた「ローカルな値」である「60pt を示す glue_spec」はグループを抜けると無効になるので、そのノードは開放される。ここで、グループを抜ける前に Lua で skipA の指すノードを Lua 変数 gsp の指すものに変えたとすると、「60pt」の代わりに gsp のノードが開放されてしまうのであろう。後に、TeX で「\skipB=1pt」を実行すると、「1pt の glue_spec」が生成されるが、その際に、今や無効になった gsp のノードが再利用されてそこに値「1pt」が設定されたため、gsp.width の値が変わってしまったのであろう。

こちらの挙動は筋は通っている。でも、Luaグローバル変数の指す値が TeX の「既に無効なオブジェクト」であるという状態は非常に危険である。Lua でグルーレジスタを扱うのは難しそうだ。


[追記]グループを二重にしてみると、全く不可解な挙動になる。

\newskip\skipA
%\tracingassigns=1 \tracingrestores=1 \tracingonline=1
\skipA=25pt
\begingroup
    \skipA=50pt
    \begingroup
        \directlua{
          local gsp = node.new(node.id("glue_spec"))
          gsp.width = 100 * 0x10000
          tex.skip.skipA = gsp
        }
        \message{^^J\the\skipA (Level \the\currentgrouplevel)}
    \endgroup % グループを抜けると……
    \message{^^J\the\skipA (Level \the\currentgrouplevel)}
\endgroup
\message{^^J\the\skipA (Level \the\currentgrouplevel)^^J}
\bye
100.0pt(Level 2)
0.0pt(Level 1)
25.0pt(Level 0)

最外で 25pt を代入し、グループ内で 50pt と 100pt を代入しているのに、Lua で代入したグループから抜けた後は、何故か値がゼロになってしまっている。これはどう考えてもバグのようだ……。