LoginSignup
2
4

More than 3 years have passed since last update.

すべてのプログラミング言語をLaTeXで書こう:DocStripプログラムの紹介

Posted at

DocStripプログラムとは?

DocStripプログラムとは,ソースファイルから特定の部分だけを抜き出す機能を実装したプログラムのことです.この機能により1つのソースファイルからソースコードとそのドキュメントを作成することができます.ドキュメント作成にはTeX/LaTeXを使用します.

言葉で説明するよりも,実際の例を見た方が早いでしょう.次の節に実行例を載せておきます.

なお,既にDocStripプログラムを使ったことがある方にお伝えしたい事として,ここで紹介するコードでは.insファイルが不要になります1

DocStripプログラムの実行例

実行環境

  • Ubuntu 20.04
  • TeX Live 2019

サンプルコード

まず,次のsample-code.dtxを保存してください.


sample-code.dtxを表示する
sample-code.dtx
% \iffalse
%<*driver>
\begingroup
\input l3docstrip
\askforoverwritefalse
\keepsilent
\nopreamble
\nopostamble
\generate{\file{trapezoidal-rule.c}{\from{\jobname.dtx}{code}}}
\endgroup
\documentclass{ltjltxdoc}
\usepackage{amsmath,minted,tcolorbox,tikz,hyperref}
\newminted{c}{bgcolor=gray!10, firstnumber=last, linenos}
\renewcommand{\figurename}{}
\begin{document}
  \DocInput{\jobname.dtx}
