LINT系関数の仕様と制約¶
LINT.* 系関数は、読み込み済み辞書の解析結果(関数内のステートメントと数式の項)から、関数・変数の使用箇所を取り出すデバッグ用の関数群です。yayalint のような静的検査を YAYA 辞書自身で組むための材料になりますが、以下の仕様と制約があります。
関数一覧¶
| 関数 | 対象 | 重複 | スコープ { } |
読み書きの区別 | 行 |
|---|---|---|---|---|---|
| LINT.GetVarRefs | 変数・関数・ブロック | 出現ごと | あり(種類付き) | あり | あり |
| LINT.GetFuncUsedBy | 関数(ユーザー定義・システム) | 除去・名前順 | なし | - | なし |
| LINT.GetUserDefFuncUsedBy | ユーザー定義関数 | 除去・名前順 | なし | - | なし |
| LINT.GetGlobalVarUsedBy | グローバル変数(読み書き両方) | 除去・名前順 | なし | なし | なし |
| LINT.GetGlobalVarLetted | グローバル変数(代入のみ) | 除去・名前順 | なし | - | なし |
| LINT.GetLocalVarUsedBy | ローカル変数(読み書き両方) | 出現ごと | あり | なし | なし |
| LINT.GetLocalVarLetted | ローカル変数(代入のみ) | 出現ごと | あり | - | なし |
新しく検査を組む場合は LINT.GetVarRefs を使うと、他の関数の情報をすべて含んだ形で得られます。
Tc574-1 での変更¶
LINT.GetVarRefsを追加しました。GetGlobalVarLetted/GetLocalVarLetted- 配列要素への代入(
_a[0] = 1、G_a[0] = 1)を、その変数への代入として列挙するようになりました。 foreach _list ; _vのループ変数_vを代入として列挙するようになりました。
- 配列要素への代入(
GetLocalVarUsedBy- 同じステートメント内では、代入先の変数を最後に並べるようになりました。代入は右辺の評価後に行われるため、
_z = _z + 1は_z(読み)→_z(代入)の順になります。
- 同じステートメント内では、代入先の変数を最後に並べるようになりました。代入は右辺の評価後に行われるため、
GetLocalVarUsedBy/GetLocalVarLettedcase構文が内部で生成するローカル変数(_CaSe_ExPr_PrEfIx_で始まる名前)を列挙しないようになりました。
制約¶
辞書の読み込みが前提¶
解析対象は読み込みに成功した辞書です。構文エラーなどで辞書が読み込めない場合は緊急モードになり、LINT 系関数を使う辞書自体が動きません。重複定義・構文エラー・括弧付きの未定義関数呼び出し(E0071)などは、YAYA 本体が読み込み時にエラーとして出力します。
位置情報¶
- 行は
LINT.GetVarRefsで得られます。ステートメント単位の行番号です。 /による行の継続やヒアドキュメントで複数行にわたるステートメントは、その最後の行の番号になります。- 桁は得られません。YAYA 内部にも保持されていません。読み込み時に行の結合・
#defineの置換・コメント除去・{};での分割を行うためです。
名前の見え方¶
#define/#globaldefineは読み込み時に文字列として置換されるので、置換後の名前が見えます。EVAL('名前')やLETTONAME('名前', …)のように文字列で指定される名前は追えません。- LINT を行う辞書自身の関数・変数も一覧に含まれます。接頭辞などで除外してください。
- 埋め込み式
%(式)の展開で内部的に使われるTOSTRが、関数の呼び出しとして含まれます(LINT.GetFuncUsedBy/LINT.GetVarRefs)。ユーザーが書いたTOSTRと区別できません。
読み・書きの区別¶
LINT.GetVarRefsでは、=の代入先はw、複合代入(+=など、++--を含む)の代入先はrwです。- 配列要素への
=の代入(_a[0] = 1)はwとして扱います。配列の他の要素は残りますが、未定義の変数への要素代入も許されるためです。 - 並べ替え(代入先を最後に並べる)はステートメント単位です。1つの式に複数の代入が含まれる場合、実行順とは一致しないことがあります。
LINT.GetVarRefs以外の関数には読み・書きの印がありません。GetGlobalVarUsedBy/GetGlobalVarLettedは重複を除去するため、「代入されているが一度も読まれていないグローバル変数」を判別できません。GetLocalVarUsedByの各要素が読みか代入かを、GetLocalVarLettedと1件ずつ突き合わせて決めることはできません。
構文の情報¶
- ブロックの種類と、出現した構文の種類(条件式・for の各部・case の式など)は
LINT.GetVarRefsで得られます。 - 変数・関数を含まないステートメント(
break/continue/ 式なしのreturn/--、リテラルだけの出力など)は列挙されません。 othersは内部でelseに変換されるため、「case ブロックの直下にある else」をothersと判定しています。whenは内部でif/elseifに変換されるため、case の内部変数を含むかどうかで判定しています。
解析の範囲¶
- 分岐やループの流れは、ブロックの種類と深さから辞書側で組み立てる必要があります。
- 関数をまたいだ解析(引数
_argvの使われ方、グローバル変数の代入元など)は、関数ごとの結果を辞書側で突き合わせる必要があります。
検査の組み立て例¶
| 検査 | 可否 | 方法 |
|---|---|---|
| 未使用関数 | 可 | GETFUNCLIST の全関数について LINT.GetVarRefs の func,user を集め、どこにも現れない関数を列挙する(イベント関数など外部から呼ばれるものは除外) |
| 未使用ローカル変数 | 可 | スコープ({ })ごとに、w しかなく r / rw がない変数を探す |
| 未定義ローカル変数の読み | 可(分岐は辞書側で考慮) | 先頭から順に追い、そのスコープまたは外側のスコープで w / rw が現れる前の r / rw を探す |
| 未使用グローバル変数 | 可 | 全関数の var,global を集め、w しかない変数を探す(EVAL などで読まれるものは追えない) |
| 条件式内の代入 | 可 | context が if / elseif / while / switch / for-cond で、access が w / rw の件を探す |
| case の不正な節 | 一部可 | case ブロックの直下(深さ+1)に、when / others 以外のブロックや context が when 以外の件がないか調べる。変数・関数を含まない記述は検出できない |
| 名前の打ち間違い候補 | 可 | GETFUNCLIST / GETVARLIST との編集距離を辞書側で計算する |
| 行・桁の表示 | 行のみ可 | 桁は得られない |