プログラムdeタマゴ

nodamushiの著作物は、文章、画像、プログラムにかかわらず全てUnlicenseです

C++のCMSIS5 SVD読み込みライブラリ作った

 ブーメランという言葉をご存じだろうか。そう、今は無き民主党のあれである。

C++ header only library for CMSIS SVD

github.com

 はい、というわけで、私が必要になりました。C++のSVD解析器。畜生。

 その成果を公開しておきます。

 

ざっくばらんな使い方

 使い方は大体こんな感じ。

#include "nodamushi/svd.hpp"
#include "nodamushi/svd/boost.hpp"
#include "nodamushi/svd/normalized.hpp"

void read(std::string& filename){
   namespace svd = nodamushi::svd;
   svd::boost_svd_reader reader(filename);
   svd::Device<> device(reader);
   auto ptr = svd::normalize(device); // derivedFromやdimの解決
}

 これだけでさくっと読み込めて、derivedFromの面倒な解決もしてくれるので、まぁまぁ便利なんちゃう?

 Visitor パターンも用意してあるので、各要素にアクセスするプログラムも割とサクッと書けます。

//visitor パターン
struct visitor{
  //お作法
  using this_t = visitor;
#undef VISITOR_MEMBER
#define VISITOR_MEMBER static
  NORM_VISITOR_INIT;
  //お作法 ここまで
  
  NORM_Visit_Register(r){
    std::cout << "Register:" <<r.get_path() << std::endl;
    return CONTINUE;
  }

  NORM_Visit_Interrupt(i){
     std::cout << "Interrupt:" << i.name << "," << i.value<<std::endl;
     return CONTINUE;
  }
};

visitor::accept(ptr) ;  //visitorパターンに投げる

 実際に私が必要になった機能とかも大凡作ってあるので、まぁまぁ実用に耐えられるんじゃないかな。これ、誰か使ってるの?っていう様な仕様も、それなりにきちんと解析してます。

 cluster周りはあんまりテストしてないけど………。

 エラー処理は甘いので、その内追々修正するかな。

 

C++ワカラン…

 ちなみに、これを最初から作ったのではなく、一回別に作った物を、腹立ち紛れに作り直しました。

 C++の魔窟メタプログラミングに私がどれだけ耐えられるのか、試してみたかったのもあります。 気が向いたらソースコードを見て貰えると良いかもしれませんが、なんでここまで複雑に作ったのって言うぐらい複雑です。 実務でこんなコード書くやついたら、人格を否定しても許されると思います。

 そして、私、よく分かった。 C++ゼッンゼンワカラン。 とても辛い。

 Rustが良くコンパイラとの格闘があるとか何とか言うじゃない? 私はあまりRustやりこんでないから分からんけど、C++のはコンパイラとの死闘だったよ。

コンパイラちゃん「ここ、ここがおかしいの!」

私「………ソコ、ナニモオカシクナイヨ」

 もうこんなことがしょっちゅう。

 あくまでコンパイラがエラーに気がついた場所で会って、実際のエラー原因は遙か遠いところにあったりするの。 何度コンパイラにブレークポイントぶち込みたいと思ったことか。

 ひたすら長大なエラーレポートを追っていって、どこで型が化けたのかを解析するんだけど、結局は大体の原因は以下の5つに分類されると思う。

  • いつの間にかconstがついてた・消えてた
  • いつの間にか参照がついてた
  • いつの間にかポインタが消えてた
  • いつの間にか暗黙に変換されてた
  • 勘違いによりそもそも型・変数が違う(タイポ含む)

 こういう、なんか思ってたんと違う型の化けにより、本来私が進むと思ってるルートを外れて、別な関数コールを走り始め、問題にぶち当たったところでエラーが出される訳です。

 もう、ホント辛いよ。二度とやりたくないね。

 私はC++上級者になれなくていいや!

helm-swoop--editが動かない

久しぶりにHelm Swoop Editを使おうとしたら

apply: No such action ‘helm-swoop--edit’ for this source

って言うて動かない。どうやら、Helm本体の仕様が変わったらしく、1年放置されているhelm-swoopが仕様変更に対応してないのが問題っぽい。

github.com

で、このイシューのashiklom氏の言う様に~/.emacs.d/elpa/helm-swoop-20180215.1154/helm-swoop.elのhelm-c-source-swoop関数のaction(547行目あたり)とhelm-multi-swoop--exec(1126行目あたり)に

("Edit" . helm-swoop--edit)

を追加して、byte-reconpile-fileして、file-loadをしたら、動きました。マジありがたい!

ashiklom氏のパッチ↓ github.com

組み込み屋さんのC++学習は普通にやっちゃ駄目って話

 周囲の組み込み屋さんを闇のC++プログラマに堕落させたいと目論む訳ですが、組み込み屋さんが普通のC++の教科書を片手にお勉強しちゃ駄目だよねって話。特に取り纏めもないメモ書きなんだけど。

 私はもう流石にC++の入門書は手元にないのですが、例えば、私もかつては書籍を持っていたロベールのC++教室などは、第一章をすっ飛ばすと第二章でクラスの話に入っていく訳です。

 普通それでいいんですけど、組み込みのリソース少ない世界において、オブジェクト指向は不要とまで言い切りはしませんが、必須じゃありません。

 それどころか、new,delete,virtual辺りは害悪と言っても良いでしょう。コンストラクタとかも、場合によっては危険ですね。staticだったり、グローバルな変数の初期化順序とか絡むので。なによりvolatileが絡むとクッソ面倒くさい。

 メモリ小さいから殆どスタックに変数を配置することもないですし、極論を言ってしまえば、組み込みのC++で有効なオブジェクト指向は、デストラクタ以外に特に見当たりません。ステップ数増えるからポインタによる間接参照は極力したくないしね。その辺はコードサイズとの兼ね合いもあるけど。

 

 C言語からの差分でC++を勉強するんだったら、大体、以下の内容を勉強すれば良いんじゃないかなぁ。

 

  1. 組み込みC++で安易に使ってはならない機能
    1. オブジェクト指向
    2. 予約語:new,delete,virtual
    3. STL : string,vector,mapなど、ていうかstd::arrayとstd::tuple以外の殆どのクラス。
    4. staticローカル変数、グローバル変数でコンストラクタを持つデータ使う
  2. extern "C"
  3. 関数
    1. 参照
    2. デフォルト引数
    3. inline関数
    4. constexpr
  4. static_assert
  5. 構造体
    1. staticな変数
    2. staticな関数
    3. 構造体の中で構造体を定義する
    4. デストラクタによるRAII
  6. namespace
    1. using namespace
    2. inline namespace
    3. typedef,using
  7. templateメタプログラミング
    1. 特殊化とか
    2. パック・アンパック
      1. sizeof...
      2. swallow
    3. using
      1. typename,templateの使い方
    4. 型(struct)を用いた静的なポリモーフィズム
    5. tuple,type_traits,utility,initializer_listヘッダ
  8. マクロ
    1. 可変長引数マクロ

 

 ま、何にせよ、普通に学習してstd::coutだの、string、vectorだの、クラスだのを勉強するのは、無駄とは言わないけど、後で良いと思います。