\end{document}
%</driver>
% \fi
%
% \title{数値積分}
% \maketitle
% \tableofcontents
% \newpage
%
% \section{台形公式}
%
% \subsection{導出}
% ^^A defining f(x)
% \def\myfunction{{0.3*(\x^3 - 7.6*\x^2 + 18.3*\x) - 2.5}}
%
% \begin{figure}[h]
% \centering
% \begin{tikzpicture}[domain=1:4, >=stealth]
% \coordinate (a) at (1,0);
% \coordinate (b) at (4,0);
% \coordinate (fa) at (1,1);
% \coordinate (fb) at (4,2.2);
% \filldraw[fill=blue!10!white] (a) -- plot (\x, \myfunction) -- (b);
% \draw[blue, very thick] plot (\x, \myfunction);
% \draw[->, thick] (-0.3,0) -- (5,0) node[below] {$x$};
% \draw[->, thick] (0,-0.3) -- (0,3) node[left] {$y$};
% \draw (0,0) node[below left] {$O$};
% \draw (a) node[below] {$a$};
% \draw (b) node[below] {$b$};
% \draw[<-] (1.7,1.8) -- (1.5,2.3) node[above] {$y=f(x)$};
% \draw (2.5,0.7) node {$\displaystyle \int_a^b f(x)\,dx$};
% \end{tikzpicture}
% \caption{$\displaystyle \int_a^b f(x)\,dx$の図形的意味}
% \label{fig:integration}
% \end{figure}
%
% $\forall x\in[a,b]: f(x)\geq0$である関数$f$に対し,
% 定積分$\displaystyle\int_a^b f(x)\,dx$は,曲線$y=f(x)$と$x$軸,
% および2直線$x=a, x=b$で囲まれた図形の面積に等しい(図\ref{fig:integration}).
%
% \begin{figure}[h]
% \centering
% \begin{tikzpicture}[domain=1:4, >=stealth]
% \filldraw[fill=red!10!white] (a) -- (fa) -- (fb) -- (b);
% \draw[blue, very thick] plot (\x, \myfunction);
% \draw[red, thick] (fa) -- (fb);
% \draw[->, thick] (-0.3,0) -- (5,0) node[below] {$x$};
% \draw[->, thick] (0,-0.3) -- (0,3) node[left] {$y$};
% \draw (0,0) node[below left] {$O$};
% \draw (a) node[below] {$a$};
% \draw (b) node[below] {$b$};
% \draw[<-] (1.7,1.8) -- (1.5,2.3) node[above] {$y=f(x)$};
% \draw (2.5,0.6) node {$\displaystyle (b-a) \frac{f(a)+f(b)}{2}$};
% \end{tikzpicture}
% \caption{一次近似}
% \label{fig:linear-approximation}
% \end{figure}
%
% $f(x)$を一次関数で近似すると,この図形は台形となる(図\ref{fig:linear-approximation}).
% したがって,この場合,$\displaystyle \int_a^b f(x)\,dx$は次のように近似される.
% \begin{equation}
%   \int_a^b f(x)\,dx \approx (b-a) \frac{f(a)+f(b)}{2}
% \end{equation}
%
% \begin{figure}[h]
% \centering
% \begin{tikzpicture}[domain=1:4, >=stealth]
% \coordinate (a1) at (1.4,0);
% \coordinate (fa1) at (1.4,1.53);
% \coordinate (a2) at (2.2,0);
% \coordinate (fa2) at (2.2,1.75);
% \coordinate (an-1) at (3.7,0);
% \coordinate (fan-1) at (3.7,1.8);
% \filldraw[fill=red!10!white] (a) -- (fa) -- (fa1) -- (a1);
% \filldraw[fill=red!10!white] (a1) -- (fa1) -- (fa2) -- (a2);
% \filldraw[fill=red!10!white] (an-1) -- (fan-1) -- (fb) -- (b);
% \draw[blue, very thick] plot (\x, \myfunction);
% \draw[red, thick] (fa) -- (fa1);
% \draw[red, thick] (fa1) -- (fa2);
% \draw[red, thick] (fan-1) -- (fb);
% \draw[->, thick] (-0.3,0) -- (5,0) node[below] {$x$};
% \draw[->, thick] (0,-0.3) -- (0,3) node[left] {$y$};
% \draw (0,0) node[below left] {$O$};
% \draw (a) node[below] {$a$};
% \draw (b) node[below right] {$b$};
% \draw (a1) node[below] {$a_1$};
% \draw (a2) node[below] {$a_2$};
% \draw (an-1) node[below] {$a_{n-1}$};
% \draw[<-] (1.7,1.8) -- (1.5,2.3) node[above] {$y=f(x)$};
% \draw (3,0.7) node {$\dots\dots$};
% \end{tikzpicture}
% \caption{台形公式}
% \label{fig:trapezoidal-rule}
% \end{figure}
%
% ここで,積分値の精度を上げるために,
% \[
%   a=a_0 < a_1 < \dots < a_n = b
% \]
% として積分区間$[a, b]$を$n$個の閉区間$[a_0,a_1], [a_1,a_2], \dots, [a_{n−1},a_n]$に分け,
% それぞれの区間を同様に一次近似する(図\ref{fig:trapezoidal-rule}).
%
% このとき,$\displaystyle \int_a^b f(x)\,dx$は次のように近似される.
% \begin{align*}
%   \int_a^b f(x)\,dx
%   = & \int_{a_0}^{a_1} f(x)\,dx
%   +\int_{a_1}^{a_2} f(x)\,dx
%   +\dots
%   +\int_{a_{n-1}}^{a_n} f(x)\,dx \\
%   = & \sum_{k=0}^{n-1} \int_{a_k}^{a_{k+1}} f(x)\,dx \\
%   \approx & \sum_{k=0}^{n-1} (a_{k+1} - a_k) \frac{f(a_k)+f(a_{k+1})}{2}
% \end{align*}
%
% ここで,$n$個に分割された区間の幅が等間隔の場合,
% すなわち,任意の$k=0,1,\dots,n-1$に対し,
% \begin{equation}
%   a_{k+1}-a_k=\frac{b-a}{n}
% \end{equation}
% となる場合を考える.このとき,
% \begin{equation}
%   a_k=a+k\frac{b-a}{n} \quad (k=0,1,2,\dots,n)
% \end{equation}
% であるから,$\displaystyle \int_a^b f(x)\,dx$は次のようになる.
% \begin{align*}
%   \int_a^b f(x)\,d{x}
%   \approx & \sum_{k=0}^{n-1} (a_{k+1} - a_k) \frac{f(a_k)+f(a_{k+1})}{2} \\
%   = & \frac{b-a}{2n}\sum_{k=0}^{n-1}(f(a_k)+f(a_{k+1})) \\
%   = & \frac{b-a}{2n}\left(f(a_0) + 2\sum_{k=1}^{n-1}f(a_k) + f(a_n)\right) \\
%   = & \frac{b-a}{n}\left(\frac{f(a)+f(b)}{2} + \sum_{k=1}^{n-1}f\left(a+k\frac{b-a}{n}\right)\right)
% \end{align*}
%
% 以上をまとめると,次のようになる.
% \begin{tcolorbox}
% \begin{equation}
%   \label{eq:trapezoidal-rule}
%   \int_a^b f(x)\,dx \approx \frac{b-a}{n}
%     \left(\frac{f(a)+f(b)}{2} + \sum_{k=1}^{n-1}f\left(a+k\frac{b-a}{n}\right)\right)
% \end{equation}
% \end{tcolorbox}
%
% \subsection{\texttt{trapezoidal-rule.c}のソースコード}
% \iffalse
%<*code>
% \fi
%
% \subsubsection{ヘッダファイルのインクルード}
% \begin{ccode}
#include <math.h>
#include <stdio.h>
% \end{ccode}
%
% \subsubsection{関数プロトタイプ}
% \begin{ccode}
double integrand(double x);
double trapezoidal_rule(double (*f)(double x), double a, double b, int n);
% \end{ccode}
%
% \subsubsection{main関数}
% 次のコードは,積分区間の分割数$n=1000$のときの$\displaystyle \int_1^3f(x)\,dx$の値を表示する.
% \begin{ccode}
int main(void)
{
  printf("Result: %f\n", trapezoidal_rule(integrand, 1, 3, 1000));
  return 0;
}
% \end{ccode}
%
% \subsubsection{被積分関数の定義}
% \begin{ccode}
double integrand(double x)
{
  return x * sin(x);
}
% \end{ccode}
%
% \subsubsection{台形公式の実装}
% 式(\ref{eq:trapezoidal-rule})を実装する.
% \begin{ccode}
double trapezoidal_rule(double (*f)(double x), double a, double b, int n)
{
  int k;
  double sum = 0.0;
  for (k = 1; k < n; k++)
    sum += f(a + k*(b-a)/n);
  return (b-a)/n * ((f(a) + f(b))/2 + sum);
}
% \end{ccode}
%
% \iffalse
%</code>
% \fi
%
% \section{Simpson公式}
%
% \subsection{導出}
% ...
%
% ^^A End of file `sample-code.dtx'.


上記のsample-code.dtxに対して,次のコマンドを実行してみてください2

$ lualatex -shell-escape sample-code.dtx

すると次のtrapezoidal-rule.csample-code.pdfの2ファイルが生成されるはずです3


trapezoidal-rule.cを表示する
trapezoidal-rule.c
#include <math.h>
#include <stdio.h>
double integrand(double x);
double trapezoidal_rule(double (*f)(double x), double a, double b, int n);
int main(void)
{
  printf("Result: %f\n", trapezoidal_rule(integrand, 1, 3, 1000));
  return 0;
}
double integrand(double x)
{
  return x * sin(x);
}
double trapezoidal_rule(double (*f)(double x), double a, double b, int n)
{
  int k;
  double sum = 0.0;
  for (k = 1; k < n; k++)
    sum += f(a + k*(b-a)/n);
  return (b-a)/n * ((f(a) + f(b))/2 + sum);
}


以下がsample-code.pdfの内容です.元ファイルは ここ でも見れます.

combine-1.png

このサンプルコードでは.dtxファイルから.cファイルを生成しましたが,任意のプログラミング言語のファイルを生成できます.

DocStripプログラムを使うメリット

  1. ソースコードとそのドキュメント・マニュアルを1つのファイルで管理できる
  2. 任意のプログラミング言語ファイルを生成できる
  3. ドキュメントに数式や図・表を記載できる

1.について,例えばあるソースコードのドキュメントを作成していたとします.もしそのコードとドキュメントを別ファイルで管理していたら,コードを変更する度にドキュメント側に記載しているコードも変更する必要があります.いちいち変更するのは手間ですし,コードの転記ミスも発生し得ます.しかし,DocStripプログラムではそういったマイナスポイントがありません.

2.について,上記の例で.dtxファイルから.cファイルを生成したように,.dtxファイルからあらゆる言語ファイルを生成できます.さらに言うと,DocStripプログラムは1つの.dtxファイルから複数の異なる言語ファイルを生成できます.その方法は後で説明します.

3.について,上記例のドキュメント(.pdfファイル)のように,数式や図・表を記載できます.すなわち,LaTeXができることは何でもできます.したがって,索引の自動作成やマクロを組むこともできます.コードについての簡単なメモ書きであれば,コメントとして直接ソースファイルに記述できますが,さすがに上記例のような数式や図は記述できないでしょう4

テンプレート

.dtxファイルのテンプレートを記載します.各コマンドの役割は次節で説明します.

基本的な編集箇所は以下になるかと思います.

  • \file{foo}{\from{\jobname.dtx}{bar}}foobar
    • fooは生成ファイル名
    • barはガード
  • \fi以降にドキュメント化するコードを記述
  • 必要に応じて\usepackage{baz}を追加
template.dtx
% \iffalse
%<*driver>
\begingroup
\input l3docstrip
\askforoverwritefalse
\keepsilent
\nopreamble
\nopostamble
\generate{\file{foo}{\from{\jobname.dtx}{bar}}}
\endgroup
\documentclass{ltjltxdoc}
\begin{document}
  \DocInput{\jobname.dtx}
\end{document}
%</driver>
% \fi
%
% \title{Template}
% \maketitle
% \tableofcontents
%
% \section{***}
% ...
% ...
% ...
%
% ^^A End of file `template.dtx'.

