プログラムdeタマゴ

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

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万文字だとよ。

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

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

簡単なHTMLの表をXMLに変換する方法

 どーも。

 Markdownとかorg-modeとかで表で書いてた情報と、同じ情報をXMLで書かなきゃいけなくなったという場面に遭遇しました。

 これらはGitBucketで表示していたので、要するにHTMLの表→XMLへの変換をしたい。

 かといって、今回の為だけにプログラム書くのも面倒くさいし、既存の物で何とかしてみよう。

前提条件

 必ず、ExcelかLibreOffice CalcもしくはGoogle SpreadSheetが使える環境であること。インターネットに接続出来ること。

 そして、生成するXMLは以下の様にシンプルな構造であること。

<全体>
 <行>
  <列1>中身 </列1>
  <列2>中身 </列2>
 </行>
 <行>
  <列1>中身 </列1>
  <列2>中身 </列2>
 </行>
</全体>

 

 データはネスト要素を持たない。

<行>
  <列1>
    <struct> <!-- 列が更に構造を持つのはちょっと辛い -->
    </struct>
  </列1>
</行>

 不可能ではないけど、表の段階で何処まできちんと分離してあるかによるかな。

関数名 引数 説明
hoge uint32_t moge ほげる
piyo const string& puyo ぴよる
taro たろる

<functions>
 <function>
  <name>hoge </name>
  <arguments>uint32_t moge </arguments>
  <descriptions>ほげる </descriptions>
 </function>
 <function>
  <!-- ブログ上、以下略 -->
 </function>
</functions>

手順

 手順は以下です。

  1. 表をExcel等に貼り付ける
  2. 1行目をタグの名前に変更する
  3. CSVとして保存する
  4. CSV to XML ConverterでXMLに変換

スプレッドシートに貼り付けてCSV化

 ブラウザ上で表をコピーすると、そのままExcelやLibreOffice CalcGoogle SpreadSheetに貼り付けることが出来ます。というか、Excelなんて持ってないので、わざわざLibreOffice入れたけど、Google SpreadSheetでも出来たことに驚いた。オフィス製品いらないねぇ…

f:id:nodamushi:20190126141518p:plain
表を選択してコピー
f:id:nodamushi:20190126131207p:plain:w240
LibreOffice Calcでペースト
f:id:nodamushi:20190126131235p:plain:w240
Google SpreadSheetでペースト

 1行目を必要なタグ名に変更する。

f:id:nodamushi:20190126131816p:plain:w320
name,arguments,descriptionに変更した

 全体を見て、ペースト結果が結合されているなどおかしい場所がなければ、CSVとして保存する。 f:id:nodamushi:20190126132114p:plain:w320

 LibreOfficeで保存する場合は、途中でフォーマットについて聞かれるので、UTF8で保存しましょう。Excelは知らない。 f:id:nodamushi:20190126134704p:plain:w320  

CSV to XML ConverterでXMLに変換

 出来たCSVをCSV to XML ConverterでXMLに変換します。

 先ほど作ったCSVファイルを入力しましょう。このサイトでXMLに変換しても、情報などはサーバー上には転送されないので安心して下さい。

f:id:nodamushi:20190126135438p:plain

 

  行を「function」で、全体を「functions」で囲いたいので、Step4を開いて、テンプレートをカスタムします。

 ちょっとしたネスト構造なら、このテンプレートを操作することで作ることも可能。なお、テンプレート中の{h1},{f2}などの1,2は列番号です。hはヘッダだと思うケド、fって何の略だろ?

f:id:nodamushi:20190126140004p:plain

 

 「Convert CSV to XML via Template」を押すとテンプレートを使って変換してくれます。

f:id:nodamushi:20190126140123p:plain

 

 後はエディタにペーストするなり、ダウンロードするなり、好きにするがよろし。

 

 

要素数が少ないなら

 ちなみに、要素数が3個、行数も10ぐらいしかなかったので、実際は私はこういう方法で解決しました。

 スプレッドシートに貼り付け、1行目を削除し、各行の間に空白の列を入れる。