組み込み屋こそC++を使うべきなのです

 やーやーやー。なんとワタクシ、遂に脱8bitしました!ワーワー、ドンドンパチパチ!

 さて、組み込みでもRustがアツかったり色々しますね。ただまぁ、Rustってサポートや車載対応等が無いので、政治的ごにょごにょでちょっと使いにくいですよね。

 だから、今まで通りC言語………って決めてしまう前に、ちょっと待って、まだ選択肢有るじゃない。

 そう、C++ですよ!

 

新しい言語とか無理

 安心してください。C++は昭和の言語です(1983年)。昭和ですよ、ショーワ。世界大戦があった時代の言語です。今は令和ですよ。断言しますが、この記事書いてる現在、令和生まれの若い子って、昭和とかもう知らないですよ。古すぎるといって過言ではありません。

 

そうじゃない オブジェクト指向とか無理

 安心してください。C++はオブジェクト指向言語ではありません。C++は 魔留血原太武言語 組み込み屋向けに嬉しい機能がいっぱいある言語です。

 単に組み込みって言ってしまうと、かなり範囲が広いので一概には言えませんが、特にRTOSも使わない様な環境では、実はオブジェクト指向ってしっくりこないんです。staticを理解していない人のコードを見ると、いちいちインスタンス宣言しているので笑ってしまう。

 オブジェクト指向なんて、メモリとポインタ使ってなんぼです。リソースも計算時間も無い系な組み込みには向いてません。オブジェクト指向なんて、組み込み屋が使えなくても問題ありません。newとか組み込みでは死語です。vector、string?使わねぇって。

 組み込み屋が理解すべきは、C++の 魔境 templateとconstexprと型システムです。

 え、潤沢に富豪プログラムができる環境だって?JavaとかPythonとか使えば良いんじゃないかな。

 

気分はtemplate constexpr!

 C言語で、あるペリフェラルのレジスタのビットに書き込みたいとき、どう書くでしょうか?こう?

PIYO->CTRL = 0x10;
FOO->EN     = 1 << 15 | 1 << 20 | 1 << 1;

 

 あ、ちなみに、場合によりけりだけど、以下の様にビットフィールドを使うのは駄目です。

 FOO->EN_b.EN15=1;  FOO->EN_b.EN20=1; FOO->EN_b.EN1=1;

 通常、ペリフェラルのレジスタはvolatileだから、上記のコードはread-modify-writeが3回走ります。一括で初期化したい場合には向いてないですね。

 C++なら、こんな感じでかけます。

// ----------- どこかのヘッダに書いておく -------------------
// ビットを立てない場合は0
constexpr uint32_t bit(){return 0;}

// ビットの計算をする関数
template<typename... ARGS> //←ここは今は気にしない
constexpr uint32_t bit(size_t a,ARGS... args){ //ARGS...も気にしない
  return (1 << a) | bit(args...); //bit(args...)も気にしない
}
//-----------------------------------------------------------------

FOO->EN = bit(1,15,20);

 1,15,20を引数とするbit関数の呼び出しになっていますが、C言語と違って、最適化ビルドをしなくても、この関数の計算をコンパイル時にさせてしまう機能がC++にはあります。この関数の実行時の計算コストは0で、C言語で直値で書いたのと同じです。恐ろしいですね。

 

   

constexprな変数を使ってみよう

 constexprは定数式と言います。C言語では定数を#defineで名前を付けていました。C++では、これをconstexprで記述します。

#define            FOO_MODE1_CONFIG   1 << 1 | 1 << 21
constexpr uint32_t FOO_MODE1_CONFIG = 1 << 1 | 1 << 21;

 もはや理解が追いつかない!と言う程の大した差は無いですね。

 

 組み込み屋にとって、一番の大きな違いは、マクロ特有のバグが入らないことです。

 例えば、以下のコードは、#defineとconstexprでは結果が変わります。

FOO->CONFIG = FOO_MODE1_CONFIG + 0x1;

 define文はあくまで文字列置換なので、「1 << 1 | 1 << 21 + 0x1」となり、結果的に「1 << 1 | 1 << 22」と同じになりますね。このバグを避ける為に、defineを書くときはヒステリックな程に()で式を囲います。

 constexprでは特に()で囲ったりしていませんが、正しく「FOO_MODE1_CONFIGの値と、0x1の加算」となります。

 

constexprな変数?定数?

 ところで、constexprを外すと、普通の変数宣言と同じになります。

constexpr uint32_t FOO_MODE1_CONFIG = 1 << 1 | 1 << 21;
          uint32_t FOO_MODE1_CONFIG = 1 << 1 | 1 << 21;

 ということは、どこかに変数用の領域が用意されていて、それを読み出しているのだろうと思われるかも知れません。

 対象デバイスの命令セットに依存しますが、今回のFOO_MODE1_CONFIGは大きな値なので、命令の直値にはならないでしょう。多分、普通の定数と同じく、関数の後ろに値が配置されるかと思われます。FOO_MODE1_CONFIGが非常に大きな配列やら構造体なら、Flashのどこかに1箇所だけ置かれるかも知れません。

 しかし、逆に、FOO_MODE1_CONFIGが1とか100みたいな小さな数であれば、命令の直値に変換されることでしょう。

 constexprな変数は、書き換え不可能な変数であり、かつ、直値にもなり得えます。直値として埋め込まれ、誰も実体を必要としなければ、最終的なバイナリに残りません。

 マクロと違って安全で、それでいて、マクロと同じく直値にもなり得る。こんな素晴らしい機能があるのに組み込みで使わないなんて信じられません。さぁ、C++を使いましょう。

 

constexprな関数を使ってみよう

 constexprな変数FOO_MODE1_CONFIGには問題があります。

 MODEが10個くらいあったらどうするというのだろうか。全部書くのか? FOO_MODE A_X_1 CONFIGとか、プロパティ増えたらどーすんの!?

 これを解決してくれるのがconstexprな関数です。

 例えば、FOO_MODEx_CONFIGは「1 << x | 1 << 21」としよう。

constexpr uint32_t FOO_MODE_CONFIG(int x){  return 1 <<  x  |  1 << 21; }

//使う場合
FOO->CONFIG = FOO_MODE_CONFIG(1) + 0x1;

 

 関数なので、全部のモードについて定義をしなくても、勝手に値が求まります。

 そして、C++コンパイラはconstexprな関数を定数だけで呼び出している場合、勝手に計算して、定数にしてくれます。 実行時には関数呼び出しが行われませんので、計算コストはタダです。

 なお、実行時に値が決まる変数などで呼び出すと、実行時に関数を呼び出します。constexprな関数は、定数としても、関数としても使うことができます

 文法も単純で、ふつーの関数定義にconstexprがついているだけです。非常に簡単ですね。

 

 と、言いたいのですが、constexprな関数は場合によってはちょっと面倒くさくなります。

 例えば、 \sum_{n = 0}^x nを愚直に0+1+2+3....と計算するconstexprな関数はこう書きます。なお、愚直じゃない計算は  \frac{x(x+1)}{2}ですね。