各コマンドの役割

ここでは上記template.dtx内の各コマンドの役割を説明していきます.より詳しい説明は docstrip.pdf を参照してください.

\iffalse\fi

\DocInputから読み込まれるとき,\iffalse\fiで囲まれた部分は読み飛ばされます.\iffalse\fiで囲まないとエラーが発生します.

<*driver></driver>

driverガードを設定します.「ガード」という用語については後述します.

実は,<*driver> ... </driver>で囲まなくてもエラーが発生することなくコンパイルできますが,生成ファイルに余計な記述が追加されてしまいます.

\begingroup\endgroup

\begingroup\endgroupで囲まれた部分をグループ化します.これによりDocStripプログラムの影響がグループ内にしか及ばないようにします.

\input l3docstrip

DocStripプログラムを利用するための記述です.これによりl3docstrip.tex内に定義されているコマンドが使えるようになります.

\askforoverwritefalse

生成したいファイル名のファイルが既に存在する場合,上書きしてよいか確認を求められます.ただ,\askforoverwritefalseを書いておくことにより,いちいち確認されずに済みます.

逆に上書きの確認をしたいときは\askforoverwritetrueを記述しておくとよいでしょう.

\keepsilent

コンパイル時にDocStripプログラムの処理状況がターミナルに表示されますが,\keepsilentを記述しておくことでそれが表示されません.