f:id:nodamushi:20190126132515p:plain

 タグで挟む f:id:nodamushi:20190126132716p:plain

 左下の端をドラッグして縦に複製 f:id:nodamushi:20190126132832p:plain

 全体をコピってメモ帳に貼り付け。functionsで囲う。 f:id:nodamushi:20190126133015p:plain

 

 ただ、出来上がるXMLとやり方があまりに雑なので、もうちょっと上手なやり方ないかなと、模索してみたのがこの記事の内容です。

もっともシンプルなmalloc,freeの実装と理解

mallocはOSからメモリを動的に確保する?

 mallocをするとOSからメモリを確保出来る。

 mallocで確保したメモリはfreeでOSに返される。

 一体どこの誰だ、こんな嘘の解説を世に出したのはぁ!

 こんな説明がまかり通っているから、初心者の脳内メモリイメージが何だかよく分からない、お花畑な状態になってしまうのだ。

 なんかOSっていうスゲーのが何かしてるらしい。よくわからないけど、なんか駄目って言われてるから2回解放したら駄目らしい。使ったら解放しないと駄目って言うから解放したけど、何か動かない。なんか駄目って言われたから、やっちゃ駄目なことは分かるけど、逆に何をしても良いのか実はわかってない。

 どうも、こんな感じのイメージになっているっぽい。

 

 同じ嘘をつくなら、mallocはOSからメモリを確保しない、freeはOSにメモリを返さないと説明した方がまだマシである。

 なぜなら、mallocはOSから動的にメモリを確保する場合もあるが、動的にメモリを確保しない場合もある。そして後者の方が、理解に圧倒的に重要な項目だからだ。

 

 この記事では、OSから一切動的にメモリを確保しない極シンプルなmalloc、freeを作る。なんだかmalloc,freeがよく分からない、と思っている初心者の方々の理解の手助けになれば良いと思う。

 とにかく簡単な物が、どう動くのかを理解することが、一番手っ取り早い理解だ。

 

 

OSからメモリを動的に確保しないmallocを作ろう

 mallocにまつわる技術は中々に奥深い物があるのだが、そんな物を解説したって混乱するだけである。とにかく、ヒープだのなんだのを全て排除した、簡単で、機能としては十分なmallocとfreeを作ることを目標としよう。

 まず、一番簡単でかつ、仕様を満たすmallocとfreeの実装は以下だ。

int* my_malloc(int size)
{
  return 0;
}

void my_free(int* ptr)
{
}

 今回はわかりやすさを最優先する為に、全てをintの単位で管理する。sizeも何int分かを表す。voidポインタなんてまだキミ達には早すぎる。実際のmallocのsizeと今回のmy_mallocのsizeは単位が違うことだけを認識しておいて欲しい。

 さて、これは完全で一切バグのないmalloc、freeだ。決して問題を起こすことはない。そして、このmalloc、freeにOSは一切絡んでいないことは納得して頂けるだろう。(OSどころか、処理すらないのだから!)

 とはいえ、10という入力が来たら、長さ10のメモリのアドレスを返さなければ、流石に実用に耐えない。追加の実装が必要だ。

( ・Д・)「あ、そこでOSからメモリを持ってく…………

 持ってきません。この記事は最後までOS絡みません。

 

グローバル変数でメモリを管理

 さて、OSからメモリを取ってこないと言っても、どこかにメモリは必要。ということで、以下の様にグローバル変数memoryを用意しよう。

int memory[20];

 10個分のメモリが欲しいと言われたら、このmemoryから10個分返してあげれば、別にOSに頼らなくてもメモリを10個動的に確保したのと同じだ。

 というわけで、memoryを返してあげよう。

int memory[20];
int* my_malloc(int size)
{
  return memory;
}
void my_free(int* ptr)
{
}

 

 完璧である。さぁ、実際に使ってみよう。

 

int main(int argc, char *argv[])
{
  int *a = my_malloc(10),*b = my_malloc(10);
  a[0] = 1;    a[1] = 2;
  b[0] = 10;   b[1] = 20;
  printf("a[0]=%d,a[1]=%d,b[0]=%d,b[1]=%d\n",a[0],a[1],b[0],b[1] );
  my_free(a);  my_free(b);
  return 0;
}

 

 まぁ、言うまでもなく、これの結果は以下の様になる。

