2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Hello World あたたたた 23日目 BibTeX編

Last updated at Posted at 2025-12-23

本記事は Hello World あたたたた Advent Calendar 2025 の23日目の記事です.

今日はBibTeXで「Hello World あたたたた」を実装して解説していきます.

そもそも「Hello World あたたたた」が何かは 1日目の記事 をご参照ください.

本記事は, LaTeXを知っていることを前提とします.

コーディング例

Overleaf で実行できます.

atatatata.bst
ENTRY
  { timestamp }
  { }
  { }

INTEGERS { 
  prng.seed prng.a prng.c prng.m 
  math.lhs math.rhs math.res math.q math.r
  parse.total parse.tmp.val parse.sign
  hokuto.is.alive hokuto.counter hokuto.max.iter hokuto.combo.count 
}
STRINGS  { tmp.char tmp.str }

FUNCTION {math.not} { { #0 } { #1 } if$ }
FUNCTION {math.and} { 'skip$ { pop$ #0 } if$ }

FUNCTION {math.multiply}
{
  'math.rhs := 'math.lhs :=
  #0 'math.res :=
  { math.rhs #0 > }
  { math.res math.lhs + 'math.res :=
    math.rhs #1 - 'math.rhs :=
  }
  while$
  math.res
}

FUNCTION {math.div.mod}
{
  'math.rhs := 'math.lhs :=
  math.rhs #0 =
    { "Error: Division by zero" warning$ #0 #0 }
    { #0 'math.q :=
      { math.lhs math.rhs < math.not }
      { math.lhs math.rhs - 'math.lhs :=
        math.q #1 + 'math.q :=
      }
      while$
      math.q math.lhs
    }
  if$
}

FUNCTION {math.modulo} { math.div.mod swap$ pop$ }
FUNCTION {math.divide} { math.div.mod pop$ }

FUNCTION {math.char.to.int}
{
  'tmp.char :=
  #0 'parse.tmp.val :=
  #10 'math.res :=

  { parse.tmp.val #10 < }
  {
    tmp.char parse.tmp.val int.to.str$ =
      { parse.tmp.val 'math.res :=
        #10 'parse.tmp.val :=
      }
      { parse.tmp.val #1 + 'parse.tmp.val := }
    if$
  }
  while$
  math.res
}

FUNCTION {prng.parse.pdf.date}
{
  timestamp 'tmp.str :=
  #0 'parse.total :=
  
  #3 'hokuto.counter :=
  { hokuto.counter #17 < }
  {
    tmp.str hokuto.counter #1 substring$ math.char.to.int
    'parse.tmp.val :=
    
    parse.tmp.val #10 <
      { parse.total #10 math.multiply
        parse.tmp.val +
        prng.m math.modulo 'parse.total := 
      }
      { skip$ }
    if$
    hokuto.counter #1 + 'hokuto.counter :=
  }
  while$

  tmp.str #17 #1 substring$ "-" =
    { #-1 'parse.sign := }
    { #1 'parse.sign := }
  if$

  #18 'hokuto.counter :=
  { hokuto.counter #23 < }
  {
    tmp.str hokuto.counter #1 substring$ math.char.to.int
    'parse.tmp.val :=

    parse.tmp.val #10 <
      { parse.total #10 math.multiply
        parse.tmp.val +
        prng.m math.modulo 'parse.total :=
      }
      { skip$ }
    if$
    hokuto.counter #1 + 'hokuto.counter :=
  }
  while$

  parse.total parse.sign math.multiply
  prng.seed + prng.m math.modulo 'prng.seed :=
}

FUNCTION {prng.init}
{ 
  #65   'prng.a :=
  #1    'prng.c :=
  #2048 'prng.m :=
  #0  'prng.seed :=
}

FUNCTION {prng.next.bit}
{ 
  prng.seed prng.a math.multiply prng.c +
  prng.m math.modulo 'prng.seed :=
  prng.seed #1024 math.divide #2 math.modulo
}

FUNCTION {hokuto.attack}
{
  #1   'hokuto.is.alive :=
  #0   'hokuto.counter :=
  #0   'hokuto.combo.count :=
  #300 'hokuto.max.iter :=

  { hokuto.counter hokuto.max.iter < hokuto.is.alive #0 > math.and }
  {
    prng.next.bit #0 = { "あ" } { "た" } if$ 'tmp.char :=
    tmp.char write$

    tmp.char "あ" =
      { #1 'hokuto.combo.count := }
      { tmp.char "た" =
          { hokuto.combo.count #0 > 
              { hokuto.combo.count #1 + 'hokuto.combo.count := } 
              { #0 'hokuto.combo.count := } 
            if$ 
          }
          { #0 'hokuto.combo.count := }
        if$
      }
    if$

    hokuto.combo.count #5 =
      { #0 'hokuto.is.alive := 
        newline$ newline$ "お前はもう死んでいる" write$ newline$
      }
      { skip$ }
    if$
    
    hokuto.counter #1 + 'hokuto.counter :=
  }
  while$
  
  hokuto.is.alive #0 > 
    { newline$ "貴様はこのわしに近寄ることすら出来んのだ" write$ newline$ } 
    { skip$ } 
  if$
}

READ
EXECUTE {prng.init}
ITERATE {prng.parse.pdf.date}
ITERATE {hokuto.attack}
atatatata.tex
\documentclass{jsarticle}
\newwrite\tempfile
\immediate\openout\tempfile=time.bib
\immediate\write\tempfile{@time{now, timestamp="\pdfcreationdate"}}
\immediate\closeout\tempfile
\begin{document}
\bibliographystyle{atatatata}
\bibliography{time}
\nocite{now}
\end{document}

実行方法

latexmkrc
$latex = 'uplatex';
$bibtex = 'upbibtex';
$dvipdf = 'dvipdfmx %O -o %D %S';
$makeindex = 'mendex -U %O -o %D %S';
$pdf_mode = 3; 

END {
    if (-e 'time.bib') {
        print "Cleaning up temporary seed file: time.bib\n";
        unlink 'time.bib';
    }
}
terminal
$ latexmk atatatata
example
出力結果

コードと文法の解説

変数の宣言

ENTRY でフィールド変数とエントリ変数を宣言します. なお, エントリ変数 (第2・3引数) は使用しないため空にしています.

ENTRY
  { timestamp } % フィールド変数名
  { } % 整数型エントリ変数名
  { } % 文字列型エントリ変数名

INTEGERS で整数型グローバル変数を宣言します.

INTEGERS { 
  prng.seed prng.a prng.c prng.m 
  math.lhs math.rhs math.res math.q math.r
  parse.total parse.tmp.val parse.sign
  hokuto.is.alive hokuto.counter hokuto.max.iter hokuto.combo.count 
} % 整数型グローバル変数名

STRINGS で文字列型グローバル変数を宣言します.

STRINGS  { tmp.char tmp.str } % 文字列型グローバル変数名

算術・論理関数の定義

BibTeXには加算 + と減算 - しか算術演算子が組み込まれていないため, FUNCTION で必要な算術・論理関数を定義しています.

論理演算

論理否定

スタックトップが $0$ なら $1$ を, それ以外なら $0$ を返し, 真偽値を反転させています.

FUNCTION {math.not} { { #0 } { #1 } if$ }

論理積

第2引数が真なら第1引数を残し, 偽ならスタックを空にしてから $0$ を残すことで論理積を実現しています.

FUNCTION {math.and} { 'skip$ { pop$ #0 } if$ }

算術演算

乗算

被乗数 (math.lhs) を乗数回 (math.rhs 回) だけ加算するループ処理により, 乗算を実現しています.

FUNCTION {math.multiply}
{
  'math.rhs := 'math.lhs :=
  #0 'math.res :=
  { math.rhs #0 > }
  { math.res math.lhs + 'math.res :=
    math.rhs #1 - 'math.rhs :=
  }
  while$
  math.res
}

除算

被除数 (math.lhs) から 除数 (math.rhs) を繰り返し (math.q 回) 引き, 商 (math.q) と剰余 (math.r) を算出しています.

FUNCTION {math.div.mod}
{
  'math.rhs := 'math.lhs :=
  math.rhs #0 =
    { "Error: Division by zero" warning$ #0 #0 }
    { #0 'math.q :=
      { math.lhs math.rhs < math.not }
      { math.lhs math.rhs - 'math.lhs :=
        math.q #1 + 'math.q :=
      }
      while$
      math.q math.lhs
    }
  if$
}

math.div.mod の結果から商のみを取り出しています.

FUNCTION {math.modulo} { math.div.mod swap$ pop$ }

剰余

math.div.mod の結果から剰余のみを取り出しています.

FUNCTION {math.divide} { math.div.mod pop$ }

文字列解析とシード値の生成

PDFのタイムスタンプ文字列 (例:D:20251223...) を数値に変換し, 乱数の初期シードを動的に生成しています.

文字列から数値への変換

入力文字が数値文字なら数値に変換し, そうでなければ $10$ (エラー値) に変換しています.

f(c) = 
\begin{cases}
i & (\exists i \in \{0, 1, \dots, 9\}\, \text{ s.t. }\, \text{str}(i) = c) \\
10 & (\text{otherwise})
\end{cases}

ここで, $\text{str}(i)$ は整数 $i$ を文字列に変換する関数であり, 実装においてはBibTeXの組み込み関数である int.to.str$ が処理を担っています.

FUNCTION {math.char.to.int}
{
  'tmp.char :=
  #0 'parse.tmp.val :=
  #10 'math.res :=

  { parse.tmp.val #10 < }
  {
    tmp.char parse.tmp.val int.to.str$ =
      { parse.tmp.val 'math.res :=
        #10 'parse.tmp.val :=
      }
      { parse.tmp.val #1 + 'parse.tmp.val := }
    if$
  }
  while$
  math.res
}

日付文字列の解析

PDFのタイムスタンプは D:YYYYMMDDhhmmss±HH'mm' という形式 (例:D:20251223064232+09'00') で記録されています. このうち, 位置3-16の14桁 (年月日時分秒) と位置17の符号, 位置18-22のタイムゾーンオフセット (時分) を数値として抽出し, 以下の式に基いて数値に変換しています.

V_k = (V_{k-1} \times 10 + f(c_k)) \mod m

ここで, 文字列を $S = c_1 c_2 \dots c_n$, 各ステップの累積値を $V_k$, 法を $m$ としています. また, 初期値 $V_{0}$ は $0$ とし, フィルタリングにより数値以外を無視しています.

FUNCTION {prng.parse.pdf.date}
{
  timestamp 'tmp.str :=
  #0 'parse.total :=
  
  #3 'hokuto.counter :=
  { hokuto.counter #17 < }
  {
    tmp.str hokuto.counter #1 substring$ math.char.to.int
    'parse.tmp.val :=
    
    parse.tmp.val #10 <
      { parse.total #10 math.multiply
        parse.tmp.val +
        prng.m math.modulo 'parse.total := 
      }
      { skip$ }
    if$
    hokuto.counter #1 + 'hokuto.counter :=
  }
  while$

  tmp.str #17 #1 substring$ "-" =
    { #-1 'parse.sign := }
    { #1 'parse.sign := }
  if$

  #18 'hokuto.counter :=
  { hokuto.counter #23 < }
  {
    tmp.str hokuto.counter #1 substring$ math.char.to.int
    'parse.tmp.val :=

    parse.tmp.val #10 <
      { parse.total #10 math.multiply
        parse.tmp.val +
        prng.m math.modulo 'parse.total :=
      }
      { skip$ }
    if$
    hokuto.counter #1 + 'hokuto.counter :=
  }
  while$

  parse.total parse.sign math.multiply
  prng.seed + prng.m math.modulo 'prng.seed :=
}

擬似乱数生成

BibTeXには乱数生成関数がないため, FUNCTION で擬似乱数生成関数を実装しています. なお, 擬似乱数生成には線形合同法を使用しています.

$$X_{n+1} = (a \cdot X_n + c) \mod m$$

初期化

擬似乱数生成器 (PRNG) の初期化関数を定義しています. なお, BibTeXで扱える整数範囲1と計算能力の制約を考慮して, $a=65$, $c=1$, $m=2048$ としています.

ちなみに, このPRNGはHull-Dobellの定理を満たすため, $2048$ の完全周期を持ちます.

周期の評価

Hull-Dobellの定理

  1. $c$ と $m$ が互いに素である
  2. $a-1$ が $m$ のすべての素因数で割り切れる
  3. $m$ が $4$ の倍数ならば, $a-1$ も $4$ の倍数

1に関して, $\text{gcd}(1, 2048) = 1$ であり, 満たしている.
2に関して, $m=2^{11}$ の素因数は $2$ のみであり, $a-1 = 64$ は $2$ で割り切れるため, 満たしている.
3に関して, $2048$ は $4$ の倍数であり, $64$ も $4$ の倍数であるため, 満たしている.

FUNCTION {prng.init}
{ 
  #65   'prng.a :=
  #1    'prng.c :=
  #2048 'prng.m :=
  #0  'prng.seed :=
}

ビット生成

次の状態 $X_{n+1}$ を計算し, 法 $m$ の中央値より大きいか小さいかで $0$ または $1$ のビットを生成しています.

FUNCTION {prng.next.bit}
{ 
  prng.seed prng.a math.multiply prng.c +
  prng.m math.modulo 'prng.seed :=
  prng.seed #1024 math.divide #2 math.modulo
}

メイン処理

prng.next.bit を評価してBibTeXの組み込み関数である write$ を用いて「あ」または「た」を出力しています. このとき, 擬似乱数ビット $b_t$ に基づき, 文字 $c_{t}$ が生成されます.

c_t = \begin{cases} \text{あ} & (b_t = 0) \\ \text{た} & (b_t = 1) \end{cases}

また, hokuto.combo.count で「あ → た → た → た → た」という状態遷移を監視しており, hokuto.combo.count が5になるとループを抜けて「お前はもう死んでいる」を出力し, 処理を終了します.
なお, 安全のため, 最大反復回数は300回に制限しています.

FUNCTION {hokuto.attack}
{
  #1   'hokuto.is.alive :=
  #0   'hokuto.counter :=
  #0   'hokuto.combo.count :=
  #300 'hokuto.max.iter :=

  { hokuto.counter hokuto.max.iter < hokuto.is.alive #0 > math.and }
  {
    prng.next.bit #0 = { "あ" } { "た" } if$ 'tmp.char :=
    tmp.char write$

    tmp.char "あ" =
      { #1 'hokuto.combo.count := }
      { tmp.char "た" =
          { hokuto.combo.count #0 > 
              { hokuto.combo.count #1 + 'hokuto.combo.count := } 
              { #0 'hokuto.combo.count := } 
            if$ 
          }
          { #0 'hokuto.combo.count := }
        if$
      }
    if$

    hokuto.combo.count #5 =
      { #0 'hokuto.is.alive := 
        newline$ newline$ "お前はもう死んでいる" write$ newline$
      }
      { skip$ }
    if$
    
    hokuto.counter #1 + 'hokuto.counter :=
  }
  while$
  
  hokuto.is.alive #0 > 
    { newline$ "貴様はこのわしに近寄ることすら出来んのだ" write$ newline$ } 
    { skip$ } 
  if$
}

実行

READ.bib ファイルからデータを読み取り, EXECUTEprng.init を実行しています. その後, ITERATE で各エントリに対して prng.parse.pdf.datehokuto.attack を実行しています.

READ
EXECUTE {prng.init}
ITERATE {prng.parse.pdf.date}
ITERATE {hokuto.attack}

動的シードの生成と結果の組版

動的シードを得る手段として, 実行時刻がよく用いられます. しかし, BibTeXだけでは実行時刻を取得することができないため, pdfTeXやe-pTeX, e-upTeXに実装されている \pdfcreationdate を用いて実行時刻を取得し, @time エントリの timestamp フィールドに取得した実行時刻を格納しています. そして, そのエントリを time.bib に書き込んでいます.

\documentclass{jsarticle}
\newwrite\tempfile
\immediate\openout\tempfile=time.bib
\immediate\write\tempfile{@time{now, timestamp="\pdfcreationdate"}}
\immediate\closeout\tempfile
\begin{document}
\bibliographystyle{atatatata}
\bibliography{time}
\nocite{now}
\end{document}

BibTeXの歴史

BibTeXの歴史を知るには, まずLaTeXの歴史について知る必要があります2. そのため, 本セクションでは, LaTeXの歴史の後にBibTeXの歴史について紹介します.

なお, 本セクションにおける内容及び引用文は全て「BibTEX yesterday, today, and tomorrow」から引用したものです.

LaTeXの誕生

TeXのマクロパッケージであるLaTeXは, Leslie Lamportにより1980年代前半から開発され始めました. Lamportは, Brian Reidが開発したScribeという文書作成システムをモデルとしてLaTeXを設計しており, Scribeの「コンピュータプログラムで可能な範囲において, 執筆者は書式設定の詳細ではなく内容そのものに集中できるようにすべきである (to the extent possible with a computer program, writers should be allowed to concentrate on content rather than on formatting details)」という基本理念を踏襲しています.

Lamport自身も, LaTeXはScribeと同じくらい使いやすく, かつTeXと同じくらい強力であると述べています.

I think of LaTeX as a fairly successful Scribification of TeX — LaTeX is almost as easy to use as Scribe yet almost as powerful as TeX.

BibTeXの誕生

Scribeは特定の学術界で普及していたため, LamportはScribeユーザがLaTeXへ移行しやすくするために, Sclibeの文献管理方式をLaTeXにも採用することにしました. しかし, TeXのマクロだけではアルファベット順並べ替えなどの文献処理に必要な全ての機能を実用的に実現するには不十分でした. そこで, Lamportは文献処理専用の独立したプログラムを開発することにし, そのプログラム開発にOren Patashnikが推薦されました. このプログラムが後のBibTeXです.

BibTeXの最初の動作バージョンであるBibTeX 0.41は1984年の5月に完成しました. この時, Mary-Claire van Leunenの「Handbook for Scholars」で示された提案に基づいてLamportが作成し, Howard Trickeyが修正した文献スタイルが後の btxbst.doc であり, 現在のBibTeX標準スタイルである plain, abbrv, alpha, unsrt です.

そして, 1985年3月にLaTeX 2.08に合わせてBibTeX 0.98が一般公開され, その後, 1988年にBibTeX 0.99が公開されました. この頃, PatashnikはBibTeX 1.0を最終的な凍結版とすることを目指し, 1995年末にはベータ版を公開すると述べていましたが, 現時点でもBibTeX 0.99eに留まっています3.

おまけ:BibTeXスタイルファイルの記述言語

BibTeXスタイルファイル (.bst) を記述するための言語には正式名称がありません. BibTeXの開発者であるOren Patashnikが執筆した「Designing BIBTEX Styles」においても, "an unnamed language" として扱われています.

Basically the style file is a program, written in an unnamed language, that
tells BibTEX how to format the entries that will go in the reference list (henceforth “the entries” will be “the entry list” or simply “the list”, context permitting).

なお, 一部のコミュニティでは, BAFLLやBeaSTと呼ばれています.

BAFLLの由来

BAFLLとは, BibTeX Anonymous Forth-Like Languageの略で, comp.lang.lispにおいてDrew McDermottが投稿した「Bibtex in Lisp?」というスレッドが起源です. Forthのような特徴を持つ名もなき言語であることに由来しています.

It occurs to me that a better acronym is BAFLL: Bibtex Anonymous Forth-Like
Language.

BeaSTの由来

BeaSTとは, Nicolas Markeyが執筆したBibTeXのチュートリアル文書である「Tame the BeaST: The B to X of BibTEX」を起源とする呼称です. BeaSTBSTはBibTeXスタイルファイルの拡張子である .bst を表しています. また, サブタイトルに含まれる "B to X" はBibTeXの頭文字 "B" と末尾文字 "X" を示しており, BibTeXを網羅的に (隅から隅まで) 解説した文書であることが表現されています.

個人的な感想

本来は「BAFLL/BeaST編」とすべきな気がしますが, 本文中でBibTeXとBAFLL/BeaSTの使い分けがややこしくなったため, 「BibTeX編」としました.
(本アドカレのプログラムを実装する上では) 変な言語です.

  1. 整数型エントリ変数の整数範囲が記述された文書が見当たらなかったため, 私の環境 (TL 2025, macOS) で実験したところ, 整数型エントリ変数は32bit符号付き整数でした.

  2. TeXの歴史には触れませんし, TeX由来のLaTeXの開発動機にも触れません.

  3. BibTeX 0.99e は, 2025-10-02にリリースされました.

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?