\nopreamble\nopostamble

\nopreambleは生成ファイルにプリアンブルを記述しないようにするためのコマンドです.同様に,\nopostambleはポストアンブルを記述しないようにするためのコマンドがです.

\generate{ ... }

\generateコマンドでファイルを生成します.例えば,次のコードではbarガードで囲まれた部分をfooファイルに書き込みます.

\generate{\file{foo}{\from{\jobname.dtx}{bar}}}

ここでガードとは%<...>のことで,ガードの始まりは%<*...>,終わりは%</...>で記述します.

例えば,次のguard-example.dtxに対して,$ lualatex guard-example.dtxを実行します.


guard-example.dtxを表示する
guard-example.dtx
% \iffalse
%<*driver>
\begingroup
\input l3docstrip
\askforoverwritefalse
\keepsilent
\nopreamble
\nopostamble
\generate{\file{output-file.txt}{\from{\jobname.dtx}{myguard}}}
\endgroup
\documentclass{ltjltxdoc}
\begin{document}
  \DocInput{\jobname.dtx}
\end{document}
%</driver>
% \fi
%
%<*myguard>
text1
% comment
text2
%</myguard>
%
% ^^A End of file `guard-example.dtx'.


すると,次のようなoutput-file.txtが生成されます.

output-file.txt
text1
text2

また,複数個ファイルを生成することもできます.例えば,次のコードではqiita.mdsample.pytest.cppというファイルを生成できます.

\generate{
  \file{qiita.md} {\from{\jobname.dtx}{qiita}}
  \file{sample.py}{\from{\jobname.dtx}{sample}}
  \file{test.cpp} {\from{\jobname.dtx}{test}}
}

\jobname

\jobname.dtxファイル名に置き換わります.例えば,my-source-code.dtxであれば\jobnamemy-source-codeに置き換わります.

\DocInput{ ... }

\DocInput{ ... }の引数で指定されたファイルを読み込みます.その際,%をコメントと解釈せずに処理していきます.

つまり,\DocInput{\jobname.dtx}とすることにより,自分自身のファイルにあるコメント行もLaTeXのコードとして解釈してドキュメント化してくれます.


  1. 何を言っているかわからない方は無視しても問題ありません. 

  2. 複数回実行する必要があります. 

  3. その他の形式のファイルも同時に生成されます.不要であれば$ latexmk -c sample-code.dtxなどで削除して問題ありません. 

  4. アスキーアートで表現することも可能かもしれませんが,あまり現実的ではありません. 

2
4
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
4