a[0]=10,a[1]=20,b[0]=10,b[1]=20

 問題は1回目のmy_mallocも2回目も、同じポインタを返しているから、同じ場所を書き換えてしまうのだ。

f:id:nodamushi:20181211214648p:plain
a,bのアドレスと範囲

使用した場所を記録する

 毎回違う場所を返せる様にするには、いったい何処が使われているか記録しておかなければならない。

 グローバル変数usedを用意し、そこに使っているか使っていないかの状態を保存しよう。usedの値が0なら未使用、1なら使用中である。

 size分の領域が必要な場合、0番目から1つ1つ、使われているかいないかを確認し、使われていなければ、使用中(1)をマークして返す。

int memory[20];
int used[20]={0};

int* my_malloc(int size)
{
  if( size <= 0 || size > 20) return 0;
  
  for(int i=0;i < 20-(size-1);i++){
    if(!used[i]){
      //size分の領域が連続して空かを確認してから返す
      int not_used=1;

      for(int k=0;k<size;k++)
        if(used[i+k]) not_used=0;

      if(not_used){
        for(int k=0;k<size;k++)  used[i+k] = 1; //使用中をマーク
        return memory + i;
      }
    }
  }
  return 0;
}

 (gotoを使う誘惑に駆られたが、わかりやすさ優先の為に我慢した)

 これで、先ほどのmainを実行してみると………

a[0]=1,a[1]=2,b[0]=10,b[1]=20

 エクセレンッ!完璧である。

f:id:nodamushi:20181211215029p:plain
usedのお陰で、aとbが違う場所に割り当てられた

 と、言いたいところだが、まだ問題がある。freeが実装されていない。

 このままだと、以下のコードでcの確保に失敗してしまう。

int main(int argc, char *argv[])
{
  int *a = my_malloc(10),*b = my_malloc(10);
  my_free(a);
  int *c = my_malloc(10);// 0が返ってしまう
  return 0;
}

 cを確保する前に同じサイズのaをfreeしているので、cは確保出来て当然だろう。

 というわけで、freeを実装したいのだが………

void my_free(int* ptr)
{
  if(!ptr) return;

  int index = ptr-memory;
  for(int i =0; i <    /* ????? */   ;i++)
    used[index + i] = 0; // 未使用に
}

 ちょっと待って、何メモリ分を解放すれば良いのか分からない。for文の条件式が作れない。

 

使用量も保存し、freeを実装

 どうやらusedをクリアする為には、ptrがどれだけの大きさのブロックなのかを保存する必要もあるらしい。

 size配列を用意しても良いが、勿体ないので、usedに使用サイズも保存しよう。

int memory[20];
int used[20]={0};

int* my_malloc(int size)
{
  if( size <= 0 || size > 20) return 0;
  
  for(int i=0;i < 20-(size-1);i++){
    if(!used[i]){
      int not_used=1;

      for(int k=0;k<size;k++)
        if(used[i+k]) not_used=0;

      if(not_used){
        for(int k=0;k<size;k++)
          used[i+k] = size-k; // 終わりまでの距離を保存
        return memory + i;
      }
    }
  }
  return 0;
}
void my_free(int* ptr)
{
  if(!ptr) return;
  
  int index = ptr-memory;
  int size  = used[index];
  for(int i = 0; i <size ;i++)
    used[i+index] = 0;
}

 これでmallocとfreeの実装は完成だ。  

 さて、実際にサンプルコードを動かしてみよう。

int main(int argc, char *argv[])
{
  int *a = my_malloc(10),*b = my_malloc(10);
  printf("a address=%d,b address = %d\n",a,b);

  my_free(a);
  int *c = my_malloc(10);
  int *d = my_malloc(10);//足りないので0
  printf("c address=%d,d address = %d\n",c,d);
  return 0;
}

 結果は下の様になり、完璧な動作をしている。

a address=4225504,b address = 4225544
c address=4225504,d address = 0

f:id:nodamushi:20181211220015p:plain
c,bがmemoryに割り当てられている

 

