AST トラバーサル
bca.analyze(...) gives you metrics. When you need the syntax tree itself (to find every function definition, pull a docstring, or port a py-tree-sitter matcher), parse once into an Ast and walk it with lazy Node handles. This page assumes you know what a tree-sitter parse tree is; the Node API mirrors py-tree-sitter’s.
Ast ハンドル
bca.Ast.parse(code, language) (or bca.Ast.from_path(path)) parses the source once and hands back a handle you can draw both metrics and the tree from, instead of parsing twice (once in py-tree-sitter, once in analyze()):
import big_code_analysis as bca
ast = bca.Ast.parse("fn main() { let x = 1 + 2; }", "rust")
ast.metrics() # analyze_source(...) と同じ dict
ast.root_node # 構文木。遅延走査される(後述)
このハンドルはイミュータブルかつスレッドセーフなので、analyze とまったく同様に ThreadPoolExecutor によるファンアウトと組み合わせられます。
Node ハンドル
ast.root_node は木のルートを遅延評価の Node として返します。ノードごとに dict を実体化する ast.dump() と異なり、Node は保持された木へのカーソルです。読み取るまでコストはかからず、選択的な抽出処理は訪問したノードの分だけコストを払います。
root = ast.root_node
root.kind # "source_file"
root.type # "source_file"(kind の py-tree-sitter 互換エイリアス)
root.children # list[Node]、直接の子ノード
root.child_by_field_name("…") # フィールドの子ノード、なければ None
node.text # ノードのソースバイト列
走査 API は py-tree-sitter を踏襲しています。children / named_children、parent、next_sibling / prev_sibling(および *_named_* 系)、child(i) / named_child(i)、child_by_field_name(name) / children_by_field_name(name)、そして親がそのノードに到達する際に経由する field_name が使えます。
サブツリー全体の走査
walk() is a lazy pre-order iterator over a node and its descendants; descendants_by_kind(kinds) collects the matches in one pass; and ast.find(filters) searches the whole tree, accepting the same vocabulary as bca count (all, function, call, comment, string, error, an exact kind, or a numeric kind_id):
# ファイル内のすべての関数名を遅延評価で取得する。
for fn in ast.find(["function_item"]):
name = fn.child_by_field_name("name")
print(name.text.decode())
# あるいは生の文法 kind でサブツリーを絞り込む。
idents = root.descendants_by_kind(["identifier"])
These have Rust counterparts, Node::preorder and Node::descendants_by_kind, so library callers get the same helpers.
座標
ノードは自身の位置をあらゆる語彙で報告するため、手作業での変換は不要です。
| アクセサ | 意味 |
|---|---|
start_byte / end_byte | ast.source へのバイトオフセット |
start_point / end_point | 0 始まりの (row, col)(py-tree-sitter 互換) |
start_line / end_line | 1 始まりの行番号 |
span | the {start_line, start_col, …, start_byte, end_byte} dict dump() emits: lines and columns are 1-based; byte offsets are 0-based and half-open |
したがって node.start_line == node.start_point[0] + 1 であり、ast.source[node.start_byte:node.end_byte] == node.text が成り立ちます。
node.type は node.kind の py-tree-sitter 互換エイリアスです。py-tree-sitter の node.type を前提に書かれたマッチャーはそのまま移植できます。bca における正準の綴りは kind のままです。
遅延ノードと dump() の比較
ast.dump() は木をネストした dict として返し、ast.root_node は遅延ハンドルを返します。両者には重要な違いが 2 つあります。
-
Memory.
dump()builds one dict (withspan,value,children) per node: fine for small files, costly for large ones. ANodewalk allocates only the handles you touch. -
Taxonomy. A
Node’skindis the raw grammar kind.dump()kinds pass through bca’sAlteratorand are curated; for example, string-literal nodes are renamed to"string"and flattened (their grammar children removed). So the two surfaces intentionally disagree on altered nodes:ast = bca.Ast.parse('fn f() { let s = "hi"; }', "rust") # 生の木では、文字列は引用符や内容の子ノードを保持している。 raw = next(n for n in ast.root_node.walk() if "string" in n.kind) assert raw.children文法が生成したものをそのまま扱いたい場合(py-tree-sitter のマッチャーを移植するならこちらが正解です)は遅延ノードを、bca が整形した JSON シリアライズ可能なビューが欲しい場合は
dump()を使ってください。
ライフタイムとスレッド
Node はその Ast を生かし続けます。パース結果への他の参照をすべて手放しても有効なままなので、Ast をローカルに構築する関数からノード(またはノードのリスト)を返しても安全です。ノードはスレッド間で共有しても安全です。
C/C++ preprocessor. The Python
Asttakes no preprocessor inputs:Ast.parseaccepts onlycode,languageandname, andAst.from_pathdoes not run the C/C++ preprocessor pass. Soast.source, and every node’s byte offsets, always index into the bytes the parser was given: thecodeyou passed toparse, or the file asfrom_pathread it (through the same EOL-normalising readeranalyzeuses). The expanded-source behaviour described in the Rust chapter applies only to a RustSourcebuilt with preprocessor results.
次のステップ
- Metric selection: compute only the metrics you need from the same parse.
- The CLI’s
dumpandcountcommands: the shell-level equivalents ofdump()andfind().