constexpr uint32_t sum(uint32_t x)
{
  return 
    x == 0? 0 :   
    x == 1? 1 :
            2 * (sum(x/2) + sum((x-1)/2)) + (x +1)/2;
}

 ちょっと何書いてるのか意味が分からないですね。これ書いた私は、非常に頭が悪いですね。私にもさっぱり分かりません。

 こんな頭の悪いクソコードを読み解く必要はなくて、重要なのは、return文しかないという所です。実はconstexprな関数には、return文しか書けませんでした。

 でした、って述べた様に、この書き方はC++11っていうちょびっと古いバージョンで、凡骨向けの書き方です。 私の様なポンコツには想像も及ばない世界です。

 でも大丈夫。C++14でだいぶポンコツフレンドリーになりました。C++14ではfor文もif文も変数宣言も解禁され、以下の様に書けます。

constexpr uint32_t sum(uint32_t x)
{
  uint32_t s = 0;
  for(uint32_t i =0 ; i < x; i++)
    s += i;
  return s;
}

 読める、読めるぞぉ!

 C++11のconstexprは、どうしても再帰関数の形になって、最適化しないとスタック消費が激しいですが、C++14なら、constexpr用のsumと実行時用のsumを別個に実装なんてしなくても良いのです。ビバC++14!

 

 さて、constexprな関数がどれぐらい便利かは、お使いのコンパイラがどのC++のバージョンにまで対応しているかによります。

 constexprが使えるC++のバージョンはC++11、C++14があります。正確には「ISO/IEC 14882:2011」、「ISO/IEC 14882:2014」といいます。要するに2011年規格と、2014年規格ってことです。1998年版はC++98です。2098年?さぁ……?

 ARM系なら、基本的には最新のコンパイラはC++14に正式に対応しています。 KailのARM Compiler Version6、IARのコンパイラ、arm-none-eabi-gcc 8辺りは全部対応しています。

 

 本当は更にポンコツ迎合したC++17ってのもあるんだけど、今のところ組み込みで正式に対応してるのはないんじゃないかな。 GCCは先月公開された9.1で正式にC++17に対応しています。組み込みも、後2,3年ぐらいしたらC++17対応してくるかと思いますが。 C++20対応はあと6,7年ぐらいしたら出てくるんじゃないの。

 

static_assertでテストを簡略化しよう

 組み込みって、チープな環境でのテストで嫌になりますよね。テスト走らそうにも、メモリが足りないしね。

 しかし、どれだけ面倒だろうが、C言語では、関数の実装が本当に正しいかは、実機やシミュレータで走らせて確認するしかありませんでした。

 C++はこれをコンパイル時に確認するstatic_assertを提供してくれます。

#include <cstdint>  // uint32_t
#include <cassert> // static_assertを使う為にinclude

constexpr uint32_t sum(uint32_t x)
{
  uint32_t s = 0;
  for(uint32_t i =0 ; i < x; i++)
    s += i;
  return s;
}

static_assert(   sum(0) == 0 ,"sum(0) == 0");
static_assert(   sum(1) == 1 ,"sum(1) == 1"); 
static_assert(   sum(10) == 55 ,"sum(10) == 55");
static_assert(  sum(1000) == 1000*1001/2, "sum(1000) = 1000*1001/2");

 static_assert ( 成立するべき式 , "エラーメッセージ" ) と記述します。C++17ではエラーメッセージの省略ができます。うらやましい。

 これをコンパイルすると、例えばこんな感じになります。

test.cpp:13:25: error: static assertion failed: sum(1) == 1
 static_assert(   sum(1) == 1 ,"sum(1) == 1");
                  ~~~~~~~^~~~

 sum(1)が1にならない!と言っていますね。原因は i < x; の部分ですね。正確には、i <= xです。バグってのはこういうしょーもない所に入り込む物です。

 このように、実機に持ち込まなくても、コンパイル時にconstexprな関数の動作を確認することができます。言い換えれば、ある入力に対する結果が確定する処理は、コンパイル時にテスト可能です。

 組み込みはコンパイル時に動作が決まらないんだよ、糞野郎!という魂の叫びが聞こえますが、果たして本当にそうでしょうか?

 無論、組み込みである以上、ペリフェラルの状態に依存することも多いでしょう。しかし、動作の全てがコンパイル時に未定義なのでしょうか?

 例えば、UARTから値を受け取って、コマンドを実行する場面を考えましょう。確かに、通信相手とUARTペリフェラル次第で、動きは確定できません。UARTから値を受け取る処理は、実機検証するしかないでしょう。

 しかし、「コマンドの実行」まで動作未定義でしょうか?むろん、ペリフェラル依存のコマンドもあるかも知れませんが、コンパイル時に確定する動作もあるのではないでしょうか?

 全体としてはコンパイル時に決まらない動作でも、部分的にはコンパイル時に確定するはずです。そう言った内容は、constexprな関数で抜き出して、先にC++コンパイラの上でテストすることができます。

 

 (ペリフェラルの動作をmocで作っちゃえば、static_assertでテストできそうとか思いましたが、実機って思った様に動かないので、やめた方がいいでしょうね。)

 

template constexprな関数を使おう

 先に挙げたbit関数の例は、複数ビットを指定することができます。C++14版も載せておきましょう。

// C++ 11用
constexpr uint32_t bit(){return 0;}
template<typename... ARGS> constexpr uint32_t bit(size_t a,ARGS... args){
  return (1 << a) | bit(args...);
}

// C++ 14用
template<typename... ARGS>
constexpr uint32_t bit(ARGS... args){
  uint32_t v = 0;
  for(const auto a: {args...})  // 鉞投げても良いのよ
    v |= 1 << a;

  //  意味の分からない書き方をするなら、こう
  //  (void)std::initializer_list<int>{(void(v |= 1 << (int)args),0)...};

  return v;
}

FOO->EN = bit = bit(1,15,20);

 

 さて、templateという謎の機能が出てきました。

 テンプレートやスケルトンと言ってなんとなーくイメージが湧いてくれれば良いのですが、これを簡単に語れというのは、ぶっちゃけ無理です。

 ざっくり言ってしまえば、関数の設計書で、template<typename... ARGS>のARGSを型を指定することで、実際の関数を作る機能です。

 色々探してみて、読みやすいかなーと思った記事を紹介しておきます。以下の記事で理解できなければ、「C++ 関数テンプレート」「C++ テンプレート アンパック」とかで検索してください。

 

template<数値> constexprな関数を使おう

 先ほどは、「型で」templateを指定していましたが、実は型意外にも整数でtemplateを作ることができます。

//Vに100を足す
template<int V>
constexpr int add100(){ return V + 100;}

static_assert( add100<1>() == 101 ,"");

 これを使って、bit関数を再定義すると以下の様になります。

template<unsigned int... ARGS>
constexpr uint32_t bit(){
  uint32_t v = 0;
  for(const auto a: {ARGS...})  v |= 1 << a;
  return v;
}