malloc , freeを理解する

 さて、どうだっただろうか?実に簡単だったと思う。

 無論、今回作ったmalloc,freeは実用には全く耐えない。(しかも、わざとすぐに問題を起こせる様にしてある)

 しかし、メモリの確保、解放がなんなのかを理解するに十分である。

 ここまで見てきた様に、メモリを動的に割り当てる、解放する行為にOSは一切関係ない

 OSからメモリを確保する、返すというのは、次の段階の話だ。キミ達が先ず理解しなくてはならないのは、OSレベルの話ではなく、アプリケーションレイヤーでの話なのだ。

 例えば、メモリ確保直後の値はどうなっているのだろう?初期化されているだろうか?

 否である。mallocやfree関数内でmemory配列に対して一切の操作はしていない。誰かが使った後だと、使いっぱなしで、次に渡される。

 

 例えば、NULL(0)の解放をするとどうなるだろう?問題が何か起こるだろうか?

my_free(0);
my_free(NULL);

 これは、実は何も問題を起こさない。なぜなら、1行目で即座にreturnしているからだ。 別に私が気を利かしたのではなく、freeがそういう定義だからである。

void my_free(int* ptr)
{
  if(!ptr) return;

 

 

 じゃぁ、0が大丈夫なら1は?

my_free(1);

void my_free(int* ptr)//ptr = 1
{
  if(!ptr) return;
  
  int index = ptr-memory; // ここが負になる
  int size  = used[index];
  for(int i = 0; i <size ;i++)
    used[i+index] = 0;  // used [ 0 + 負 = 負 ]  = 0;という処理になる
}

 実装に依存するが、今回の実装では明らかに問題が起こる。つまり、やってはならないと分かるだろう。

 なら、次はmallocで確保した領域を途中から開放した場合はどうなるだろう。

int *a = my_malloc(10);
my_free(a + 5);
int *b = my_malloc(10);
my_free(a);
int *c = my_malloc(10);

 今回作ったmallocはとても簡単だ。動きをゆっくり追ってみて欲しい。(そのために、役に立たない簡単なmallocを作ったのだから。)

  1. aが0番目に割り当てられる
  2. my_free(a+5) を解放しようとする。used[5]=5なので、5,6,7,8,9を未使用に変更する
  3. 5~14が未使用なのでbが5番目に割り当てられる
  4. my_free(a)でused[0]=10なので、0~9を未使用に変更する
  5. 0~9が未使用なのでcが0番目に割り当てられる

 最終的には以下の図の様になる。酷い有様だ。

f:id:nodamushi:20181211220851p:plain
b,cが中途半端に重なる

 途中から解放したり、mallocで取得したアドレスをなくしたら困ると分かるだろう。

 

 では、二回解放をするとどうなるだろう。今回の実装では、直ちに影響はない。だが、暫くすると影響がある。

int *a = my_malloc(5);
int *b = my_malloc(5);
my_free(a);
my_free(b);
my_free(a);//特に問題は起こさない
int *c = my_malloc(10);

my_free(b);// cを半分解放する
int *d = my_malloc(10);

 最終的にこれは以下の図の様なメモリ配置になる。やっぱり酷い有様だ。

f:id:nodamushi:20181211222515p:plain
bの2回解放の所為で、c,dが重なっている

 小さなメモリを何度も取るとどうなるだろう。

int *a = my_malloc(5);//残り容量 15
int *b = my_malloc(5);//残り容量 10
int *c = my_malloc(5);//残り容量 5
my_free(b);//残り容量 10

int *d = my_malloc(10); // 失敗

 残り容量は10なのに、dの確保に失敗する。これは空き容量の断片化が起こるからだ。

f:id:nodamushi:20181211222836p:plain
容量は足りていてもdを確保出来ない

 

 gccとかのmallocは、私が作ったのに比べずっと賢いが、基本的にはmalloc,freeは頻繁に行わず、大きな単位で行った方が良い。

 実行環境にも依存するが、小さいオブジェクトなら、普通にスタックに置けば良い(ローカル変数で定義すれば良い)。SPの移動でメモリの解放がされるので0コストだ。(組み込みだとSRAMが小さくて、すぐパンクする可能性があるので気をつけたし)

 

 

まとめ

 

 さて、malloc,freeの大事なところを理解するのに、OSからメモリを確保とか糞みたいなどうでも良いことだと理解して貰えただろうか。

 mallocをするとOSからメモリを確保出来る………。確かにその通りではある。

 だが、その話を鵜呑みにして、OSからメモリがご降臨されて、OSにメモリがご帰還なさっていく様なふんわりしたイメージをいつまでも持っていると、大事なことが理解出来ない。

 メモリの確保が行われるのは、mallocが管理しているリソースが足りなくなった時だ。常にOSからメモリを確保している訳ではない。

 mallocはOSからメモリを確保しないし、freeはOSにメモリを解放しないと覚えていた方がまだマシだ。

 今日から「mallocとfreeはメモリを確保、解放しない」と念仏の様に唱えてC言語を捨て、RustやC++を完全に理解しよう。

 

参考文献:glibcのmalloc実装

 実際のmallocがどういう実装になっているのかに興味が湧いたら、以下の記事を読んでみると良いでしょう

Glibc malloc internal

あなたのメモリはどこから来てる?malloc入門 - Qiita

 あと、今回は説明事項から省いたsizeofに関する話など。

sizeof演算子にまつわるアレコレ - Qiita

侍エンジニア塾のC言語のサンプルがヤバすぎる。 - Qiita

Cの本

 楽してとか、わかる、とか、そういうタイトルで良い本だったことはない

苦しんで覚えるC言語

苦しんで覚えるC言語

C言語ポインタ完全制覇 (標準プログラマーズライブラリ)

C言語ポインタ完全制覇 (標準プログラマーズライブラリ)

プログラミング言語C 第2版 ANSI規格準拠

プログラミング言語C 第2版 ANSI規格準拠

(古いのであまりお勧めはしないけど)

コンピューターの起源:ジャカード織機の仕組み

 富岡製糸場然り、工業の近代化において、衣服にまつわる技術は多く世に貢献してきた。

 さて、私たちが今扱っているコンピュータも元を辿っていくと、実はジャカード織機という織機に辿り着く。

 いわゆるパンチカードによるプログラミングの原型とも言えるべき機械で、パンチカードの穴の並びで布の縦糸をどう動かすかを決めることが出来た。これによって、一気に複雑な模様を短時間で作れる様になったらしい。

 

 で、そうは言われても、じゃぁ、どういう風に動くのか日本語で調べたりしてもよく分かってなかったんだけど、偶然素晴らしい動画を見つけて、よーく理解出来た。

www.youtube.com

 こういうデカい機械が、がっしょんがっしょん動くのは、いつになっても浪漫がある。

 

 さて、動画に全てを任せても良いけど、簡単に解説。

 まず、一般には縦糸と横糸を交互に上下に交差させることで布のを編む。これが平織。

f:id:nodamushi:20181209212951p:plain:w320
平織り

 これを、交互ではなくすると、模様になる。図では隙間のせいで模様に見えないけど、布にする時には隙間は詰めるので、交点の部分しか表面からは見えない。

f:id:nodamushi:20181209213134p:plain:w320
ジャカード織

 

   ジャカード織機はパンチカードの情報から、自動的にこのパターンを作り出す。

 パンチカードが無い、もしくはパンチカードの穴が開いたところでは、縦糸を動かす針が上下に動く装置に引っかかり、同じように上下に動く。(縦糸が横糸の上になる)

f:id:nodamushi:20181209211803p:plain:w320
パンチカードが無い状態

 一方で、パンチカードがあって、穴がない場合は、押し込まれ、引っかからなくなり、上下に動作しなくなる。(縦糸が横糸よりも下になる)

f:id:nodamushi:20181209213705p:plain:w320
パンチカードがある状態

   こうやって全ての縦糸を一本ずつ操作し、縦糸と横糸の上下を組み合わせて目的の模様を自動的に作り出す。すごいね。

 ていうか、模様の図からパンチカードに変換する職人もスゴイと思うわ。

ExcelやWordに判子の無駄はなくならない

「証明書が必要だって言うから、証明書取りに行くじゃん?」

「せやな?」

「で、その証明書を発行するのに判子が必要だっていう。」

「よくあるな。」

「しゃーないから、判子持ってって証明書もらうじゃん?」

「うん」

「で、その証明書持ってったら、また判子が必要だって。」

 日本でよくある光景。そんな日本で、より頭が痛くなる記事を見つけた。

forest.watch.impress.co.jp

 Lhaplusが根強く1位を保ってるのも驚き(その上、7-zipが14位でだいぶ離されている)だが、こんなソフトが出てることも、そしてそれのダウロード数が増える日本も頭が痛い。もう一週回って、こういう判子を押せる様に、親切にしてあげるエンジニアの方が悪いんじゃないのかという気がしてきた。

 未だに、こんな↓判子の画像をWordとかExcelとかに普通に貼らせるからね。自分がこの都市伝説の様な作業をするとき、軽く興奮すら覚える。

f:id:nodamushi:20181208224130p:plain

 しかも、普通の企業なら、提出ファイルは誰がチェックしたかとか残るシステムが入ってるから、完全に冗長。加えて、判子がないと差し戻された暁には、軽く絶頂を迎え、アヘ顔を晒すことも辞さない。実に気持ちが良い。

 判子に限らず、印刷することを目的にして、こんな↓感じに面倒くさく表組みされたExcelやWordファイルへの記入とかも、都市伝説じゃなくて、至って極普通のことだ。

f:id:nodamushi:20181208231122p:plain:w320

 ご丁寧に判子を貼る場所も用意されている。見覚えがある人なら何処に貼るか分かるだろう。

 

言うだけ無駄で、変わることはない

 情報処理をする人からすると、こういった不合理や無駄は非常に阿呆らしく見える。役所仕事だの批判の対象になることも多い。

 だが、断言しよう。変わらない。言うだけ無駄である。むしろ、言うだけ白い目で見られるだけだ。ネ申エクセルもなくなることは無いだろう。問題視する方がキモイのだ。

 

 例えば、判子画像は単純にやめれば良いだけだ。まともな企業なら、情報の管理をする義務がある以上、こういった提出物の承認フローを管理するシステムが導入されているはずだ。判子なんていくらでも複製可能なデータに信頼性はない。

 社外秘、みたいな判子についても必要ない。タイトル部分などに社外秘、NDAなどと書いておけば十分だ。画像であれ何であれ暗号化されていない電子データなどいくらでも改変出来るのだから、それをもって変更されていないかどうかなど分からない。

 それどころか、むしろ画像の様に後から処理しにくいデータで管理するべきではない。社外秘、NDAなどの文字情報の状態なら、PDF化する際に処理を加えて、たとえばフォントの中に情報を埋め込むことも容易だ。判子画像の状態でも不可能とは言わないが、難易度は上がる。管理する情報は平易である程良い。

japan.cnet.com

 ということを、上の人間が納得しないといけないので、日本では絶対に後数十年はなくならない。彼らは、判子があれば信用出来るなんて甘いことは当然考えてはいないが、判子がない物はそもそも視界に入らない。なんせ、日本のトップ達はパソコンなんて下賤なもの触ったこともないが、指示を出すのは天才な高貴なお方達なのだから。  

 さて、上の人間は承知しないだろうが、それでも判子に関しては割と納得してくれる人はいる。(白い目では見られるが)

 しかし、こっちが問題だと納得してくれた一般人は未だ誰一人いない。

f:id:nodamushi:20181208231122p:plain:w320

 

 私が思う問題点は大きく以下の二つである。

  1. 入力が面倒くさい
  2. プログラムで情報を抜き出すのが面倒くさい

 このフォーマットは基本的に、「読む側」の事しか考えておらず、書く、プログラムで処理するということが面倒くさい=コストとなる。

 まず、入力が面倒くさい点について。例えば、少しでも面倒くさくない入力はこういう状態だ。

f:id:nodamushi:20181208233331p:plain

 先ほどと違って、上から下に入力するだけである。横に見る必要はないし、構成の意味を考える必要が無い。とにかく、上から下に入力していけば良い。見逃すことも少ない。

 しかし、普通の人は、元の表組み状態を面倒くさいと思わないので、この理屈は通じない。むしろ、縦スクロールが入る分、面倒くさいと感じたり、冷たいと感じる様だ。

 次に、プログラムで情報を抜き出すのが面倒くさい、に関してだが、普通の人間はそもそもプログラムはおろか、情報処理をしない。だから、この書き方は後で情報処理したい時にきっと困ることになる、と言っても、「ふーん、で?」で終わりである。関係ないのだ。

 しかも、実際、殆どの書類は今後何らかの情報処理されることもなく、どこぞのフォルダの文鎮と化してお終いである。一回見られてお終いなのだ。効率化というのは、何度も行われるからこそ意味を成す。実装に1日、効率化は一人1分(しかも1回しかやらない)なんかだと、500人は入力しないとペイしない。実に無駄な効率化だ。

 さらに、こういうことを実現しようと思えば、やってくれる人が居るならそれでいいが、誰もいないなら、そのための余計な勉強までしなくてはならない。

 そもそも最近はパソコンなんて、時代遅れな骨董品を使える方が稀だ。ただでさえ、社会に出たら強制的に使わされる苦痛を味わされるのに、更にまだ勉強しろというのは、流石にちょっと拷問に近い行為だとは思う。(感覚が分からないなら、80年代のワークステーションを強制的に使わされて、かつ、BASICやCOBOL、ないしパンチカードを覚えさせられると考えてみよう。吐き気がしてくるはずだ。)

 誰が、そんな将来役にも立たない(っていうか淘汰される)パソコンの勉強をするというのだ。それぐらいなら、速記や会計の勉強をした方がマシである。

 

 というわけで、言うだけ無駄である。今後も変わらないであろう。パソコンは手書きの清書機械でしかないのだ。

 むしろ、機械学習の性能が上がって手書き文字認識精度が上がったせいで、書類は手書きに回帰する可能性すらあると言って良い。

Shell(Bash,Power Shell)でバージョンソート

x.y.z形式のバージョンをソート

 バージョンの形式は、「接頭辞x.y.z」とする。これのソートは割と簡単。(接頭辞はvとかver.とか)

 

Shell(Bash)

※最初のVERSIONは、各行にversion情報がある情報を、パイプに流すコマンドなど

VERSION   | sed 's/^接頭辞//' | sort -n -t . -k 1,1 -k 2,2 -k 3,3 | sed 's/^/接頭辞/'

 sedで一度接頭辞を削除した後、「.」で文字を区切って数値としてsortし、最後に接頭辞をsedで再度付加する。

Power Shell

※最初のVERSIONは、各行にversion情報がある情報を、パイプに流すコマンドなど

VERSION |  sort {[Version]$_.Replace('接頭辞','')} 

 接頭辞を削除した文字列をSystem.Versionとしてソート。PowerShellの方が簡単だと…

 

x.y.z-αをソートしたい

 「接頭辞x.y.z」で済めば良いのだが、v0.1.2を複数作りたい(alpha版やらbeta版やら)とかなると面倒。しかも、-αの部分はあったりなかったりすることもよくある。

 ここでは、何もなし「接頭辞x.y.z」、日付「接頭辞x.y.z-YYYYMMDD」、ベータ版「接頭辞x.y.z-b0」、アルファ版「接頭辞x.y.z-a0」の順に並べる。

 

Shell(Bash)

上手い方法は思いつかなかったので、もうAWKでa,b,日付をゴリゴリと数値に変換した。

※最初のVERSIONは、各行にversion情報がある情報を、パイプに流すコマンドなど

VERSION | \
gawk 'match($0, /([0-9]+)\.([0-9]+)\.([0-9]+)(-([ab])?([0-9]+))?/, v){
  if(v[4]==""){v[5] = 0;v[6]=0;}
  else if(v[5]==""){v[5] = 1;}
  else if(v[5]=="b"){v[5] = 2;}
  else if(v[5]=="a"){v[5] = 3;}
  printf "%3s%3s%3s%s%8s@%s\n",v[1],v[2],v[3],v[5],v[6],$0;}' |\
sort  | sed 's/^.*@//'

 AWKで無理矢理-αの部分を数値の形式に変換している。格好いい事なんてせずに、もうベタベタの変換だ。

 -α部分が何もなければ「0.0」に、日付なら「1.日付」に、ベータ、アルファならそれぞれ2,3の番号を振った。RCやら色々増やすなら、if文を追加すれば良いでしょう。

 最後にprintfで成形して出力する。%3sなんかの文字列長は好きな様にすれば良いんじゃないかな。お好みなら%03dでもいいね。適当な区切り文字(@)で$0も保存して、後で取り出せるようにもしてある。(文字数カウントしても良いけど、面倒くさいし)

 後は、sortで並び替えて、sedで@以降を取り出す。

 

