Mercurial > hg > Papers > 2021 > anatofuz-master
annotate paper/chapter/04-interface.tex @ 91:4232c9dc1431
update
author | anatofuz <anatofuz@cr.ie.u-ryukyu.ac.jp> |
---|---|
date | Fri, 05 Feb 2021 19:09:08 +0900 |
parents | a93c2401753b |
children | d4b5c1b2925f |
rev | line source |
---|---|
51 | 1 \chapter{GearsOSのInterfaceの改良} |
84 | 2 GearsOSのモジュール化の仕組みであるInterfaceは、 GearsOSの中心的な機能である。 |
3 Interfaceの取り扱いには様々なメタ計算が含まれ、 このメタ計算はPerlスクリプトによって生成される。 | |
4 | |
5 InterfaceをGearsOSで使ったプログラミングをするにつれて、様々な不足している機能や、改善すべき点が見つかった。 | |
6 またPerlスクリプトがInterfaceを適切に取り扱う為のAPIも必要となることが分かった。 | |
7 本章では本研究で行ったGearsOSのInterfaceの改良について述べる。 | |
51 | 8 |
91 | 9 \section{GearsOSのInterfaceの構文の改良\label{sec:newInterface}} |
16 | 10 GearsOSのInterfaceでは、 従来はDataGearとCodeGearを分離して記述していた。 |
11 CodeGearの入出力をDataGearとして列挙する必要があった。 | |
12 CodeGearの入出力として\texttt{\_\_code()}の間に記述したDataGearの一覧と、Interface上部で記述したDataGearの集合が一致している必要がある。 | |
53
1a4d4e64f0b8
add Stack Interface code
anatofuz <anatofuz@cr.ie.u-ryukyu.ac.jp>
parents:
52
diff
changeset
|
13 ソースコード\ref{src:old-stack}はStackのInterfaceの例である。 |
1a4d4e64f0b8
add Stack Interface code
anatofuz <anatofuz@cr.ie.u-ryukyu.ac.jp>
parents:
52
diff
changeset
|
14 \lstinputlisting[label=src:old-stack, caption=従来のStack Interface]{src/old-stack.h} |
16 | 15 |
16 従来の分離している記法の場合、 このDataGearの宣言が一致していないケースが多々発生した。 | |
58 | 17 またInterfaceの入力としてのDataGearではなく、 フィールド変数としてDataGearを使うプログラミングスタイルを取るケースも見られた。 |
16 | 18 GearsOSでは、 DataGearやフィールド変数をオブジェクトに格納したい場合、 Interface側ではなくImpl側に変数を保存する必要がある。 |
19 Interface側に記述してしまう原因は複数考えられる。 | |
20 GearsOSのプログラミングスタイルに慣れていないことも考えられるが、構文によるところも考えられる。 | |
21 CodeGearとDataGearはInterfaceの場合は密接な関係性にあるが、 分離して記述してしまうと「DataGearの集合」と「CodeGearの集合」を別個で捉えてしまう。 | |
22 あくまでInterfaceで定義するCodeGearとDataGearはInterfaceのAPIである。 | |
23 これをユーザーに強く意識させる必要がある。 | |
24 | |
25 golangにもInterfaceの機能が実装されている。 | |
26 golangの場合はInterfaceは関数の宣言部分のみを記述するルールになっている。 | |
27 変数名は含まれていても含まなくても問題ない。 | |
28 | |
29 \begin{lstlisting}[frame=lrbt,label=src:golang_interface,caption={golangのinterface宣言}] | |
30 type geometry interface { | |
31 area() float64 | |
32 perim() float64 | |
33 } | |
34 \end{lstlisting} | |
35 | |
53
1a4d4e64f0b8
add Stack Interface code
anatofuz <anatofuz@cr.ie.u-ryukyu.ac.jp>
parents:
52
diff
changeset
|
36 GearsOSのInterfaceは入力と出力のAPIを定義するものであるので、 golangのInterfaceのように、関数のAPIを並べて記述するほうが簡潔であると考えた。 |
54 | 37 改良したInterfaceの構文でStackを定義したものをソースコード\ref{src:stack}に示す。 |
53
1a4d4e64f0b8
add Stack Interface code
anatofuz <anatofuz@cr.ie.u-ryukyu.ac.jp>
parents:
52
diff
changeset
|
38 \lstinputlisting[label=src:stack, caption=変更後のStack Interface]{src/stack.h} |
1a4d4e64f0b8
add Stack Interface code
anatofuz <anatofuz@cr.ie.u-ryukyu.ac.jp>
parents:
52
diff
changeset
|
39 |
54 | 40 |
58 | 41 従来のInterfaceでは\texttt{<Type, Impl>}キーワードが含まれていた。 |
54 | 42 これはジェネリクスの機能を意識して導入された構文である。 |
43 \texttt{Impl}キーワードは実装自身の型を示す型変換として使われていた。 | |
44 しかし基本Interfaceの定義を行う際にGearsOSのシステム上、CodeGearの第一引数は\texttt{Impl}型のポインタが来る。 | |
45 これはオブジェクト指向言語で言う\texttt{self}に相当するものであり、 自分自身のインスタンスを示すポインタである。 | |
46 Implキーワードは共通して使用されるために、 宣言部分からは取り外し、デフォルトの型キーワードとして定義した。 | |
47 \texttt{Type}キーワードは型変数としての利用を意識して導入されていたが、現在までのGearsOSの例題では導入されていなかった。 | |
48 ジェネリクスとしての型変数の利用の場合は\texttt{T}などの1文字変数がよく使われる。 | |
49 変更後の構文ではのちのジェネリクス導入のことを踏まえて、\texttt{Type}キーワードは削除した。 | |
50 | |
51 | |
56 | 52 |
53
1a4d4e64f0b8
add Stack Interface code
anatofuz <anatofuz@cr.ie.u-ryukyu.ac.jp>
parents:
52
diff
changeset
|
53 構文を変更するには、 GearsOSのビルドシステム上でInterfaceを利用している箇所を修正する必要がある。 |
1a4d4e64f0b8
add Stack Interface code
anatofuz <anatofuz@cr.ie.u-ryukyu.ac.jp>
parents:
52
diff
changeset
|
54 Interfaceはgenerate\_stub.plで読み込まれ、 CodeGearと入出力のDataGearの数え上げが行われる。 |
1a4d4e64f0b8
add Stack Interface code
anatofuz <anatofuz@cr.ie.u-ryukyu.ac.jp>
parents:
52
diff
changeset
|
55 この処理はInterfaceのパースに相当するものである。 |
84 | 56 パース対象のInterfaceの構文は、変更前の構文にしか対応していなかった。 |
57 後方互換性を維持したまま、新しい構文に対応させるために、generate\_stub.plが利用するInterfaceの解析ルーチンを両方の構文に対応させた。 | |
53
1a4d4e64f0b8
add Stack Interface code
anatofuz <anatofuz@cr.ie.u-ryukyu.ac.jp>
parents:
52
diff
changeset
|
58 |
1a4d4e64f0b8
add Stack Interface code
anatofuz <anatofuz@cr.ie.u-ryukyu.ac.jp>
parents:
52
diff
changeset
|
59 |
91 | 60 \section{Implementの型定義ファイルの導入}\label{sec:implType} |
56 | 61 Interfaceを使う言語では、 Interfaceが決まるとこれを実装するクラスや型が生まれる。 |
62 GearsOSもInterfaceに対応する実装が存在する。 | |
63 例えばStack Interfaceの実装はSingleLinkedStackであり、 Queueの実装はSingleLinkedQueueやSynchronizedQueueが存在する。 | |
64 | |
65 このSynchronizedQueueはGearsOSではDataGearとして扱われる。 | |
58 | 66 Interfaceの定義と同等な型定義ファイルが、 実装の型については存在しなかった。 |
56 | 67 従来はcontext.hのDataGearの宣言部分に、構造体の形式で表現したものを手で記述していた。(ソースコード\ref{src:singleContext.h}) |
68 \lstinputlisting[label=src:singleContext.h, caption=cotnext.hに直接書かれた型定義]{src/singleContext.h} | |
69 | |
58 | 70 CbCファイルからはcontext.hをインクルードすることで問題なく型の使用は可能である。 |
91 | 71 Perlのトランスパイラであるgenerate\_stub.plはInterfaceの型定義ファイルをパースしていた。 |
58 | 72 しかし型定義ファイルの存在の有無がInterfaceと実装で異なっている為に、 generate\_stub.plでImplementの型に関する操作ができない。 |
56 | 73 Implementの型も同様に定義ファイルを作製すれば、generate\_stub.plで型定義を用いた様々な処理が可能となり、ビルドシステムが柔軟な挙動が可能となる。 |
74 また型定義は一貫して\texttt{*.h}に記述すれば良くなるため、 プログラマの見通しも良くなる。 | |
75 本研究では新たにImplementの型定義ファイルを考案する。 | |
76 | |
77 GearsOSではすでにInterfaceの型定義ファイルを持っている。 | |
78 Implementの型定義ファイルも、 Interfaceの型定義ファイルと似たシンタックスにしたい。 | |
79 Implementの型定義ファイルで持たなければいけないのは、 どのInterfaceを実装しているかの情報である。 | |
80 この情報は他言語ではInterfaceの実装を持つ型の宣言時に記述するケースと、型名の記述はせずに言語システムが実装しているかどうかを確認するケースが存在する。 | |
81 Javaでは\texttt{implements}キーワードを用いてどのInterfaceを実装しているかを記述する。\cite{javaimpl} | |
82 ソースコード\ref{src:javaimpl}では、\texttt{Pig}クラスは\texttt{Animal} Interfaceを実装している。 | |
83 \lstinputlisting[label=src:javaimpl, caption=JavaのImplementキーワード]{src/java-interface-implements.java} | |
84 golangではInterfaceの実装は特にキーワードを指定せずに、 そのInterfaceで定義しているメソッドを、Implementに相当する構造体がすべて実装しているかどうかでチェックされる。 | |
85 これはgolangはクラスを持たず、構造体を使ってInterfaceの実装を行う為に、 構造体の定義にどのInterfaceの実装であるかの情報をシンタックス上書けない為である。 | |
86 GearsOSでは型定義ファイルを持つことができるために、 golangのような実行時チェックは行わず、 Javaに近い形で表現したい。 | |
87 | |
88 導入した型定義でSynchronizedQueueを定義したものをソースコード\ref{src:syncqueue}に示す。 | |
89 大まかな定義方法はInterface定義のものと同様である。 | |
90 違いとして\texttt{impl}キーワードを導入した。 | |
91 これはJavaの\texttt{implements}に相当する機能であり、 実装したInterfaceの名前を記述する。 | |
92 現状のGearsOSではImplが持てるInterfaceは1つのみであるため、\texttt{impl}の後ろにはただ1つの型が書かれる。 | |
93 型定義の中では独自に定義したCodeGearを書いてもいい。 | |
94 これはJavaのプライベートメソッドに相当するものである。 | |
95 特にプライベートメソッドがない場合は、 実装側で所持したい変数定義を記述する。 | |
96 SynchronizedQueueの例では\texttt{top}などが実装側で所持している変数である。 | |
97 \lstinputlisting[label=src:syncqueue, caption=SynchronizedQueueの定義ファイル]{src/SynchronizedQueue.h} | |
84 | 98 従来context.hに直接記述していたすべてのDataGearの定義は、 スクリプトで機械的にInterfaceおよびImplementの型定義ファイルに変換を行った。 |
99 | |
100 context.hからInterfaceおよびImplementの型定義をファイルに分割することができた。 | |
101 しかしGearsOSのContextはすべてのDataGearの型定義を持つ必要がある。 | |
102 この為、context.hには分割した型定義ファイルをもとに、CbCのメタレベルに変換された型情報を書き込む必要がある。 | |
103 この処理はgenerate\_context.pl内でビルド時に行うようにした。 | |
56 | 104 |
37 | 105 \section{Implementの型をいれたことによる間違ったGearsプログラミング} |
106 Implementの型を導入したが、 GearsOSのプログラミングをするにつれていくつかの間違ったパターンがあることがわかった。 | |
107 自動生成されるStubCodeGearは、 goto metaから遷移するのが前提であるため、 引数をContextから取り出す必要がある。 | |
108 Contextから取り出す場合は、 実装しているInterfaceに対応している置き場所からデータを取り出す。 | |
109 この置き場所は\texttt{data}配列であり、 配列の添え字は\texttt{enum Data}と対応している。 | |
110 また各CodeGearからgotoする際に、 遷移先のInterfaceに値を書き込みに行く。 | |
111 | |
112 | |
113 Interfaceで定義したCodeGearと対応しているImplementのCodeGearの場合はこのデータの取り出し方で問題はない。 | |
114 しかしImplementのCodeGearから内部でgotoするCodeGearの場合は事情が異なる。 | |
58 | 115 内部でgotoするCodeGearは、 Javaなどのプライベートメソッドとして使用できる。 |
37 | 116 このCodeGearのことをprivate CodeGearと呼ぶ。 |
117 privateCodeGearにgotoする場合、 goto元のCodeGearからは\texttt{goto meta}経由で遷移する。 | |
58 | 118 goto metaが発行されるとStub Code Gearに遷移するが、現在のシステムではInterfaceから値を取得しに行く。 |
66 | 119 private CodeGearの入力もStubから取得したいと考え、 ImplementをInterfaceのつもりでGearsOSのコードを記述した。 |
120 | |
44 | 121 |
91 | 122 \section{Interfaceのパーサーの構築}\label{sec:interfaceParser} |
123 従来のGearsOSのトランスパイラでは、 generate\_stub.plがInterfaceファイルを開き、情報を解析していた。 | |
78 | 124 この情報解析はgetDataGear関数で行われていた。 |
125 しかしこの関数は、CbCファイルのCodeGear、DataGearの解析で使用するルーチンと同じものである。 | |
84 | 126 この為Interface特有のパースが出来ていなかった。 |
78 | 127 |
84 | 128 また、開いたヘッダファイルがInterfaceのファイルでも、そうでないCのヘッダファイルでも同様の解析をしてしまう。 |
78 | 129 Interfaceの定義ファイルの構文はすでに統一されたものを使用している。 |
84 | 130 Interfaceの定義の構文で実装されていないInterfaceファイルを読み込んだ場合は、 エラーとして処理したい。 |
78 | 131 また、Interfaceが満たすべきCodeGearの種類やInputDataGearの数の管理も行いたい。 |
132 さらにInterfaceではなく、Implementの定義ファイルも同様にパースし、情報を解析したい。 | |
133 | |
84 | 134 これらを実現するには、今までgenerate\_stub.plで使っていた情報解析ルーチンをもとに、最初からInterfaceに特化したパーサーが必要となる。 |
135 本研究ではGears::InterfaceモジュールとしてInterfaceのパーサーを実装した。 | |
78 | 136 |
137 \subsection{Gears::Interfaceの構成} | |
84 | 138 Gears::InterfaceはPerlのモジュールであるが、 実際はパーサー用のAPIを提供しているサブルーチンのまとまりである。 |
139 その為オブジェクトを作らずに直接メソッドを呼び出して利用する。 | |
85 | 140 Gears::Interfaceは2種類のAPIを提供している。 |
84 | 141 |
86 | 142 \subsection{パースAPI} |
85 | 143 1つは\texttt{parse}メソッドである。これはパースしたいファイル名を与えると、Interfaceであった場合にヘッダファイルをパースして情報を返すAPIである。 |
144 parseAPIでStack Interfaceをパースした結果の値をソースコード\ref{src:parsedStack1}に示す。 | |
145 これはPerlの連想配列のリファレンスで表現されている。 | |
146 \lstinputlisting[label=src:parsedStack1, caption=parseAPIでパースしたStack Interface]{src/parsedStack1.pl} | |
147 contentが持つ要素は配列であり、 これはInterfaceをCbCの構造体に変換した際の内容である。 | |
148 file\_nameにはパースしたファイルのパスが入る。 | |
149 inner\_code\_gearsは、 Interfaceが継続として受け取るCodeGearの集合が入っている。 | |
150 StackではnextとwhenEmptyは入力で受け取るため、 inner\_code\_gearsに格納されている。 | |
151 nameはファイルパスではなく、 Interfaceの名前が格納されている。 | |
152 | |
153 ImplファイルであるSingleLinkedStack.hをパースした結果をソースコード\ref{src:parsedSingleStack}に示す。 | |
154 \lstinputlisting[label=src:parsedSingleStack, caption=parseAPIでパースしたSingleLinkedStack]{src/parsedSingleLinkedStack.pl} | |
155 ほとんど返す値はInterfaceの時のものと同様であるが、 Implの場合はisaキーに、実装しているInterfaceの名前が格納される。 | |
156 | |
157 この情報はパース対象がInterface、もしくはImplementでなければ返さない。 | |
84 | 158 パーサーはInterfaceであるかどうかを、構文の正規表現にマッチするかどうかで確認をする。(ソースコード\ref{src:IsInterface}) |
159 \lstinputlisting[label=src:IsInterface, caption=Interfaceであるかどうかの確認]{src/IsInterface.pm} | |
160 | |
86 | 161 \subsection{詳細なパースAPI} |
162 parse APIはシンプルな結果を返していたが、 Interfaceに定義しているCodeGearの引数など、詳細な情報を取得したいケースがある。 | |
163 Gears::Interfaceに、詳細なパース用のAPIである\texttt{detailed\_parse} APIを用意した。 | |
164 先ほどのStack Interfaceをパースした結果をソースコード\ref{src:parsedStackDetail}に示す。 | |
165 新たな情報としてcodeNameが連想配列の要素に追加されている。(ソースコード2行目) | |
166 codeNameはCodeGearの名前がキーになっており、valueとして引数の文字列情報が\texttt{args}に、、 Interfaceの呼び出し時に必要な引数の個数が\texttt{argc}に設定される。 | |
167 これらの情報は配列codesからもアクセス可能となっている。(ソースコード48行目) | |
168 Interfaceが持つDataGearの一覧は、配列dataに格納される。(ソースコード62行目) | |
169 | |
170 OutputDataGearがあるCodeGearの一覧が、 hasOutputArgsに格納される。(ソースコード68行目) | |
171 codeNameと同様に、CodeGearの名前がキーとなっている。 | |
172 対応する値は、 出力する変数の名前と、その型の組のリストになっている。 | |
173 | |
87 | 174 この詳細なパースの結果は、以下に例を示す用途で使われる。 |
175 \begin{itemize} | |
176 \item implementのCodeGearの名前の保管 | |
177 \item InterfaceのCodeGearの定義と実装の対応の確認 | |
178 \item OutputDataGearがあるAPI呼び出しであるかの確認 | |
179 \item API呼び出し時の引数のチェック | |
180 \end{itemize} | |
181 | |
86 | 182 \lstinputlisting[label=src:parsedStackDetail, caption=Stack Interfaceの詳細なパース]{src/StackParseDetail.pl} |
183 | |
184 | |
185 | |
84 | 186 \subsection{Interfaceパーサーの呼び出し} |
87 | 187 定義したパーサーは都度呼ぶこともできるが、ヘッダファイルのパスを入力で与える必要がある。 |
88 | 188 generate\_stub.plは実行時のコマンドライン引数としてヘッダファイルは与えられないので、スクリプト中で探索する必要がある。 |
189 毎回パースしたいInterface名の探索をするのは煩雑である。 | |
190 | |
191 基本的にgenerate\_stub.plではInterfaceの名前がすでに判明しており、そのInterfaceのパースした結果を取得したいのがほとんどである。 | |
192 ここからスクリプト内部で、 Interfaceの名前とパースした結果を対応させる連想配列を実装した。 | |
193 generate\_stub.plでは、スクリプト起動時に連想配列を変換処理を行う前に作製する。 | |
194 ソースコード\ref{src:createHeaderName2Info}のサブルーチンが連想配列を作り出す処理である。 | |
87 | 195 このサブルーチンでは、ヘッダファイルを起動時に全探査し、 すべてパースを行う。 |
196 4行目でGears::UtilのAPI呼び出しをしているが、 このAPIはGearsOSで使うヘッダファイルを、指定されたパスから再帰的に探索するものである。 | |
197 | |
198 なお同名のヘッダファイルが見つかった場合は、 変換をしているCbCファイルと同じディレクトリにあるヘッダファイルが優先される。(ソースコード 13行目) | |
84 | 199 \lstinputlisting[label=src:createHeaderName2Info, caption=ヘッダファイルの名前とInterfaceのパース結果の対応リストの作製]{src/createHeaderName2Info.pl} |
78 | 200 |
68 | 201 |
91 | 202 \section{Interfaceの実装のCbCファイルへの構文の導入} \label{sec:newInterfaceInCbC} |
82 | 203 今までのGearsOSではマクロに似た\texttt{\#interface}構文で使用するInterfce名を指定した。 |
204 しかしInterfaceを実装する場合も、 InterfaceのAPIを利用する際も同じシンタックスであった。 | |
205 この2つは意味が異なっている為、 シンタックスを分離したい。 | |
206 Implementの型定義ファイルを導入したので、Interfaceの実装をする場合に別のシンタックスを導入する。 | |
207 | |
208 導入された構文をソースコード\ref{src:implHeader}に示す。 | |
209 この例ではStack Interfaceの実装としてSingleLinkedStackを定義する宣言である。 | |
210 | |
211 Implementの宣言の構文では、 まず\texttt{\#impl}の後ろに実装したいIntefaceの名前を入れる。 | |
90 | 212 続く\texttt{as}キーワードの後ろに、 Implementの型名を記述する。 |
82 | 213 宣言はgenerate\_stub.plが読み取り、 変換した後のCbCファイルからは該当する行が削除される。 |
214 \lstinputlisting[label=src:implHeader, caption=Intefaceの実装をする際の宣言]{src/implHeader.h} | |
37 | 215 |
88 | 216 |
91 | 217 \section{Interface APIに対応したCodeGearの名前の自動変換} \label{sec:autoCodeGearName} |
89 | 218 InterfaceのAPIに対応したCodeGearを実装する際、今までは暗黙にInterface名とImplの型名をつなげた名前で定義していた。 |
219 例えばStack InterfaceのAPIであるpopをSingleLinkedStackが実装した場合、CodeGearの名前はpopSingleLinkedStackにしていた。 | |
220 | |
221 CodeGearの名前につけられるImplの名前は、GearsOSのシステムがCodeGearの識別に使うメタな情報と言える。 | |
222 ユーザーレベルではInterfaceのAPIと同じ名前のCodeGearを実装できると、GearsOSのメタなCodeGearの処理と分離可能である。 | |
223 この為Perlスクリプトで、InterfaceのImplementの場合はCodeGearの名前を自動で変換する機能を実装した。 | |
224 | |
225 generate\_stub.plはソースコードの情報を読み取るフェーズと、変換した情報を書き込むフェーズに別けられる。 | |
226 まずは読み取りの際の処理をソースコード\ref{src:replaceCodeGearName1}に示す。 | |
227 generate\_stub.plは、CodeGearの宣言時に、自分が今変換しているInterfaceのImplのCbCファイルかどうかをまず確認する。(ソースコード6行目) | |
228 ImplのCbCファイルであった場合、変数\texttt{\$implInterfaceInfo}に具体的な値が入っているため、if文に進む。 | |
229 if文の中ではInterfaceのパースの結果と、今定義しているCodeGearの名前を比較する。 | |
230 ここでInterfaceのAPIであるCodeGearの名前と、今CbCファイルで定義しているCodeGearの名前が等しい場合、後ろに型名をつけたCodeGearの名前に変換し、スクリプト内で変換したことを記憶する。 | |
231 \lstinputlisting[label=src:replaceCodeGearName1, caption=CodeGearの名前が等しいかどうかの確認]{src/replaceCodeGearName1.pl} | |
232 | |
233 CodeGearの宣言は、 Contextを引数に含めるようにすでに書き換えるルーチンで処理されていた。 | |
234 この部分でCodeGearの名前を変更したい。 | |
235 実際に書き出している処理の部分をソースコード\ref{src:replaceCodeGearName2}に示す。 | |
236 この際に書き込むCodeGearの名前は、定義のCodeGear名を正規表現でキャプチャし、変数\texttt{\$currentCodeGearName}に代入している。(5行目) | |
237 読み込み時に作製した、名前の変更があることを保存する連想配列\texttt{\$replaceCodeGearNames}に、今書き込もうとしているCodeGearの名前を問い合わせる。 | |
238 連想配列側にCodeGearの名前に対応する値があった場合は書き換え対象なので、 \texttt{\$currentCodeGearName}を、ソースコード\ref{src:replaceCodeGearName1}で作製したCodeGearの名前に変換する。 | |
239 | |
240 \lstinputlisting[label=src:replaceCodeGearName2, caption=CodeGearの名前の変更]{src/replaceCodeGearName2.pl} | |
88 | 241 |
58 | 242 \section{GearsCbCのInterfaceの実装時の問題} |
39 | 243 |
58 | 244 Interfaceとそれを実装するImplの型が決定すると、最低限満たすべきCodeGearのAPIは一意に決定する。 |
245 ここで満たすべきCodeGearは、Interfaceで定義したCodeGearと、 Impl側で定義した privateなCodeGearとなる。 | |
246 例えばStack Interfaceの実装を考えると、各Implで\texttt{pop}, push, shift, isEmptyなどを実装する必要がある。 | |
39 | 247 |
58 | 248 従来はプログラマが手作業でヘッダーファイルの定義を参照しながら\texttt{.cbc}ファイルを作成していた。 |
249 手作業での実装のため、 コンパイル時に下記の問題点が多発した。 | |
45 | 250 |
251 \begin{itemize} | |
58 | 252 \item CodeGearの入力のフォーマットの不一致 |
253 \item Interfaceの実装のCodeGearの命名規則の不一致 | |
254 \item 実装を忘れているCodeGearの発生 | |
45 | 255 \end{itemize} |
256 | |
48 | 257 |
58 | 258 特にGearsOSの場合はPerlスクリプトによって純粋なCbCに一度変換されてからコンパイルが行われる。 |
91 | 259 実装の状況とトランスパイラの組み合わせによっては、 CbCコンパイラレベルでコンパイルエラーを発生させないケースがある。 |
58 | 260 この場合は実際に動作させながら、gdb, lldbなどのCデバッガを用いてデバッグをする必要がある。 |
261 またCbCコンパイラレベルで検知できても、すでに変換されたコード側でエラーが出る。 | |
91 | 262 このため、 トランスパイラの挙動をトレースしながらデバッグをする必要がある。 |
58 | 263 Interfaceの実装が不十分であることのエラーは、 GearsOSレベル、最低でもCbCコンパイラのレベルで完全に検知したい。 |
48 | 264 |
58 | 265 \section{Interfaceを満たすコード生成の他言語の対応状況} |
48 | 266 |
58 | 267 Interfaceを機能として所持している言語の場合、Interfaceを完全に見たいしているかどうかはコンパイルレベルか実行時レベルで検知される。 |
268 例えばJavaの場合はInterfaceを満たしていない場合はコンパイルエラーになる。 | |
269 | |
270 | |
271 InterfaceのAPIを完全に実装するのを促す仕組みとして、Interfaceの定義からエディタやツールが満たすべき関数と引数の組を自動生成するツールがある。 | |
45 | 272 |
58 | 273 Javaでは様々な手法でこのツールを実装している。 |
274 Microsoftが提唱しているIDEとプログラミング言語のコンパイラをつなぐプロトコルにLanguage Serverがある。 | |
275 Language Serverはコーディング中のソースコードをコンパイラ自身でパースし、 型推論やエラーの内容などをIDE側に通知するプロトコルである。 | |
276 主要なJavaのLanguage Serverの実装であるeclipse.jdt.ls\cite{eclipse.jdt.ls}では、 LanguageServerの機能として未実装のメソッドを検知する機能が実装されている。\cite{eclipse_pull322} | |
277 この機能を応用してvscode上から未実装のメソッドを特定し、 雛形を生成する機能がある。 | |
278 他にもIntelliJ IDEなどの商用IDEでは、 IDEが独自に未実装のメソッドを検知、雛形を生成する機能を実装している。 | |
279 | |
280 | |
281 golangの場合は主に\texttt{josharian/impl}\cite{golang_impl}が使われている。 | |
282 これはインストールすると\texttt{impl}コマンドが使用可能になり、 実装したいInterfaceの型と、 Interfaceを実装するImplの型(レシーバ)を与えることで雛形が生成される。 | |
283 主要なエディタであるvscodeのgolangの公式パッケージである\texttt{vscode-go}\cite{vscode-go}でも導入されており、 vscodeから呼び出すことが可能である。 | |
284 vscode以外にもvimなどのエディタからの呼び出しや、 シェル上で呼び出して標準出力の結果を利用することが可能である。 | |
46 | 285 |
58 | 286 \section{GearsOSでのInterfaceを満たすCbCの雛形生成} |
287 GearsOSでも同様のInterfaceの定義から実装するCodeGearの雛形を生成したい。 | |
288 LanguageServerの導入も考えられるが、 今回の場合はC言語のLanguageServerをCbC用にまず改良し、 さらにGearsOS用に書き換える必要がある。 | |
289 現状のGearsOSが持つシンタックスはCbCのシンタックスを拡張しているものではあるが、これはCbCコンパイラ側には組み込まれていない。 | |
290 LanguageServerをGearsOSに対応する場合、 CbCコンパイラ側にGearsOSの拡張シンタックスを導入する必要がある。 | |
291 CbCコンパイラ側への機能の実装は、 比較的難易度が高いと考えらる。 | |
292 CbCコンパイラ側に手をつけず、 Interfaceの入出力の検査は既存のGearsOSのビルドシステム上に組み込みたい。 | |
293 | |
294 対してgolangの\texttt{impl}コマンドのように、 シェルから呼び出し標準出力に結果を書き込む形式も考えられる。 | |
295 この場合は実装が比較的容易かつ、 コマンドを呼び出して標準出力の結果を使えるシェルやエディタなどの各プラットフォームで使用可能となる。 | |
296 先行事例を参考に、コマンドを実行して雛形ファイルを生成するコマンド\texttt{impl2cbc.pl}をGearsOSに導入した。 | |
297 \texttt{impl2cbc.pl}の処理の概要を図\ref{fig:impl2cbc}に示す。 | |
46 | 298 |
88 | 299 impl2cbc.plでは、 実行時引数にImplementの型定義ファイルを与える。 |
300 impl2cbc.plの内部でImplementのパース結果からInterfaceを特定し、 雛形を生成する。 | |
301 Interfaceの定義ファイルが複数見つかった場合、Implementの型定義ファイルがあるディレクトリと同じファイルが優先される。 | |
302 コマンドラインからの呼び出しと、生成した結果をソースコードに示す。 | |
303 | |
304 | |
46 | 305 |
58 | 306 \begin{figure}[hp] |
307 \begin{center} | |
308 \includegraphics[width=130mm]{drawio/impl2cbc.pdf} | |
309 \end{center} | |
310 \caption{impl2cbcの処理の流れ} | |
311 \label{fig:impl2cbc} | |
312 \end{figure} | |
48 | 313 |
88 | 314 \lstinputlisting[label=src:callimpl2cbc, caption=impl2cbcの実行方法]{src/callimpl2cbc.sh} |
315 \lstinputlisting[label=src:impl2cbcExecuted, caption=生成された雛形ファイル]{src/gneratedPhils.cbc} | |
58 | 316 |
88 | 317 \subsection{雛形生成の手法} |
58 | 318 Interfaceでは入力の引数がImplと揃っている必要があるが、 第一引数は実装自身のインスタンスがくる制約となっている。 |
319 実装自身の型は、Interface定義時には不定である。 | |
320 その為、 GearsOSではInterfaceのAPIの宣言時にデフォルト型変数\texttt{Impl}を実装の型として利用する。 | |
321 デフォルト型\texttt{Impl}を各実装の型に置換することで自動生成が可能となる。 | |
52 | 322 |
323 | |
58 | 324 実装すべきCodeGearはInterfaceとImpl側の型を見れば定義されている。 |
325 \texttt{\_\_code}で宣言されているものを逐次生成すればよいが、 継続として呼び出されるCodeGearは具体的な実装を持たない。 | |
326 GearsOSで使われているInterfaceには概ね次の継続である\texttt{next}が登録されている。 | |
327 \texttt{next}そのものはInterfaceを呼び出す際に、入力として与える。 | |
328 その為各Interfaceに入力として与えられた\texttt{next}を保存する場所は存在するが、 nextそのものの独自実装は各Interfaceは所持しない。 | |
329 したがってこれをInterfaceの実装側で明示的に実装することはできない。 | |
330 雛形生成の際に、入力として与えられるCodeGearを生成してしまうと、プログラマに混乱をもたらしてしまう。 | |
331 | |
332 入力として与えられているCodeGearは、Interfaceに定義されているCodeGearの引数として表現されている。 | |
333 コードに示す例では、\texttt{whenEmpty}は入力して与えられているCodeGearである。 | |
334 雛形を生成する場合は、入力として与えられたCodeGearを除外して出力を行う。 | |
335 順序はInterfaceをまず出力した後に、 Impl側を出力する。 | |
336 | |
337 | |
338 \subsection{コンストラクタの自動生成} | |
339 雛形生成では他にコンストラクタの生成も行う。 | |
340 GearsOSのInterfaceのコンストラクタは、 メモリの確保及び各変数の初期化を行う。 | |
341 メモリ上に確保するのは主にInterfaceとImplのそれぞれが基本となっている。 | |
342 Interfaceによっては別のDataGearを内包しているものがある。 | |
343 その場合は別のDataGearの初期化もコンストラクタ内で行う必要があるが、 自動生成コマンドではそこまでの解析は行わない。 | |
344 | |
345 | |
346 コンストラクタのメンバ変数はデフォルトでは変数は0、ポインタの場合はNULLで初期化するように生成する。 | |
347 このスクリプトで生成されたコンストラクタを使う場合、 CbCファイルから該当する部分を削除すると、\texttt{generate\_stub.pl}内でも自動的に生成される。 | |
348 自動生成機能を作成すると1CbCファイルあたりの記述量が減る利点がある。 | |
82 | 349 generate\_stub.pl内で作製する場合は、 すでにメタ情報を含むコードに書き換えたものを作製する。 |
350 その為厳密には同じコードを生成する訳ではない。 | |
58 | 351 |
352 | |
353 明示的にコンストラクタが書かれていた場合は、 Perlスクリプト内での自動生成は実行しないように実装した。 | |
354 これはオブジェクト指向言語のオーバーライドに相当する機能と言える。 | |
355 現状のGearsOSで使われているコンストラクタは、 基本は\texttt{struct Context*}型の変数のみを引数で要求している。 | |
356 しかしオブジェクトを識別するためにIDを実装側に埋め込みたい場合など、 コンストラクタ経由で値を代入したいケースが存在する。 | |
357 この場合はコンストラクタの引数を増やす必要や、 受け取った値をインスタンスのメンバに書き込む必要がある。 | |
358 具体的にどの値を書き込めば良いのかまではPerlスクリプトでは判定することができない。 | |
359 このような細かな調整をする場合は、 generate\_stub.pl側での自動生成はせずに、 雛形生成されたコンストラクタを変更すれば良い。 | |
360 あくまで雛形生成スクリプトはプログラマ支援であるため、 いくつかの手動での実装は許容している。 | |
59 | 361 |
362 | |
363 \section{Interfaceの引数の検知} | |
79 | 364 GearsOSのノーマルレベルでは、 InterfaceのAPIの呼び出しは\texttt{interface->method(arg)}の呼び出し方であった。 |
365 \texttt{arg}は引数であり、これはInterfaceで定義したAPIの引数の一致している必要がある。 | |
366 Interfaceの定義の引数は、Implの実装自身が第一引数でくる制約があった。 | |
367 この制約の為に、 厳密にはInterfaceの定義ファイルに書かれているCodeGearの引数と、Interfaceの呼び出しの引数は数が揃ってはいない。 | |
368 generate\_stub.plは第一引数が実装自身の型であるので、union Data型にキャストし、Contextの引数保存場所に書き込むようになっている。 | |
369 問題が第1引数以外の引数が揃っていない場合である。 | |
370 generate\_stub.plを通すと、次の継続はgoto metaに変換されてしまい、引数情報が抜けてしまう。 | |
371 その為引数はすべて適切にcontextに書き込まれている必要があるが、 一部引数が足りず書き込みが出来なかったケースでも、 CbCコンパイラレベルでは引数関係のエラーが発生しない。 | |
372 また上手くInterfaceの入力の数を取得できなかった場合も、generate\_stub.plは止まらずにマクロを生成してしまう。 | |
373 Gearefを通してcontextに書き込む右辺値が抜けているコードなどがよく発生した。 | |
374 この場合は原因を.cファイルと.cbcファイル、Interfaceファイル、contextファイルのすべてを確認しなければならず、デバッグが非常に困難だった。 | |
81 | 375 InterfaceのAPI呼び出し時の引数検知は、 Interfaceの型定義ファイルからCodeGearの入力の数の取得が不十分であるのが主な原因であった。 |
79 | 376 |
377 | |
81 | 378 この問題はPerlスクリプトレベルで引数のチェックを十分に行う必要がある。 |
379 すでにInterfaceのパーサーは実装している為、 パーサー経由で呼び出しているAPIを持つInterfaceの情報を取得する。 | |
380 パースした結果の情報に、 各CodeGearの引数情報と引数の数を取得できれば、それらとAPI呼び出し時に与えられている引数を比較すればチェックが可能である。 | |
381 現状は引数の数が揃っているかどうかで確認をしている。 | |
382 | |
383 Intefaceの引数を確認し、Gearefマクロを生成しているgenerate\_stub.plの箇所に、引数の確認処理を実装した。(ソースコード\ref{src:parsedArgs}) | |
384 ここでAPI呼び出し時の引数は、\texttt{\$tmpArgs}に代入されている。 | |
385 CbCの関数呼び出しの引数はカンマで区切るので、2行目でカンマで文字列を分割し、引数を配列\texttt{@args}に変換している。 | |
386 | |
387 generate\_stub.plはローカル変数のすべての型を記録しているので、6行目でAPI呼び出しをしているインスタンスの名前からInterfaceを特定する。 | |
388 特定後、ヘッダファイルの場所を取得し、8行目でInterfaceのパーサーを呼び出している。 | |
389 パーサーから取得した情報から、メソッドの引数の数を14行目で取得し、 引数が格納されている配列\texttt{@args}の要素数と比較している。 | |
79 | 390 \lstinputlisting[label=src:parsedArgs, caption=Perlレベルでの引数チェック]{src/parsedArgs.pl} |
391 | |
81 | 392 Perlスクリプトでエラーを検知すると、 エラーで終了する。 |
393 ソースコード\ref{src:StackTestArg}のInterfaceの\texttt{insertTest1}を呼び出す例題でエラーを発生さる。 | |
394 \lstinputlisting[label=src:StackTestArg, caption=StackTestInterfaceの定義]{src/StackTestArg.h} | |
395 ソースコード\ref{src:StackTestArgCbC}でAPIを呼び出しているが、 この呼び出し方法では\texttt{stack}が引数にない。 | |
396 \lstinputlisting[label=src:StackTestArgCbC, caption=StackTestInterfaceのAPI呼び出し(引数不足)]{src/StackTestArg.cbc} | |
397 GearsOSのビルドを行うと、ソースコード\ref{src:argErr}のエラーが発生し、以降のビルドが停止する。 | |
398 Cmakeはエラーを検知するとビルドを止めるようにMakefileを作製するため、 GearsOSの拡張構文のレベルで停止ができる。 | |
399 \lstinputlisting[label=src:argErr, caption=InterfaceのAPI呼び出し時の引数エラー]{src/argError.txt} | |
400 | |
79 | 401 generate\_stub.pl側で、出てきたローカル変数と型の組はすべて保存している。 |
402 Interface側のCodeGearの定義にも当然引数の型と名前は書かれている。 | |
403 このローカル変数の型と、CodeGearの定義の引数の型が、完全に一致しているかどうかのチェックを行うと、さらに強固な引数チェックが可能となる。 | |
404 ただし引数で渡す際に、例えばint型の値の加算処理などを行っていると、その処理の結果がint型になっているかどうかをPerlレベルでチェックする必要が出てしまう。 | |
59 | 405 |
81 | 406 |
72 | 407 \section{InterfaceのAPIの未実装の検知} |
82 | 408 InterfaceAPI呼び出し時に引数の数以外に、そもそも実装していないAPIを呼び出してしまうことがある。 |
409 この場合はCbCがPerlスクリプトによって変換された後でエラーが出る。 | |
410 内容はCbCコンパイラのコンパイル時にInterfaceの構造体に、APIに対応するフィールドがないエラーである。 | |
411 コンパイル時に発覚できるので問題ないが、 これも変換する前に発見したほうがデバッグが容易である。 | |
412 | |
413 API呼び出し時の処理は、ソースコード\ref{src:parsedArgs}の処理そのものであるため、この処理の中に未実装のAPIを検知する様にした。 | |
414 呼び出し元のInterfaceの情報パースした結果、ヘッダファイルにAPIの定義がなかった場合は11行目の\texttt{unless}に処理が落ち、 エラー終了する。 | |
415 これによってInterface呼び出しの問題が、 Perlスクリプトによって変換する前に検知可能になった。 | |
72 | 416 |
88 | 417 \section{par goto のInterface経由の呼び出しの対応} |
90 | 418 従来のpar gotoではInterface経由の呼び出しは想定しておらず、 |