FOO->EN = bit = bit<1,15,20>();

 これのメリットは変数を渡せなくなったことです。

 このbit関数って、組み込み的に考えたら、高確率で定数ですよね。 しかし、間違えてbit関数に変数を受け入れてしまうと、そこがbit関数呼び出しになってしまう。しかし、コンパイルエラーになる訳じゃないから、そのもったいないを見抜けない。

 この書き方だと、bit<変数>とは書けませんから、コンパイル時にミスに気がつきます。なお、constexprな変数=定数は入れることができます。

 

template constexprな変数を使おう

 先ほどの「bit<1,15,20>();」ですが………、面倒くさくないっすか?「()」書くの。だってさー、()っていらないじゃーん。ぶっちゃけ。

 そういう場合は、template constexprな変数を使いましょう。

template<unsigned int... ARGS>
constexpr uint32_t __bit(){   // __を追加
  uint32_t v = 0;
  for(const auto a: {ARGS...})  v |= 1 << a;
  return v;
}

template<unsigned int... ARGS>
constexpr uint32_t bit = __bit<ARGS...>(); //template constexprな変数bit

FOO->EN = bit = bit<1,15,20>;

 無駄な「()」が必要なくなりましたね!

 

気分はstatic!

 templateとconstexprを理解した皆さんは、C++がいかにCより組み込み屋にとって便利か、理解できたかと思います。

 しかし、まだ、使いこなしているとは言いがたいです。

 最後のキーワード、それはstaticです。C言語にも、ファイルスコープを表すstaticがありましたが、ここでのstaticはクラスの中で使うstaticです。

 staticはC++のclassやstructを名前空間に変えてしまう、魔法の単語です。

 例えば、各ペリフェラルの電力供給管理をするペリフェラルのPOWレジスタがある、という場面を考えてみましょう。各ペリフェラルの電力は、POWレジスタの適当なビットに対応してるとします。例えば、以下の様な情報として記述しておくとしましょうか。

struct DMA
{
  static constexpr uint32_t POW = bit<3>;
};

template<unsigned int N> struct PWM
{
  static constexpr uint32_t POW = bit<6 + N>;
};
using PWM0 = PWM<0>;
using PWM1 = PWM<1>;


template<unsigned int N>  struct UART
{
  static constexpr uint32_t POW = bit<12 + N>;
};
using UART0 = UART<0>;
using UART1 = UART<1>;
using UART2 = UART<2>;
using UART3 = UART<3>;

 ここで重要なのは、DMA,PWM,UART全ての構造体が、「static constexpr POW」を持つという所です。これを利用すると、POWレジスタを設定する関数は以下の様に作れます。

template<typename... ARGS>
constexpr uint32_t  __get_POW(){
  uint32_t v = 0;
  (void)std::initializer_list<int>{(void( v |= ARGS::POW),0)...};
  return v;
}

template<typename... ARGS>
inline void powerOn(){
  FOO-> POW = FOO-> POW | __get_POW<ARGS...>();
}

//使い方
powerOn<PWM0,UART2,UART3>();

 「(void)std::initializer_list{(void( v |= ARGS::POW),0)...};」ここがミソですね。swallowと呼ばれるテクニックを使っていますが、それを可能にしているのが、全ての構造体が「static constexpr POW」を持つという条件です。

 確かにこの構造体を先に定義するのは面倒ですが、一度定義してしまえば、別プロジェクトでも使い回せます。あくまでconstexprな定数でしかないので、余計なバイナリを生成する心配はありません。

 ビットフィールド指定と違って、constexprな関数の__get_POWでビットを計算するので、read-modify-writeも1回しか発生しません。それでいて、ソースコードを後から読んだときでも、PWM0、UART2、UART3の3モジュールの電源を入れてるんだなと、理解しやすいですよね。可読性もバッチリです。

 

まとめ

 どうでしたでしょうか、C++が理解できたでしょうか? うん、知ってる、 わからんよね!!

 ぶっちゃけると、C++は深淵を覗けば、何処までも深みにはまります。他人のコードを見ると、魔法みたいなことをしてることが多々あります。

 私もC++なんて全然分かりません。最近になってinline namespaceとか知ったぐらいだしね。C++は謎の記号が多くて、検索しにくく、何の文法かも分からんことがあります。C++の全てを理解してる人間なんて、この世にいないんじゃないかとすら思います。理解してるのは、魔人か何かです。

 しかし、「Cで書けるんだから、C++なんて価値もないクソ」ではなく、「C++はよく分からないけど、組み込みに便利らしい」と、少しでも歩み寄って頂けたなら本望です。

 今回は紹介しませんでしたが、RAIIなど、Cでは使えない組み込みで有用なテクニックが他にも色々あるので、調べてみると良いでしょう。

基礎からしっかり学ぶC++の教科書 C++14対応

基礎からしっかり学ぶC++の教科書 C++14対応

ロベールのC++入門講座

ロベールのC++入門講座

Effective C++ 第3版 (ADDISON-WESLEY PROFESSIONAL COMPUTI)

Effective C++ 第3版 (ADDISON-WESLEY PROFESSIONAL COMPUTI)

今更ながら、文字列を型にしたい

 やーやーやー

 なんで私、最近こんな黒魔術に手を出してるんだろうね。最近、相当変態的な方向性でC++を悪用 利用しようと企ててるから、こんな羽目になってますよ。

 と言うのもですね、こう、色々と面倒くさい諸問題を回避しようとしてたら、黒い方法を思いついてしまったんだ。いや、私は悪くない。Goサインを出した人が悪いんだ。

 

 しかし、C++11、C++14で相当敷居が下がったって言っても、型だとか、constexprってやっぱり、こう、闇の軍団か中3女子的なイメージあるじゃない。タイトルみたいなことは、Sprout使ったらすぐできそうなんだけど、政治的ごにょごにょにより…

 ポンコツ…いや、豚骨な私が手を出して良い領域じゃないと思うんですよ。私はC++を完全に理解した日は来るのだろうか。

 闇の軍団的にはできて当たり前なのかも知れないけど、こう、ファイアをやっと唱えられましたレベルの私だと中々ね。

 

やりたいこと

 以下の型を宣言したい。

template<char... chars>struct char_type
{
  static void print(){
    (void)std::initializer_list<int>{(void(std::cout << chars),0)...};
    std::cout << std::endl;
  }
};

constexprな関数から作る場合

 C++14なら豚骨にも簡単にできた。

 関数から作ることができるので、constexprな配列の名前が定義されてたら、同じように作ることが可能。

template<int N> struct make_alphabet_type
{
private:
  static constexpr char get(int n){return (char)('a'+n);}
  template<typename Seq>struct make_char_type{};
  template<typename I,I... seq>struct make_char_type<std::integer_sequence<I,seq...>>{
    using type = char_type<get(seq)...>;
  };
public:
  using type =typename make_char_type<std::make_integer_sequence<size_t,N>>::type;
};