PowerShell

 やっぱゴリった。誰か上手い方法を教えて。

※VERSIONは、各行にversion情報がある情報を、パイプに流すコマンドなど

$version_regex=[regex]"(\d+\.\d+\.\d+)(-([ab])?(\d+))?"
function Get-VersionKey($x){
    If($x -match $version_regex){
        $s = $Matches[1]
        If(!$Matches[2]){
            $s = $s+"0.0"
        }ElseIf(!$Matches[3]){
            $s = $s+"1." +$Matches[4].PadLeft(8,"0")
        }ElseIf($Matches[3] -eq "a"){
            $s = $s + "3."+$Matches[4].PadLeft(8,"0")
        }ElseIf($Matches[3] -eq "b"){
            $s = $s + "2."+$Matches[4].PadLeft(8,"0")
        }
        return [Version]$s
    }
}

VERSION | sort { Get-VersionKey($_) }

 やり方はさっきとBashとほぼ一緒。一度"x.y.zα.n"という形に変換した後(日付のYYYYMMDDが8文字なので、PadLeftで全て8文字にしてある。変換される型はint32なので、ギリギリ入る。)、System.Versionに変換、それを元にソートする。

 

参考

PowerShell実践ガイドブック ~クロスプラットフォーム対応の次世代シェルを徹底解説~

