多分 ChatGPT でも読めるページ記述言語

# 多分 ChatGPT でも読めるページ記述言語

<details>
<summary>ChatGPT は SATO のラベルプリンタ用ドライバを SBPL で設計できますか？</summary>

[org-ai - MELPA](https://melpa.org/#/org-ai) で `(setq org-ai-default-chat-model "gpt-4o")` が設定されてる時の実行結果

```
#+begin_ai
ChatGPT は SATO のラベルプリンタ用ドライバを SBPL で設計できますか？

[AI]: ChatGPT はプログラムコードやドライバを直接設計・開発することはできませんが、SATO のラベルプリンタ用ドライバを SBPL（SATO Barcode Printer Language）で開発する際のアドバイスや一般的な情報を提供することは可能です。

SBPL は、SATO のラベルプリンタで使用されるコマンドベースのプリンタ制御言語です。これを使ってラベルの内容やレイアウトを定義します。以下は、その設計において考慮すべき基本的な事項のいくつかです。

1. **SBPL コマンドの理解**: SBPL コマンドの公式マニュアルを参照して、どのようにラベルをレイアウトし、テキスト、バーコード、グラフィックスを配置するかを学びます。

2. **ラベルの設計**: 印刷するラベルのフォーマットをまず設計し、必要なフィールド（例えば、バーコード、テキスト、画像など）を明確にします。

3. **コマンドシーケンスの作成**: ラベルのレイアウトに基づいて適切な SBPL コマンドシーケンスを作成します。これは通常、プリンタに送信するファイルに含まれます。

4. **テストとデバッグ**: プリンタにコマンドを送信して、ラベルが意図したとおりに印刷されるかを確認します。必要に応じてコマンドシーケンスを修正します。

5. **プログラムでの統合**: 必要であれば、SBPL コマンド生成を自動化するプログラムを作成し、データベースなどからデータを取得してラベルを印刷する機能を実装します。

開発を始めるには、SATO の公式ドキュメントやサポートを参照することをお勧めします。具体的な技術支援やコードレビューが必要な場合は、経験のある開発者やエンジニアに協力を依頼するのが良いでしょう。

[ME]: 
#+end_ai
```

</details>

まだ現代では経験のある開発者に依頼しよう、と返ってくるようです。具体的な SBPL の生成も依頼しましたが、構文上おかしなものを吐き出します。単純に情報源が少ないのであろうと想像できます。

---

前回は ImHex のパターン言語を使って SBPL を構造化しました。

https://qiita.com/karronoli/items/203b7d994c6bfbc379a2

記事を公開して 1 年が経ちましたが、ChatGPT はまだ SBPL の構造を理解できてるようには見えません。ソフトウェアフレンドリな構造化が更に必要ということです。ということで Yacc などと同様に BNF ライクな構文で文法を表現できる Ragel を使ってみます。

https://www.colm.net/open-source/ragel/

```console
$ ragel -v
Ragel State Machine Compiler version 6.10 March 2017
Copyright (c) 2001-2009 by Adrian Thurston
$
```

インストールは Mac なら [brew install ragel](https://formulae.brew.sh/formula/ragel)、Ubuntu なら [apt-get install ragel](https://packages.ubuntu.com/focal/ragel) あたりでいけます。

## Ragel x SBPL

SBPL についてはいつもの仕様書「I Programming Reference For MB200i MB400i MB410i」から引用していきましょう。

https://www.satoamerica.com/resources/documentation/manuals

![a-011.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4162/cfc348ef-04ab-312b-c513-6685a05a92f6.png)

まずはこのページで紹介されている ASCII コードの通信開始、通信終了を表現してみましょう。ファイル名は stx_etx.rl とします。シンタックスハイライトはありません。

```
%%{
  machine stx_etx;

  STX = 0x02;
  ETX = 0x03;

  main := STX ETX;
}%%
```

これは DOT 言語を経由することで状態遷移図にコンパイルできます。

```sh
ragel -Vp stx_etx.rl | dot -Tpng > stx_etx.png
```

![stx_etx.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4162/41e2e54a-c890-cc95-cd02-2d7b01b9f0d3.png)

1. IN とか main とかが開始状態
2. 状態 1 が STX (0x02) を出力して状態 2 へ
3. 状態 2 が ETX (0x03) を出力して終了状態へ

---

次は命令をまとめるための A 命令と Z 命令を表現してみましょう。この命令フォーマットは `<ESC>{Command}` となっています。
これは 1 回の通信中にセットで何度も使います。基本的な印字命令においては必ずある命令です。ファイル名は az.rl とします。

```
%%{
  machine az;

  STX = 0x02;
  ETX = 0x03;
  ESC = 0x1b;

  OPE_A = ESC "A";
  OPE_Z = ESC "Z";

  main := STX (OPE_A OPE_Z)* ETX;
}%%
```

```sh
ragel -Vp az.rl | dot -Tpng > az.png
```

![az.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4162/66f28274-c24d-ecea-19f0-6082860c1fc6.png)

今のところやりたいことが読み取りやすいのではないでしょうか。画像だと 0x1b が 10 進数の 27 で書き直されてるのが見づらさを助長するのが惜しいところ。

---

さらに命令を盛っていきましょう。次は印字位置の座標移動で水平位置を決める H 命令と垂直位置を決める V 命令です。この命令フォーマットは `<ESC>{Command}{Data}` となっています。
セットで使うことが多いと思いますが、片方だけの使用でも問題ないです。ファイル名は hv.rl とします。

```
%%{
  machine hv;

  STX = 0x02;
  ETX = 0x03;
  ESC = 0x1b;

  OPE_A = ESC "A";
  OPE_Z = ESC "Z";
  OPE_H = ESC "H" [0-9]{4};
  OPE_V = ESC "V" [0-9]{4};

  main := STX (OPE_A OPE_H* OPE_V* OPE_Z)* ETX;
}%%
```

```sh
ragel -Vp hv.rl | dot -Tpng > hv.png
```

![hv.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4162/73e14874-5f6d-47d1-3ee3-894297c337d5.png)

状態遷移図が見づらくなってくると意図通りに動くのか不安になってきましたね？

## ソフトウェアのパーツとして使う

閑話休題。
ここまでで作った座標移動するだけの状態遷移図をソフトウェアのパーツとして使ってみましょう。

- https://github.com/karronoli/ragel_sbpl_tutorial/blob/main/hv2.rl

状態遷移図としては hv.rl と同じですが、適当なホスト言語用のステートマシンに変換して実行します。今回は Ruby を使いました。

```sh
# hv2.rb を生成する
ragel -R hv2.rl
```

最近の Ruby だと gdb ライクなデバッガの呼び出しが `require "debug/start"` を挿入するだけなので手軽さを感じています。
実行時のコンソール出力はこんな感じです。命令が通ったときに ok とコンソール出力させるようにしました。

```console
$ ruby hv2.rb
[237, 242] in hv2.rb
   237|
   238|
   239| end
   240|
   241| require "debug/start"
=> 242| 1
=>#0    <main> at hv2.rb:242
(ruby) parse "\u0002\u0003"
ok
nil
(ruby) parse "\u0002\u0004"
nil
(ruby) parse "\u0002\u001bA\u001bH1234\u001bZ\u0003"
ok
nil
(ruby) parse "\u0002\u001bA\u001bH1234\u001bZ\u001bA\u001bV5678\u001bZ\u0003"
ok
nil
(ruby) parse "\u0002\u001bA\u001bH1234\u001bV5678\u001bZ\u0003"
ok
nil
(ruby) parse "\u0002\u001bA\u001bV1234\u001bZ\u001bA\u001bH5678\u001bZ\u0003"
ok
nil
(ruby) parse "\u0002\u001bA\u001bV1234\u001bZ\u001bA\u001bh5678\u001bZ\u0003"
nil
(rdbg)
```

分かりやすさでいえば命令毎にエラーハンドラを設定したいところです。ただそうやると状態遷移図が大きく変わるので今回はやりませんでした。

## Windows bitmap 形式の画像印字命令

最後に扱うのは GM 命令です。

![a-084.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4162/acec28f7-e200-1ec2-a57f-fc9a1cf4e11e.png)

この命令フォーマットも `<ESC>{Command}{Data}` のバリエーションの一種です。ただし `{Data}` の部分は座標移動のものより複雑です。
それぞれの括弧に具体的な値を入れてみましょう。画像データの長さは説明用に 4 バイトとします。

- `{Command}`: GM
- カンマ前 `{Data}`: 0004
  - カンマの後ろの `{Data}` の長さが 4 バイト
- , （カンマ）
  - `{Data}` のセパレータ
- カンマ後 `{Data}`: BMP1
  - サンプル用のデータ
  - 構文を解析する際はカンマの前後で辻褄が合えば問題ない

ここで BNF の表現力について考えてみます。前 `{Data}` の数字を 4 桁の 10 進数として解釈し、後 `{Data}` を読み取るバイト数として扱う、そんなことが実現できるのでしょうか？答えはノーです。こういったときは Yacc などと同様で、ホスト言語と密接に連携させるアクションを定義することで機能を実現します。

- https://github.com/karronoli/ragel_sbpl_tutorial/blob/main/gm.rl

```console
$ ragel -R gm.rl; ruby gm.rb
[346, 351] in gm.rb
   346|
   347|
   348| end
   349|
   350| require "debug/start"
=> 351| 1
=>#0    <main> at gm.rb:351
(ruby) parse "\u0002\u001bA\u001bGM00004,BMP1\u001bZ\u0003"
ok
nil
(rdbg)
```

## まとめ

今回は Ragel で構文解析器を作りました。
ChatGPT による Ragel のコードの生成もまともに使えるものではなく、今回の記事が情報源の 1 つとなってくれるとうれしいです。