int main(int argc, char *argv[])
{
  using a5_t = make_alphabet_type<5>::type;
  a5_t::print(); // abcdeと出力される
  return 0;
}

 実体は何処にもないけど、constexprの計算で作ったchar配列の型を作ることができたよ!

 

文字列リテラルから作りたい

 GCC拡張のtemplateユーザー文字列リテラルが使えるなら簡単なんだけど、これどうすれば良いんだろ。一度どこかに名前を作れれたりすればいいんだけど。ラムダで何とかならないかと思ったけど無理でした。

 BOOST_PP_ENUMとか使えばできるっぽいけど。

 BOOST_PP_ENUMと同じことを手でやってしまえば、固定長だけど、出来ないことは無くて、しかも私の目的としてはこれで十分なんだけど。

//GCCの場合はこれ
template<char... chars>
constexpr char_type<chars...> operator ""_chartype(){return char_type<chars...>();}

template<char... chars>struct _char_type{
private:
  static constexpr char str[] = {chars...};
  static constexpr size_t array_size = sizeof...(chars);
  static constexpr size_t count(size_t i){
    return i == array_size? array_size:
      str[i] == '\0' ? i : count(i+1);
  }
  
  static constexpr size_t length = count(0);
  
  template<typename seq>struct make_char_type{};
  template<typename I,I... seq>struct make_char_type<std::integer_sequence<I,seq...>>{
    using type = char_type<str[seq]...>;
  };
public:
  using type = typename make_char_type<std::make_integer_sequence<int, length>>::type;
};
template<int N>
constexpr char get_char(const char (&array)[N],int index){
  return index < N? array[index] : '\0';
}

#define GET_CHAR_TYPE(x) _char_type<get_char(x,0),get_char(x,1),get_char(x,2),get_char(x,3),get_char(x,4),get_char(x,5),get_char(x,6)>::type

int main(int argc, char *argv[])
{
  using abc_t = GET_CHAR_TYPE("abc");
  abc_t::print();
  return 0;
}

constexprでfloatのバイナリ値を取得したい

 constexprで計算したfloatの値を、バイナリをそのままにuint32_tに変換して配列の一部に保存したいという場面に出くわしました。

 templateの構造体で定義すれば良いようにも思えますが、floatが入る位置や数は未定で、やりにくいのです。

 というわけで、float→uint32_tというバイナリ解釈の変更をしたいのですが、なんと、constexprでunion型って、アクティブでない要素を計算に使えないんですね。

union b32_t{
  uint32_t i;
  float f;
  constexpr b32_t(float v):f{v}{}
  constexpr b32_t(uint32_t v):i{v}{}
};

constexpr uint32_t fbToUi32(float f)
{
    b32_t b(f);
    return b.i; // iがアクティブでないのでエラー
}

 んー。まじか。

 リトル/ビッグエンディアンぐらいは考慮しないといけないかなーとか思ってたけど、それ以前の問題だった。たぶん、C++コンパイラのconstexpr処理系では、unionは普通に二つの要素を持つオブジェクトとして処理されてるんだろうね。

 C++20からは制限が解除されるとからしいけど、C++11とかC++14なので関係ない。

 GCCだと、cmathの関数がconstexprになってるから、logbとかsignbitとかを使えば再構築できるけど、残念ながら私が使っている処理系では無理。 中3女子ことBolero MURAKAMIさんのSproutライブラリを使えれば良いのですが、政治的諸事情により使いにくい。困った。

 

 あ、なお、この辺もC++20でconstexprがつく様になるらしいっすよ。今の私にゃ関係ないけどね。

 

 

float→uint32_tを自前で実装

 諦めて自分でfloatの値に対応するバイナリを手動で書いてしまえばいいのだが、残念ながら私はクソニートだ。ここで意志を曲げて部屋から出て行く様では、老後が心配になってしまう。親に立ち向かう勇気を持って、この問題に立ち向かう義務がある。

 さて、こうなってしまっては自分でfloatの値を解析して、uint32_tを再構築するしかないだろう。

 float(IEEE 754準拠)は符号、仮数部、指数部の3つからなる。仮数部は2のn乗の部分で、実際にはn+127の値が入ります。指数部は1から2未満の値です。

 まぁ、そんなに難しかぁないさ。単純にそれぞれについて解析していけば良いでしょう。

 ただし、幾つかfloatには特殊な値が存在があります。+0、-0、∞、-∞、NaN、非正規化数である。これらは別個に処理…

constexpr bool is_pos0(float f){ return f ==  .0f; }
constexpr bool is_neg0(float f){ return f == -.0f; }

int main(int argc, char *argv[])
{
  static_assert(is_pos0(.0f));
  static_assert(!is_pos0(-.0f)); // エラー
  static_assert(!is_neg0(.0f));  // エラー
  static_assert(is_neg0(-.0f));
  static_assert(!is_pos0(1.0f));
  static_assert(!is_neg0(-1.0f));
  return 0;
}

 

 あるぇえ?-0と+0の区別が付かない………。あれ、バイナリ同じ物が生成されるの?いや、b32_tで出力してみたらちゃんと符号ビットが違うし………

 ………

 あははは!そうだよね、0は0だよね!誰だよ、0に正と負があるとか言った奴!ねぇよ、んなもん!

 よし、見なかったことにしよう。

 ただし、幾つかfloatには特殊な値が存在があります。0、∞、-∞、NaN、非正規化数である。これらは別個に処理する必要があります。

 一先ずはC++14で実装。

#include <limits>
#include <cstdint>

using float_limits = std::numeric_limits<float>;

constexpr float FLOAT_POS_INFINITY = float_limits::infinity();
constexpr float FLOAT_NEG_INFINITY = -float_limits::infinity();

constexpr bool is_zero(float f){ return f == .0f; }
constexpr bool is_posinf(float f){ return f == FLOAT_POS_INFINITY;}
constexpr bool is_neginf(float f){ return f == FLOAT_NEG_INFINITY;}
constexpr bool is_nan(float f){ return f != f;}
constexpr bool is_denormal(float f){
  //こんなんでいいのかな?
  return f != 0.f 
      && f < float_limits:: min_exponent 
      && f > -float_limits:: min_exponent;
}

constexpr uint32_t fbToUi32(float f){
  if(is_zero(f)){
    return 0;
  }else if(is_posinf(f)){
    return 2139095040;
  }else if(is_neginf(f)){
    return 4286578688;
  }else if(is_nan(f)){
    return 2143289344;
  }else if(is_denormal(f)){
    //デバイスの制限上0にしています。エラーでも良いかも
    return 0;
  }

  const uint32_t sign = f < 0? 0x80000000:0;
  if(f < 0){ f = -f; }

  uint32_t exp = 127;
  if(f < 1){
    while(f < 1){
      f *=2;
      exp--;
    }
  }else{
    while(f >=2){
      f *=0.5;
      exp++;
    }
  }

  uint32_t frac =0;
  f -=1;
  uint32_t _frac = 1 << 22;
  float sub = .5;
  while(f != 0.0 && _frac != 0){
    if(f >= sub){
      frac |= _frac;
      f -= sub;
    }
    sub *= .5;
    _frac = _frac >> 1;
  }
  
  return sign | (exp << 23) | frac;
}

 どうせコンパイル時にしか使わないんだし、仮数部の探索とかを二分探索とかにする必要はないでしょう。

 一応何個かテストしてみたら、それっぽく正しい値が出てるっぽいよ。とりあえずはこれでいっかな。後は必要ならC++11にすっか。