PowerShell実践ガイドブック ~クロスプラットフォーム対応の次世代シェルを徹底解説~

「シェル芸」に効く!AWK処方箋

「シェル芸」に効く!AWK処方箋

SystemVerilog: ビット長拡張(符号拡張)の書き方

 Mbitの信号をNbitに符号拡張したい、0fillしてビット長を変更したい場合、Verilogは書くのが面倒くさい。でも、SystemVerilogなら短く書けた。

結論

 以下のコードで符号拡張、ビット長拡張が出来る。S,Dはパラメータでも、直値(数値)でも良い。

logic [S-1:0] src;
logic [D-1:0] dst_unsigned  ,  dst_signed;

assign dst_unsigned = D'(unsigned'(src));
assign dst_signed   = D'(signed'(src));

 これ、assignで何やってるかというと、srcのキャストである。以下の様にキャストされてdstに接続される。

  1. unsigned'(src)、signed'(src) でsrcをunsigned型、signed型にキャスト
  2. D'(x)でxをDbitにサイズをキャスト

 サイズをキャストする際には、符号が考慮される。unsignedは必要はないはずだが、明示した方がいいと思う。こう、暗黙って嫌いなんです。

 詳細はSystemVerilog 3.1a Language Reference Manualを参照して欲しい。27p(PDFビューア上では43p)辺りである。

 一応、Verilatorによるシミュレーション、Cadenceのツールによる合成で確認しています。あと、Quartus PrimeでElaborateは通った(ピンアサインとか面倒くさいし、合成はしてない)

 

Verilogでのよくある書き方

 例えば、以下の記事の様に一般的な書き方をするとこうなる。

logic [S-1:0] src;
logic [D-1:0] dst_unsigned,dst_signed;

assign dst_unsigned = { {D-S{1'b0}} , src };
assign dst_signed   = { {D-S{src[S-1]}} , src };

 これ、問題があってD,Sがパラメタライズされていて、さらにD-S が0になるとき、処理系によってはエラーになるのだ。糞言語め

 先に示したキャストを使わずに、D=Sの問題を何とか回避しようとすると、以下の様にならざるを得ない。

always_comb begin
  dst_unsigned = {S{1'b0}};
  dst_signed   = {S{src[S-1]}};
  dst_unsigned[S-1:0] = src;
  dst_signed[S-1:0]   = src;
end

//もしくはfor文
always_comb begin
  int i;
  for(i = S; i < D ; i = i + 1)begin
    dst_unsigned[i] = 1'b0;
    dst_signed[i]   = src[S-1];
  end
  dst_unsigned[S-1:0] = src;
  dst_signed[S-1:0]   = src;
end

 うーん、まぁ、合成は出来るだろうけど、綺麗じゃないよねぇ。

参考URL