symbol各言語のgeneratorを作成するために解読する

# catbuffer generator とは
Symbolではアプリゲーション側で作成したトランザクションを、ペイロード（16進数）にシリアライズし、アナウンス(PUT)します。また、REST APIから取得したデータ（16進数）をデシリアライズし活用します。

トランザクション内で使われるすべてのモデルを生成するのがgeneratorで、generatorを実行するとParserが起動しSchemaを読み込んでmodelsが生成されます。
※schemaについてはこちらが参考になります
 [TransferTransactionのスキーマを学習しましょう！](https://medium.com/opening-line/transfertransaction%E3%81%AE%E3%82%B9%E3%82%AD%E3%83%BC%E3%83%9E%E3%82%92%E5%AD%A6%E7%BF%92%E3%81%97%E3%81%BE%E3%81%97%E3%82%87%E3%81%86-1160a173ff96) by [松本さん](https://twitter.com/kazumasamatsumo)

:::note info
例）JavaScript
実行ファイル: https://github.com/symbol/symbol/blob/dev/sdk/javascript/scripts/run_catbuffer_generator.sh
schemas: https://github.com/symbol/symbol/tree/dev/catbuffer/schemas/symbol
parser: https://github.com/symbol/symbol/tree/dev/catbuffer/parser/catparser
parserでは言語ごとのgeneratorを使う
generator: https://github.com/symbol/symbol/tree/dev/sdk/javascript/generator
model: https://github.com/symbol/symbol/blob/dev/sdk/javascript/src/symbol/models.js
:::

# なぜgeneratorが必要なのか？モデルベタ書きでよくね？
※推測を含むので参考程度に
大きく2つ理由があると思います。
一つ目は、例えば過去にあったバージョンアップでRevocable（回収可能）なモザイクとそのトランザクションがありました。このような場合、内容はSchemaに記載されるので、ModelsのアップデートもGeneratorで再生成すれば良いだけです。

２つ目は、Schemaの文法がSymbol（Catapult）だけでなくNEM（NIS1）も対応しています。ここでのGenaratorはSymbolのためでなくこのSchemaの文法に沿ったもの全てに対応するので、同時にNEMとSymbolのModelsが生成されます。今後、同じSchemaの文法に沿ったブロックチェーンが生まれれば、Modelｓはそのブロックチェーンも同時に生成されるため、SDK制作が非常に捗ると考えます。

# modelsってそもそも？
Symbol sdk v3では例えば以下のようにトランザクションを生成します。
``` ts
const tx = facade.transactionFactory.create({
	type: 'transfer_transaction',
	signerPublicKey: keypair.publicKey.toString(),
	deadline:createDeadline(),
	recipientAddress: 'TD4WXUXYAPPB5Y42VT6FHISG6T32I2IBUXIKKPQ',
	message:[0,...(new TextEncoder('utf-8')).encode('GoodLuck!')],
	mosaics: [
		{ mosaicId: 0x3A8416DB2D53B6C8n, amount: 1000000n }
	]
})
```
参考: [Symbolの新SDKからSSS(Safely Sign Symbol) Extensionを利用してみる](https://qiita.com/nem_takanobu/items/187748f459395dbe41e3)

詳細はぜひご自身で見ていただければ良いのですがざっくりここでのcreate()ではModelsの`TransactionFactory.createByName()` (https://github.com/symbol/symbol/blob/d39c00b72791094fbbb194d01cfd2b2afb042470/sdk/javascript/src/symbol/models.js#L11429) にtypeを渡しています。
つまり
`TransactionFactory.createByName('transfer_transaction')`で戻り値は`TransferTransaction`クラス。さらに他の引数をそのクラスにセットしています。また、この`TransferTransaction`はModels内のこちらです。
(https://github.com/symbol/symbol/blob/d39c00b72791094fbbb194d01cfd2b2afb042470/sdk/javascript/src/symbol/models.js#L10963)

また、この`TransferTransaction`のコンストラクタでは以下のように例えば`Signature`を初期化しており、その`Signature`は[ここに定義されています。](https://github.com/symbol/symbol/blob/d39c00b72791094fbbb194d01cfd2b2afb042470/sdk/javascript/src/symbol/models.js#L340)
```ts
constructor() {
		this._signature = new Signature();
		this._signerPublicKey = new PublicKey();
		this._version = TransferTransaction.TRANSACTION_VERSION;
		this._network = NetworkType.MAINNET;
		this._type = TransferTransaction.TRANSACTION_TYPE;
		this._fee = new Amount();
		this._deadline = new Timestamp();
		this._recipientAddress = new UnresolvedAddress();
		this._mosaics = [];
		this._message = new Uint8Array();
		this._verifiableEntityHeaderReserved_1 = 0; // reserved field
		this._entityBodyReserved_1 = 0; // reserved field
		this._transferTransactionBodyReserved_1 = 0; // reserved field
		this._transferTransactionBodyReserved_2 = 0; // reserved field
	}
```

このようにトランザクションを生成するためのモデルがすべて記載されている。それがModelsになります。

# generator の流れを確認する
新たに作成する前に簡単にどのようなことがgeneratorで行われているのか？また自分で別言語用のgeneratorを作成する場合に何が必要なのか？を確認していきましょう。

## main.py
Parserのmain.pyのmain関数を見てみる（なお、Pythonの知識はほとんど無いのであしからず）
実行されるのはここです※実行するときのオプションで `--output` を選択するから
```py
	if args.output:
		if args.generator:
			generator_class = _load_generator_class(args.generator)
			generator_class.generate(processor.type_descriptors, args.output)
```
`generator_class.generate` に2つの引数を渡す
松本さんとGimreさんのやりとりを見る限り引数は
１．Schema
２．出力するディレクトリ
で、ここで言うgenerator_classは言語ごとGeneratorのGenerator.pyになります
https://github.com/symbol/symbol/blob/dev/sdk/javascript/generator/Generator.py

## Generator.py
```Generator.py
class Generator:
	@staticmethod
	def generate(ast_models, output):
		print(f'python catbuffer generator called with output: {output}')
		generate_files(ast_models, Path(output))
```
先程のmain.pyで実行されるgenerate()ですね。
ここでは受け取った引数をそのまま渡してる程度なのでgenerate_files()を見てみます
```py
def generate_files(ast_models, output_directory: Path):
	factory_map = build_factory_map(ast_models)
	extend_models(ast_models, create_printer)

	output_directory.mkdir(exist_ok=True)

	with open(output_directory / 'models.js', 'w', encoding='utf8', newline='\n') as output_file:
		output_file.write(
			'''/* eslint-disable max-len, object-property-newline, no-underscore-dangle, no-use-before-define */
const { BaseValue } = require('../BaseValue');
const { ByteArray } = require('../ByteArray');
const { BufferView } = require('../utils/BufferView');
const { Writer } = require('../utils/Writer');
const arrayHelpers = require('../utils/arrayHelpers');
const converter = require('../utils/converter');
'''
		)

		for ast_model in ast_models:
			generator = TypeFormatter(to_type_formatter_instance(ast_model))
			output_file.write(str(generator))
			output_file.write('\n')

		factories = []
		factory_names = []
		for ast_model in ast_models:
			if DisplayType.STRUCT == ast_model.display_type and ast_model.is_abstract:
				factory_formatter = FactoryFormatter(factory_map, ast_model)
				factory_generator = FactoryClassFormatter(factory_formatter)
				factory_names.append(factory_formatter.typename)
				factories.append(str(factory_generator))

		output_file.write('\n'.join(factories))

		generate_module_exports(output_file, list(map(lambda ast_model: ast_model.name, ast_models)) + factory_names)

```
出力ディレクトリを作成して、model.jsというファイルに書き込んでいます。

javascriptの場合は最初に以下を記述しています。
`const { BaseValue } = require('../BaseValue');`
生成後のmodel.jsを見ると冒頭はそこが記述されていますね。ここは各言語でほかファイルのインポートなどに活用する箇所です。

次はここ
```py
for ast_model in ast_models:
			generator = TypeFormatter(to_type_formatter_instance(ast_model))
			output_file.write(str(generator))
			output_file.write('\n')

factories = []
factory_names = []
for ast_model in ast_models:
	if DisplayType.STRUCT == ast_model.display_type and ast_model.is_abstract:
		factory_formatter = FactoryFormatter(factory_map, ast_model)
		factory_generator = FactoryClassFormatter(factory_formatter)
		factory_names.append(factory_formatter.typename)
		factories.append(str(factory_generator))

```
2つのfor文があります。

先に2つ目のfor文を説明すると、ここでは先程使用した`TransactionFactory`や`EmbeddedTransactionFactory`が書き出される箇所です。`EmbeddedTransactionFactory`についてはSymbolではアグリゲートトランザクションが使えるので、アグリゲート内でのInnerTransactionを生成するためのものです。（Innner=Embedded（埋め込み））

1つ目が、そのFactoryでの戻り値である各トランザクションクラスやそのトランザクションクラスで使用されるフィールドクラスなどを生成するための箇所です。

それではここからそれぞれ解剖していきます。

## TypeFormatter
1つ目のfor文では各ast_modelをto_type_formatter_instance()に渡し、さらにその戻り値をTypeFormatter()の引数としています。
```py
def to_type_formatter_instance(ast_model):
	type_formatter_class = {
		DisplayType.STRUCT: StructFormatter,
		DisplayType.ENUM: EnumTypeFormatter,
		DisplayType.BYTE_ARRAY: PodTypeFormatter,
		DisplayType.INTEGER: PodTypeFormatter
	}[ast_model.display_type]

	return type_formatter_class(ast_model)
```
ここではast_modelがどのタイプなのか判別し、それに基づいてどのフォーマッターを使用するか決定します。フォーマッターの種類は３つです。

[StructTypeFormatter](https://github.com/symbol/symbol/blob/dev/sdk/javascript/generator/StructTypeFormatter.py) = 各種TransactionやMosaicなどのようにサイズ以外のフィールドを持つものに使用する
[EnumTypeFormatter](https://github.com/symbol/symbol/blob/dev/sdk/javascript/generator/EnumTypeFormatter.py) = Enum（列挙）型に使用する（例TransactionType)
[PodTypeFormatter](https://github.com/symbol/symbol/blob/dev/sdk/javascript/generator/PodTypeFormatter.py) = サイズ以外のフィールドを持たない物に使用する（例Amount）

この３つのフォーマッターは下層フォーマッターと自分の中では考えており、それに対して上層フォーマッターがTypeFormatterとFactoryFormatterと考えています。これはあくまでも概念的な話で理解しておくとはかどります。（他にフォーマッターと名のつく物にAbstructFromatterがありますが、これは下層フォーマッターの基底クラスでほとんど触らない）

TypeFormatterを見ていきます。

先程のGenerator.pyの1つ目のfor文では`output_file.write(str(generator))`のようにstrメソッドの引数にTypeFormatterを渡します。このstrはTypeFormatterの基底クラスであるClassFormatter（同一ファイル内）の

```py
def __str__(self):
		return self.generate_output()
```
で、`generate_output()`が
```py
def generate_output(self):
		output = self.generate_class()
		return output
```
つまり`generate_class()`ですね。
```py
def generate_class(self):
		output = self.generate_class_header()

		# additional newline between fields for js linter
		fields = self.provider.get_fields()
		fields_output = indent('\n'.join(fields))

		if fields_output:
			output += fields_output + '\n'

		methods = self.generate_methods()
		output += '\n'.join(map(indent, methods))

		output += '}\n'  # class_footer
		return output
```

ここでは最終的に戻り値でoutputを返すので、変数outputにどんどん追加していきます。

まずは、
```py
output = self.generate_class_header()
```
```py
def generate_class_header(self):
		base_class = self.provider.get_base_class()
		base_class = f' extends {base_class}' if base_class else ''
		header = f'class {self.provider.typename}{base_class} {{\n'
		comment = ''
		return header + indent(comment)
```
ここでとても重要なのがproviderとは下層フォーマッターだと思えばOKです。このproviderは今後ずっと出てくるので覚えておきましょう。

::: note
provider とはつまり下層フォーマッターのことである
:::

例えば、AmountだとするとフォーマッターはPodTypeFormatterなのでget_base_class()とは
```py
def get_base_class(self):
		return 'ByteArray' if self._is_array else 'BaseValue'
```
amountは_is_arrayがfalseなので'BaseValue'となりAmountの基底クラスはBaseValueとなります（BaseValueやByteArrayは言語に沿って要作成）

```py
header = f'class {self.provider.typename}{base_class} {{\n'
```
ここがクラスの宣言箇所になります。例えばjsでAmountなら
`class Amount extends BaseValue {`
がoutputに代入されます。

ではgenerate_class()に戻って
```py
fields = self.provider.get_fields()
```
先程と同じようにAmountを例にすると
```py
def get_fields(self):
		return [f'static SIZE = {self.pod.size};']
```
ここでのself.podとはast_modelのことで（初期化時にself.pod = ast_model）Amountであればサイズは8なので
`static SIZE = 8;`ですね。
generate_class()に戻ると
```py
fields_output = indent('\n'.join(fields))
```
です。PodTypeFormatterはシンプルでフィールドが1つしかないのでこれだけがoutputに追加代入されます。
ちなみにindentメソッドはよく使うので覚えておくと良いです。

さて、続いてgenerate_class()では
```py
methods = self.generate_methods()
```
なのでTypeFormatterのgenerate_methods()を見てみましょう。

```py
def generate_methods(self):
		methods = []

		ctor = self.generate_ctor()
		if ctor:
			methods.append(ctor)

		getters_setters = self.generate_getters_setters()
		methods.extend(getters_setters)

		size_method = self.generate_size()
		if size_method:
			methods.append(size_method)

		methods.append(self.generate_deserializer())
		methods.append(self.generate_serializer())

		representation = self.generate_representation()
		if representation:
			methods.append(representation)
		return methods
```
各メソッドを配列で生成です。順に解説しますが
ザクッと
- コンストラクタ
- ゲッター、セッター
- サイズ
- デシリアライズ
- シリアライズ
- リプレゼンテーション（おそらくtoString()のみでさほど重要ではなさそう）
があります。

引き続きAmountを例に解説しますが、AmountのproviderであるPodTypeFormatterは一番シンプルでStructTypeなどの解説は今後追加できればと思います。

```py
ctor = self.generate_ctor()
```
コンストラクタです。
```py
def generate_ctor(self):
		method_descriptor = self.provider.get_ctor_descriptor()
		if not method_descriptor:
			return None

		method_descriptor.method_name = 'constructor'
		return self.generate_method(method_descriptor)
```

```py
method_descriptor = self.provider.get_ctor_descriptor()
```
なのでPodTypeFormatterのget_ctor_descriptor()を見てみます。
```py
def get_ctor_descriptor(self):
		variable_name = self.printer.name
		body = f'super({self.typename}.SIZE, {variable_name});'
		if self._is_array:
			arguments = [f'{variable_name} = {self.printer.get_default_value()}']
		else:
			arguments = [f'{variable_name} = {self.printer.get_default_value()}']

        return MethodDescriptor(body=body, arguments=arguments)
```
```py
variable_name = self.printer.name
```
はい、初めてprinterなるものが登場しました。
printerというものは各フィールドに記述するコードなどを制御するためのもので`printers.py`というファイルがgenerator内にあります。これも結構触ります。（ちょっとうまく説明できない

ここでのprinter.nameについてはprinterの初期化時に以下のように言語ごとにキャメルケースやスネークケースなど活用箇所が違うので調整しています。
```py
self.name = fix_name(lang_field_name(name or underline_name(self.descriptor.name)))
```

```py
body = f'super({self.typename}.SIZE, {variable_name});'
```
javascriptでの基底クラスへの引き渡しですね。このあたり言語ごとに変わります。
amountであれば
`super(Amount.SIZE, amount);`となります。

```py
arguments = [f'{variable_name} = {self.printer.get_default_value()}']
```
ここはコンストラクタの引数です。amountのprinterはIntPrinterなのでget_default_value()は
```py
def get_default_value(self):
		return '0n' if 8 == self.descriptor.size else '0'
```
`amount = 0n`になります。

ここで重要なポイントは最後に
```py
return MethodDescriptor(body=body, arguments=arguments)
```
とMethodDescriptorの引数にbodyとargumentsを渡しているところです。
全てのメソッドはこのMethodDescriptorを介しているので覚えておくと良いです。とはいってもこのクラス自体にメソッドなどはなくフィールドがあるだけのものです。
https://github.com/symbol/symbol/blob/d39c00b72791094fbbb194d01cfd2b2afb042470/sdk/javascript/generator/AbstractTypeFormatter.py#L4

引数のbodyが後ほど関数内で処理されるもの、argumentsがその関数の引数です。

TypeFormatterのctorに戻ります。
```py
method_descriptor.method_name = 'constructor'
		return self.generate_method(method_descriptor)
```
method_nameが'constructor'でTypeFormatterのgenerate_methodの引数をmethod_descriptorとします。
ここはあまり詳しく解説しませんが、
```
method_descriptor.method_name　= 'constructor'
method_descriptor.arguments　= 'amount = 0n'
method_descriptor.body　= 'super(Amount.SIZE, amount);'
```
であることは覚えておいてください。
TypeFormatterのgenerate_methods()に戻って、
`methods.append(ctor)`とmethods配列に追加します。

この後、
self.generate_getters_setters()
self.generate_size()
self.generate_deserializer()
self.generate_serializer()
と、進みますが一旦ここまでにして先程のコンストラクタだけを出力すると
```
constructor(amount = 0n) {
	super(Amount.SIZE, amount);
}
```
となります。メソッド名、引数、Bodyの関係性が分かりましたか？
このあと多少複雑にはなっていきますが、やっていることは基本的にこれの繰り返しで、ひとつひとつそのメソッドがどこで何が行われているのかを理解しながら進めていきます。

ひとまず、今日のところはここまで。今後はAmountを例に他のメソッドの解説。また他のフォーマッターの解説も時間をかけて行いたいと思います。

# 一旦ここで終了
# ここから先はメモなので読まなくてよいです。いつか清書する用

### StructFormatter
https://github.com/symbol/symbol/blob/dev/sdk/javascript/generator/StructTypeFormatter.py

このあとすべてのフォーマッターを解説しますが、いずれのフォーマッターもやりたいことは

str関数では省略すると
generate_output
generate_class
generate_class_header
generate_methods
generate_ctor

みたいな感じでSchemaに定義されている内容次第で必要な関数を実行し出力している

さて、Generatorでやってることはなんとなく理解できたのでここでちょっとschemaを覗く、とりあえず冒頭だけを見てみる

parserによるとall.catsをインプットする
```all.cats
import "block.cats"
```

```block.cats
import "transaction.cats"
```

```transaction.cats
import "entity.cats"
```
```entitiy.cats
import "types.cats"
```
```types.cats
using Amount = uint64
using BlockDuration = uint64
using BlockFeeMultiplier = uint32
```
つまりこの3行が最初に読み込まれているので、ちょっとmodel.jsを見てみる
```javascript
/* eslint-disable max-len, object-property-newline, no-underscore-dangle, no-use-before-define */

const { BaseValue } = require('../BaseValue');
const { ByteArray } = require('../ByteArray');
const { BufferView } = require('../utils/BufferView');
const { Writer } = require('../utils/Writer');
const arrayHelpers = require('../utils/arrayHelpers');
const converter = require('../utils/converter');

class Amount extends BaseValue {
	static SIZE = 8;

	constructor(amount = 0n) {
		super(Amount.SIZE, amount);
	}

	static deserialize(payload) {
		const byteArray = payload;
		return new Amount(converter.bytesToInt(byteArray, 8, false));
	}

	serialize() {
		return converter.intToBytes(this.value, 8, false);
	}
}

class BlockDuration extends BaseValue {
	static SIZE = 8;

	constructor(blockDuration = 0n) {
		super(BlockDuration.SIZE, blockDuration);
	}

	static deserialize(payload) {
		const byteArray = payload;
		return new BlockDuration(converter.bytesToInt(byteArray, 8, false));
	}

	serialize() {
		return converter.intToBytes(this.value, 8, false);
	}
}

class BlockFeeMultiplier extends BaseValue {
	static SIZE = 4;

	constructor(blockFeeMultiplier = 0) {
		super(BlockFeeMultiplier.SIZE, blockFeeMultiplier);
	}

	static deserialize(payload) {
		const byteArray = payload;
		return new BlockFeeMultiplier(converter.bytesToInt(byteArray, 4, false));
	}

	serialize() {
		return converter.intToBytes(this.value, 4, false);
	}
}
```
冒頭の
`const { BaseValue } = require('../BaseValue');` はgenerate_filesで一番最初にやったあたり

次に
`using Amount = uint64` この一行が
```javascript
class Amount extends BaseValue {
	static SIZE = 8;

	constructor(amount = 0n) {
		super(Amount.SIZE, amount);
	}

	static deserialize(payload) {
		const byteArray = payload;
		return new Amount(converter.bytesToInt(byteArray, 8, false));
	}

	serialize() {
		return converter.intToBytes(this.value, 8, false);
	}
}
```
になる

サイズ、コンストラクター、デシリアライズ、シリアライズだけのシンプルなクラス。

おそらく`using Amount = uint64`だとto_type_formatter_instanceでPodTypeFormatterを返すのでproviderはPodTypeFormatterになる。
https://github.com/symbol/symbol/blob/dev/sdk/javascript/generator/PodTypeFormatter.py

TypeFormatterを見直し、順に解読するとこのへんを実行している
self.provider.get_fields()
self.provider.get_ctor_descriptor()
self.generate_deserializer()
self.generate_serializer()

つながった。
なので、このへんの関数の出力を言語ごとに調整していけば良いと思われる。

これはとてもシンプルな構造だからわかりやすいけどSchemaの構造を読み解けば他も解読できそう

## ※ここからマジでさらにメモ（いつか清書したい
適当にディレクトリ作ってターミナルで以下実行（Mac）
```
git clone https://github.com/symbol/symbol.git
```
僕の場合はC#のSDKを作りたいので `sdk/javascript`を複製して`sdk/csharp`とする
`symbol/sdk/csharp/scripts/run_catbuffer_generator.sh`と `symbol/sdk/csharp/generator/Generator.py`を少しだけ書き換える
```diff: run_catbuffer_generator.sh 15行目
- --output "${git_root}/sdk/javascript/src/$2" \
+ --output "${git_root}/sdk/csharp/src/$2" \
```
```diff: run_catbuffer_generator.sh 24行目
- rm -rf "./src/${name}/models.js"
+ rm -rf "./src/${name}/models.cs"
```
```diff: run_catbuffer_generator.sh 15行目
- diff "./src/${name}/models.js" "./src/${name}2/models.js"
- rm -rf "./src/${name}2/models.js"
+ diff "./src/${name}/models.cs" "./src/${name}2/models.cs"
+ rm -rf "./src/${name}2/models.cs"
```
```diff: Generator.py 42行目
- with open(output_directory / 'models.js', 'w', encoding='utf8', newline='\n') as output_file:
+ with open(output_directory / 'models.cs', 'w', encoding='utf8', newline='\n') as output_file:
```
要は出力先をCSに変えるのとファイルの拡張子をCSにした(dry_runとか今は意味分かってないけど変えておいて良いだろう)
んで、シェルを実行してModelsを生成するんだけどわかりやすいようにnemとsymbolの中のmodels.jsを削除しておくと良い

```sh
$ cd symbol/sdk/csharp
$ ./scripts/run_catbuffer_generator.sh
```

`csharp/sdk/src/nem/models.cs`
`csharp/sdk/src/symbol/models.cs`
が作成されました。

もちろん中身はJSなのでここからいじっていこう。

# 既存のgeneratorを複製する