Wordを捨ててTeXでドキュメントを自動生成しようじゃないか

 やーやーやー

  •  シミュレータや合成ツールに金出したら、もう金がないので、チップの検証環境を自作
  •  金がないので、RTL自動生成を自作
    •  気がつけば10モジュールぐらい担当してた
  •  金がないので、RedmineとGitbuket、Rocket.Chat、TestLinkを導入して、開発環境と検証環境と連携するシステムを自作

 

 と、命令された訳でも無いのに、ひたすら文句を言い続けてたら、いつの間にかこんな状況になってしまった。ついでの過去の罪状として、プログラムの開発環境が貧弱と文句を言っていたら、いつの間にか統合開発環境(IDE)を自作していた。器用貧乏を地で行っている気がしてならない。

 

 現状を現状のままに受け入れる力が、社会人に最も重要な能力。みんなは黙って働こう。

 

 

ドキュメントを自動生成したい

 プロダクトに関わると、どうしても設計資料とは別に、利用者の為のドキュメント(データシート)を、どこかの時点で用意する必要に迫られる。

 数ページ済む様なデータシートならどうとでもなるが、1000pにもなりそうなデータシートなんて、手で書いていられない。そもそも、プロトタイプではAだったが、具合を見て変わる予定という情報は匂いすぎる。予言者でなくとも、ドキュメントがバグる未来が見える。

 仕様書やデータがあるなら、ドキュメントは自動生成してしまった方が、安心安全だ。しかし、金はない。仕方がない、またしても余計な仕事を勝手に抱えようじゃないか

 

 では、どうやってドキュメントを自動生成するか。

 途中工程によっても違うだろうが、極論をしてしまえば、ドキュメントは最後にPDFになりさえすれば良い。自動生成プログラムから直にPDFを吐き出すことも不可能ではないが、手段はおおよそ以下の二つだろう。

  • Wordを生成し、WordからPDFにする
  • PDFに変換出来る言語(TeXなど)を生成する

 

Wordを使う選択肢

 私は、最初は諸々の政治的な理由からWordの自動生成を試した。

 しかし、Wordの自動生成は無理と言わんが、片手間に出来るレベルじゃないと言って良い。理由を語ろう。

 それぞれの場面において、ドキュメントを作成する為のフォーマットガイドはあるだろう。例えばフッターだったり、ヘッダーだったり、フォント、表の配置、行間等など、それを従ってなくては怒られる。 書籍を出す訳でもないのに、フォーマットを確認する費用で、何円の価値が生まれるのか、私には甚だ疑問だが。

 一般的に、処理とデータ(文章)と、フォーマット(文章の見た目)は別個に管理することが、見通しも、プログラマの精神衛生的にもとてもよろしい。しかし、Wordを自動生成をする場合、これらをゴッチャにして、ガイドを満たす様にプログラムを組まなくてはならない。反吐が出る。

 仮にスタイルのテンプレートファイルを使うとしても、細かいルールが全てスタイルの設定で解決する訳ではない。細かい計算をする必要はある。例えば、表は画面幅いっぱいとかいうルールは、スタイルで解決しない。だいたい、そもそものスタイルテンプレートファイルが変更されたら、どうするというのだ。

  

 更にまだ問題はある。

 ドキュメントの全てを自動生成出来るはずもない。かならず、手作業でドキュメントを作る部分は存在する。

 巨大な自動生成したドキュメントなら、別冊で良いだろう。では、2、3文の程度の、断片的な自動生成可能な情報は?1つ程度の表は?数値は?単語は?手作業で作るドキュメントに追加したい、小さな自動生成情報はどうするというのだ?

f:id:nodamushi:20190323205333p:plain:w320

 こういうものも、出来れば自動生成しておきたい。しかし、これをWordで上手く解決する手段を、私は思いつかなかった。何か良い手段があれば、是非教えて頂きたい。

 どうしてもWordファイルに拘り、手作業で書くドキュメントと、自動生成の融合を図り、Wordの様なバカ向け……WYSIWYGに拘るなら、Blogの様なWYSIWYGなシステムを提供し(なお、この記事はMarkdownで書かれてる)、Wordファイルを出力するシステムを作るしかないだろう。

 お金があるなら、それはとても良いことだ。ベンダーにお金を払えば良い。それらのシステムが出来ないことは、絶対にありえない。Office365はブラウザで動くのだから。

 しかし、お金がないからこういう問題に頭を悩ます訳で。

 むろん、私が死ぬまで働いて解決出来るなら、私一人で死ねば良い。歯車一つ壊れる程度は、コラテラル・ダメージでしかない。だが、私一人死んだからと言って、システムが完成する未来は見えない。完成するにしても、間に合うことは絶対にない。そのぐらい大きな規模のシステムになる。

 

 

ドキュメントにTeXを使う選択肢

 

 Wordで自動生成と手作業ドキュメントの統合は諦めた。ならば、残る選択肢は以下の三つだ。

  • 自動生成を諦める
  • 統合だけを諦める(Wordを自動生成するという選択肢は残る)
  • もう、Wordを使わない

 もちろん、自動生成を諦めるという選択肢は論外だが、どれにせよ、全員で痛みを共有することになる。

 ところで、なぜ、Wordファイルでなくてはならないのかというのは、大抵、査読等の理由だ。しかし、そもそも、自動生成されたWordを修正をしたところで、意味が無い。真に修正すべきはデータベースと自動生成プログラムであり、Wordファイルではない。更に言えば、毎回自動生成されるのだから、Wordファイルに履歴は残らない。そうなると、WordでPDFを編集出来るのだから、もはやWordに拘る理由がない。後は政治的な理由のみだ。

 どの手段を選ぶにせよ、自動生成するプログラムがWordを吐き出す正義はなくなった。

 ではWord以外の、何を生成すべきか、何でPDFを生成すべきか、だ。

  • TeX
  • Re:VIEW
  • troff

 まだあるかも知れないが、私が知っているのはこの程度だ。

 新しい物好きな私は、Re:VIEWにしようかなとも思ったのだが、よく読むと、結局の所LaTeXを作成してPDFにするらしい。レイアウトの細かい調整をするには結局TeXを弄る必要がある。だったら、学生の頃よく使ってたし、しょぼい処理系を作ったこともあるTeXでいっかということで、TeXを生成することにした。

 そして、TeXの場合、マクロを提供するという形で、自動生成ドキュメントと、手作業によるドキュメントの統合が可能になる。「初期のモードは\initmode です」と書いておけば、コンパイル時に\initmode が適切な文字列に変わるのだ。

 ならば、もう選ぶべき手段は、Wordを使わない、である。容易な手段が転がっているのに、それを使わないというのか。

 WYSIWYGなんて、もういいじゃないか。マイクロソフトに保護に抱かれて気持ちよかっただろう?コンピュータを、人間の直感の為に改良してくれて、とても安心出来ただろう?コンピュータという気味が悪いナニカに、親しむことが出来ただろう?でも、それは必ずしも、コンピュータをコンピュータとして、効率よく使っているとは限らない。いい加減、コンピュータをコンピュータとして使おうじゃないか。

  f:id:nodamushi:20190323215020p:plain

 と、いうわけで、上司に昼飯時にぼやいてみたら、ノリに乗ってくれて、そのまま公式に認められてしまった。素晴らしい上司である。

 

 

