Project

General

Profile

構文木に詳細な位置情報をもたせる計画

雑な lineno 以上に詳しい情報をもたせたい。

ユースケース

  • エラー位置の正確な表示 (x + y + zNoMethodError が起きたとき、どっちで起きたのか示せる)
  • カバレッジの出力
  • TracePoint#column(どんなカラム番号を返すべきなのか?は、TracePoint のユースケースに依存しそう)
  • power_assert(TracePoint#column だけでできそう?現状の ripper で十分?)
  • 構文木を扱う API の構想(組み込み ripper 的なもの、遠い将来の話)

構文木ノードの位置とは

  • ノードが広がる範囲の先頭位置
  • ノードが広がる範囲の終端位置
  • ノードをもっとも特徴的に表す文字列の先頭位置
  • ノードをもっとも特徴的に表す文字列の終端位置
obj.foo(1, 2, 3)
^   ^ ^        ^
|   | |        +-終端位置
|   | +----------代表終端位置
|   +------------代表先頭位置
+----------------先頭位置
  • 例外の可視化やカバレッジの可視化のためには、ノード代表位置が望ましい。
    • obj.foo.bar.baz のノード先頭位置は全部重複してしまう
    • 文字列の範囲で可視化するためには当然、先頭と終端の両方が必要。
  • power_assert の可視化ではノード先頭〜終端位置が必要?(本当?)

その他

終端位置を示す方法が inclusive か exclusive か((n..m)(n...m) か)はどっちでもいいと思う。

ヒアドキュメントは要注意。「先頭位置〜終端位置」で扱うと他のノードの文字列が含まれる。例えば、

<<A + <<B
...
A
...
B

だと、<<A のノードの先頭位置〜終端位置と、<<B のノードの先頭位置〜終端位置、は重複してしまう。(しょうがないと思うけど)

位置の表現方法

ファイル先頭からのオフセットか、(行番号, カラム番号) の組にするか。TAB 文字をどう扱うか。0-based or 1-based ?

候補1: ファイル先頭からのオフセット

  • [+] 行とカラムとの一貫性が崩れることがない
  • [?] エラー表示で位置を示すのは処理が必要
    • ファイル全体の文字列を保持または読み込みする必要あり
    • 行内の TAB 文字処理が必要(面倒なだけ)
  • [?] カバレッジの可視化はおそらく容易

候補2: 行番号+行頭からのオフセット(バイト数)

  • [-] 行とカラムとの一貫性を気にする必要がある
  • [?] エラー表示で位置を示すのは処理が必要
    • ファイル全体の文字列を保持または読み込みする必要あり
    • ファイル全体の文字列から指定行目先頭を特定する必要あり
    • 行内の TAB 文字処理が必要(面倒なだけ)
  • [?] カバレッジの可視化はおそらく容易

候補3: 行番号+行頭からのオフセット(TAB 文字解決済みの見た目のカラム番号)

  • [-] 行とカラムとの一貫性を気にする必要がある
  • [-] TAB 文字幅を決める必要がある(TAB 文字幅指定 API とか考えたくない)
  • [-] カバレッジの可視化は少し面倒
    • <pre> などで可視化するのでない限り、バイト数に逆変換する必要あり(面倒なだけ)
  • [+] エラー表示で位置を示すのは処理が楽かも
    • エラーメッセージだけなら、undefined local variable or method 'foo' at (1, 1) for ... とカラムの数字だけで示せる
    • ターミナルで位置を図示するのは、行の文字列を取得する処理が必要(他の候補と同じ)に加え、

候補 3 だと↓は容易

obj.foo()
#   ^ NoMethodError

候補 3 だと↓は面倒(見た目のカラム番号からバイト数に逆変換する必要がある)

obj.foo()
#   ^^^ ここだけ色を変える

0-based or 1-based

世の中の多くのカラム番号は 1-based だと思ったが、Emacs は 0-based らしい。Ripper は 0-based を採用している。

  • 全部 0-based
  • データは 0-based として保持し、エラー出力など必要なら 1 を足す
  • 全部 1-based

実装方法

NODE には nd_reserved の 1 ワードしか空きがないので工夫する必要がある。2.5 で実現する方法、2.6 以降で実現する方法を分けて決めたい。

実装 1. nd_reserved に位置情報を数字でもたせる

  • ファイルオフセットならそのまま
  • (行番号, カラム番号) なら適当にエンコードする
    • 現状の lineno とは別に行番号を保持したほうが良い
  • オーバーヘッドは小さそう
  • 先頭位置開始点 or 代表位置開始点をもたせるのが限界

実装 2. nd_reservedT_IMEMO オブジェクトをもたせる

  • 4 ワード分の空きが確保できるので、いろんな位置を持たせられる
  • 実装は容易
  • オーバーヘッドはそれなり
    • NODE 1 つにつきオブジェクト 2 つ生成になる
    • パース時間が 1.4 倍くらい

実装 3. NODE を GC 対象オブジェクトとは別に管理する

  • ワード数の縛りがなくなるので、いろんな位置を持たせられる
  • 可変長ノードが作れたり、オブジェクト生成が減ったり、他にもメリットあるかも(無視できる程度かも)
  • 実装はわりと面倒
  • オーバーヘッドは小さそう(たぶん)