TeXを何処まで自動生成するか

 さぁ、TeXを使うと決まった。

 TeXならテキストファイルを書き出すだけなので、ライブラリも特に何もなくても作れる。とても楽である。 と言って、何も考えずに「\section{ほげ} \begin{table}[h] ~~~」などと吐き出すのは、やめた方がいい。

 文章やドキュメント構成を全てプログラムで行うと、校正でダメ出しされるときが面倒くさい。プログラムを修正し、再生成しなくてはならない。運用次第ではあるが、それぞれのメンバーごとに、ファイルを自動生成する様な仕組みなら、ちょっとやっていられない。

 そもそも、TeXはほぼ何でも出来るのだ。言ってしまえば、データさえあれば、ドキュメント構成は全てTeXで処理することだって可能だ。

 ドキュメントの構成を全てTeXに任せてしまうのなら、自動生成するプログラムはデータをシリアライズして、書き出すだけである。TLCのリストみたいな感じに書き出しておくと、扱いやすい。

\def\data{%
  {peripherals}{%
    {%
      {name}{DMA}%
      {address}{{4981504}{4C0300}}%
      {registers}{%
        {%
          {name}{CTRL}%
          {address}{{4981504}{4C0300}}%
          {fields}{%
            .............以下略............
}

 これだと、自動生成する側のやることはほとんど無くて、管理が楽である。やることと言えば、_&^等の文字をエスケープするのと、10進数、16進数、2進数の数値データを用意してあげる位だ。 (n進数変換はTeXでやろうとしたのだけど、TeXのカウンターは32bit符号付き整数なので、大きな数字はやりにくい。出来ないことは無いけど。)

 ドキュメントにダメ出しが入っても、プログラムを修正する必要も、再生成する必要もなく、TeXを再コンパイルするだけで良い。実に楽だ。

 じゃぁ、これで万事解決かというと、そうでもない。

 

 話は突然変わるが、皆さんは約千行のプログラム、と聞いてどんな印象を持つだろうか?

 無論、プログラムというのはその機能や、管理のしやすさと言った面から評価すべきであり、行数は大した価値を持つべきではない。それは千も承知の上で、ただただ、千行のプログラム、で考えてみて欲しい。

 まぁ、半日~1日ぐらいで書き殴ったプロトタイプかな、という印象ではなかろうか。

 で、今回、私はTeXのマクロを1000~2000行程書いたが、気がつけば2週間過ぎている。マジか。

 慣れていないから、というのもその通りなのだが、ひじょーにデバッグしにくい。いや、マジで。高々if文で躓くし、expandafterで、noexpandで、relaxで、table環境の\hlineで、tabularxで、ありとあらゆるところでゲロを吐く羽目になった。

 一度躓くと、エラーが何処で起こってるのか分からない。プログラミングなら、エラーが起こった場所はブレークポイントなりで解析出来るが、TeXの場合はどういう展開になるのか見てくしかない。

 

 というわけで、素人に毛が生えた程度で何でもTeXでやってやるぜ、というのはやめた方がいい。いたぶられ続けて、段々トランスして、気持ちよくなってくる快感を味わいたいなら止めはしないが。アンッ♡

 どの程度をTeXで処理し、どの程度をプログラムでやるのか、その辺は技量と運用方法と性癖をよく考えながら作る必要がある。

 

 

TeXの環境を整備しよう

 TeXを使います!TeXを自動生成しました!はい、後はみんな頑張って!よっしゃ、お終い!というほど話は簡単ではない。TeXを使わせるなら、TeXを使うなりのシステムを用意する必要がある。

 少なくとも、みながTeX素人なら、以下の内容について言い出しっぺが準備する必要がある。(私も正直TeXの素人に毛が生えた程度だが)

 

  • どのTeXエンジンを使うかの選定と、インストール方法の定義
  • TeXの使い方の講習
  • ドキュメントガイドに沿ったクラスファイルの作成
  • 表、画像などを統一する為のマクロ(スタイル)の提供
  • その他、チーム特有の問題を解決する為のスタイルの提供
  • チームでファイルを分割してコンパイルする環境整備
  • Git管理の手法
  • Lintツールの導入

 

 

LuaLaTeXを使おう

 TeXかLaTeXか新規に選択しろ、というなら普通にLaTeXを選べば良い。ConTeXtは安定してないのでどうなんだろうね。わからん。

 LaTeXが提供するマクロを捨てて、TeXだけで闘う理由は特に思い当たらない。別に原理主義に陥る必要は全くない。

 次にどのTeXエンジンを使うか、である。単にTeXといっても、幾つかの処理系があり、処理系ごとに特色が微妙に異なる。同じOSで、だいたい同じような機能は提供してても、Windows、Mac、Linuxで操作が微妙に異なる様な物だ。

 これは作成するドキュメントガイド次第ではあるが、細かくフォントの指定がされているなら、LuaTeXを使うことをお勧めする。

 10年ま………ゲフン、ゲフン、学生だった頃に使ってたpLaTeXを最初弄っていたのだが、クッソメンドイ。やってられん。XeTeXも試してみたのだが、どうも日本語のフォントを切り替えるのが、上手くいかなかった。

 で、LuaTeXなのだが、これがクッソ楽だった。とりあえず、\newjfontfamilyでフォント定義しておいて、必要な場面で使えばいいだけなのだ。

 過去の資産も何もない状態なら、LuaTeXはとても良い選択肢だろう。

 

 

 ただ、幾つかの致命的注意点が存在する。

 まず、致命的にクソ遅い。pTeXなら、ペペペっと終わる処理が、LuaTeXだとぺーーーぺーーー(固まる)ぺーーーーって感じだ。たぶん、4,5倍は遅い。気分的には10倍以上遅い。精神衛生を侵し、SAN値を削ってくる。命に関わるといってよい

 最終的なドキュメントはLuaLaTeXで処理して、普段はフォント設定無視して、pLaTeXで結果を見るのが良いと思う。むろん、時々ちゃんとLuaLaTeXで処理して、確認しないと後で痛い目を見そうだが。普段からずっとLuaTeXを使う場合でも、フォント設定は切っておいた方がいい。少しは速くなる。

 そのため、提供するマクロにLuaを使わない方がいい。Luaを使うと、デバッグがしんどいマクロ作成が非常に楽になるのだが、私はSAN値が削れることに耐えられない。チマチマLaTeXに修正中である。

 

 次に、致命的にメモリを食う。とりあえず、500pぐらいのドキュメントを自動生成したのだが、余裕で2Gbのメモリを消費した。1000pぐらいになったら、そもそも32bitでメモリ足りるのか疑問である。

 最後に、仕様が安定していない。私はTeXLive2018を使っているが、TeXLive2019ではLuaのバージョンが5.2から5.3になるらしい。このとき、Luaの後方互換は破壊的であるそうだ。事故る未来が見える。

 さらに、同じTeXLive2018でも、ISO版(2018年4月版)と、常に最新版が入るインターネットインストーラ版で挙動が異なる。特に、何故かフォントに游明朝の太字(YuMincho Demibold)を使うと、インターネットインストーラ版ではメモリエラーが発生する。これはW32TeXでもだった。年間を通して、エンジンは同じらしいので、パッケージのバグだとは思う。

 従って、LuaTeXを使う場合、私から言えるアドバイスは以下である。

  • インストール日付に依存せず、全員同じ環境になるインストールの仕組みを定義せよ。TeXLive2018ならISO版を使うことを推奨する。(2019以上の場合は知らない)
  • アップデートは検証を行った後で、許可をせよ
  • TeXLiveは32bitしかインストールされないので、64bitエンジンのインストール手段を定義せよ。メモリ足りなくなってから泣いても知らん
  • PCのメモリは8Gbじゃ足りない
  • 提供するマクロはLuaに依存しない方がいい

 

 

クラスファイルを定義しよう

 ドキュメントの見た目を定義するのがクラスファイルである。\documentclass{jsarticle}とか書いてる所のjsarticleの部分だ。

 余白やフォント、ヘッダーフッター、行間、chapter、sectionの文字などを設定するのだが、全て0から定義するのは大変すぎる。無理せず\LoadClassで提供されている既存のクラスファイルを読み込み、必要に応じてハックしたり、設定を書き換えるのが良い。

 なお、間違ってもjsarticle.clsなどの実際のファイルを書き換えるべきではない。ライセンス的にどうなんだという話と、個人の環境に依存してしまうのが怖い。必ず、\LoadClassで読み込んでから必要な関数を上書きしたりしよう。

 大体次の内容を作れば十分だろう。

  • 表紙
  • 目次ページのカスタマイズ
  • ヘッダー、フッター
  • Confidentialのオプション
  • フォント
  • chapterやsectioの表現
  • 表、図、式のcaptionでの表現(図:1-1とか)
  • 表、図、式と文字の行間
  • item環境の●や■の文字
  • enumerate環境の連番表示の仕方
  • easylistの調整
  • listingsの設定(フレームやフォントなど)

 

 

ところで、Windowsフォントについて

 ガイドにWindowsのフォントが指定されていて、私は驚愕した。PDFにする以上、普通はフォントを埋め込む。それにWindowsフォントを使うのか!?と。ライセンスは大丈夫なのかと。

 しかし、それは私が学生だった10ね………数年前の話であり、今は状況が違っている。2017年にWindows標準フォントについて、ついにMicrosoftが沈黙を破り、その指標を公開していた

 読んでみた限り、PDFを暗号化し、編集不可能にしておけば、Windows標準フォントに関しては、Windows上でPDFを作成する限り、PDFに埋め込んで良いと思われる。(必ず原文を当たり自分で確認すること)

 ※Wordに付属して付いてくるフォントは標準フォントではない。HG●●フォントとか。

 

スタイルファイルを定義しよう

 クラスファイルを作ったら、次はスタイルファイルの定義だ。

 どんなマクロが必要かはチームによって違うだろうが、少なくとも以下は用意したい。

 

  • 図を読み込むマクロ(コマンド一つで表示できるように)
  • 複数の図を読み込むマクロ(図1(a)みたいに参照できるように)
  • キャプションを引数として受け取る表環境(上にキャプションを付けるのか、下に付けるのか固定)
  • 表、図、式へのref(図1、表1、式(1)みたいに接頭辞が必要ならそれも強制的に付けてしまえ)
  • inputやsubfile、includegraphicsなどのファイル読み込みを、現在のファイルからの相対パスで読めるコマンドで出来るコマンドの用意。

 

 特に、複数人でファイルを分割して作業する場合、input系のマクロは事故る未来しか見えないので、必ず現在のファイルからの相対パス化したい。\currfiledirを引数の前に付加するだけで良い。

 これらを準備した上で、各チームごとに必要なマクロを定義しよう。例えば、ペリフェラルのレジスタを参照するマクロとかを私は用意した。

 個人的には、PAD図をTikzで簡単にかける様にしたい。けど、Tikz全然分からない。  

 

ファイルの分割について

 inputを使うよりもsubfileを使った方がいい。subfileで分割すると、個別にコンパイルが出来るので、全部をコンパイルすると死ぬ程遅いLuaTeXでも、致命傷程度の速度でコンパイル出来る。

 そのために、プリアンブルだけ定義したtexファイルは一番最初に用意しておこう。

 

 

RedPen

 今時、Lintもなしにドキュメントを書くなんて考えられない。なお、このブログは特にLintは使っていない。(オイ

 Wordなら、Wordが色々とウザいお節介を焼いてくれるが、TeXだと、色々お膳立てしなければならない。

 Markdownで使ってるTextLintLaTeX拡張があるが、どうも使いにくかった。調べてみたら、RedPenがデフォルトでLaTeXに対応しているので、これを導入することにした。

 ただ、このRedPenは実行にJavaが必要なのが悩み所。一般人はジャバってなーにと言うレベルだ。特に今は無償で公式のJREのみのインストーラが無くなったのだから、余計に説明が面倒だ。まぁ、TextLintだってnode.jsという謎の危険が危ないナニカが必要なので、大差はない気もするが…

 RedPenにはサーバー機能があるので、社内サーバーを用意するか、ファイルサイズがでかくても、exe化した方がいい。通信して外に出しても良いというなら、公式のRedPenサーバーReadmeに従って、Herokuを使ってみてもいいだろう。どうするかは、運用次第だが、とにかく、Javaは一般人にとって、百害あって一利無しである。

 また、サーバーにする場合は、コマンド一つでサーバーに対してリクエストを発行し、返ってくるJSONを解析してエラー箇所を表示する仕組みを作る必要がある。 全員がEmacsかAtomを使ってくれるなら、プラグインで解決出来るが、TeXWorksだと、どうしてもそういう準備は必要だ。

 

終わりに

 うーわ、久しぶりのブログ長っ。1万文字だとよ。

 というわけで、いつもの発作でまた余計な仕事を抱えた私が、やってみたことをまとめてみた。ドキュメントを自動生成したいけど金がない、システムに金かけるのは無駄、という人々の手掛かりになれば幸いである。そして、いずれはドキュメントの自動化に金を払ってもらう足がかりにしよう。

 作ったんだから、死ぬまでオメーが管理しろ、というのであれば…………辞めるしかないかな。