Skip to main content

libperl_macrogen/
apidoc.rs

1//! Perl Apidoc Format Parser
2//!
3//! Parses Perl's embed.fnc file and =for apidoc comments in header files.
4//! These provide type information for Perl's internal API functions and macros.
5
6use std::any::Any;
7use std::collections::HashMap;
8use std::fs;
9use std::io;
10use std::path::{Path, PathBuf};
11
12use crate::perl_config::{get_perl_version, PerlConfigError};
13
14use serde::{Deserialize, Serialize};
15
16use crate::intern::StringInterner;
17use crate::macro_def::{MacroKind, MacroTable};
18use crate::preprocessor::CommentCallback;
19use crate::source::FileId;
20use crate::token::Comment;
21
22/// 引数のNULL許容性
23#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
24pub enum Nullability {
25    /// NN - ポインタはNULLであってはならない
26    NotNull,
27    /// NULLOK - ポインタはNULLでも良い
28    Nullable,
29    /// 指定なし
30    #[default]
31    Unspecified,
32}
33
34/// パースされた引数
35#[derive(Debug, Clone, Serialize, Deserialize)]
36pub struct ApidocArg {
37    /// NULL許容性 (NN/NULLOK)
38    pub nullability: Nullability,
39    /// 非ゼロ制約 (NZ)
40    pub non_zero: bool,
41    /// 型 (例: "SV *", "const char *")
42    pub ty: String,
43    /// 引数名 (例: "sv", "name")
44    pub name: String,
45    /// 生の引数文字列 (パース前)
46    pub raw: String,
47}
48
49/// パースされたフラグ
50#[derive(Debug, Clone, Default, Serialize, Deserialize)]
51pub struct ApidocFlags {
52    // 可視性
53    pub api: bool,           // A - 公開API
54    pub core_only: bool,     // C - コア専用
55    pub ext_visible: bool,   // E - 拡張から見える
56    pub exported: bool,      // X - 明示的にエクスポート
57    pub not_exported: bool,  // e - エクスポートしない
58
59    // 関数タイプ
60    pub perl_prefix: bool,   // p - Perl_プレフィックス
61    pub static_fn: bool,     // S - S_プレフィックス (static)
62    pub static_perl: bool,   // s - Perl_プレフィックス (static)
63    pub inline: bool,        // i - インライン
64    pub force_inline: bool,  // I - 強制インライン
65    pub is_macro: bool,      // m - マクロのみ
66    pub custom_macro: bool,  // M - カスタムマクロ
67    pub no_thread_ctx: bool, // T - スレッドコンテキストなし
68
69    // ドキュメント
70    pub documented: bool,    // d - ドキュメントあり
71    pub hide_docs: bool,     // h - ドキュメント非表示
72    pub no_usage: bool,      // U - 使用例なし
73
74    // 属性
75    pub allocates: bool,     // a - メモリ確保
76    pub pure: bool,          // P - 純粋関数
77    pub return_required: bool, // R - 戻り値必須
78    pub no_return: bool,     // r - 返らない
79    pub deprecated: bool,    // D - 非推奨
80    pub compat: bool,        // b - バイナリ互換性
81
82    // その他
83    pub format_string: bool, // f - フォーマット文字列
84    pub varargs_no_fmt: bool, // F - 可変引数だがフォーマットではない
85    pub no_args: bool,       // n - 引数なし
86    pub unorthodox: bool,    // u - 非標準
87    pub experimental: bool,  // x - 実験的
88    pub is_typedef: bool,    // y - typedef
89
90    /// 生のフラグ文字列
91    pub raw: String,
92}
93
94/// apidocエントリ(関数/マクロの定義)
95#[derive(Debug, Clone, Serialize, Deserialize)]
96pub struct ApidocEntry {
97    /// フラグ
98    pub flags: ApidocFlags,
99    /// 戻り値の型(なければNone)
100    pub return_type: Option<String>,
101    /// 関数/マクロ名
102    pub name: String,
103    /// 引数リスト
104    pub args: Vec<ApidocArg>,
105    /// ソースファイル(分かる場合)
106    pub source_file: Option<String>,
107    /// 行番号(分かる場合)
108    pub line_number: Option<usize>,
109    /// トークン合成を行うマクロか(引数型が `"name"` のような引用符付きの場合)
110    #[serde(default)]
111    pub has_token_pasting: bool,
112}
113
114/// apidoc辞書(名前でエントリを検索可能)
115#[derive(Debug, Default, Serialize, Deserialize)]
116pub struct ApidocDict {
117    entries: HashMap<String, ApidocEntry>,
118}
119
120impl ApidocFlags {
121    /// フラグ文字列をパース
122    pub fn parse(flags: &str) -> Self {
123        let mut result = Self {
124            raw: flags.to_string(),
125            ..Default::default()
126        };
127
128        for ch in flags.chars() {
129            match ch {
130                // 可視性
131                'A' => result.api = true,
132                'C' => result.core_only = true,
133                'E' => result.ext_visible = true,
134                'X' => result.exported = true,
135                'e' => result.not_exported = true,
136
137                // 関数タイプ
138                'p' => result.perl_prefix = true,
139                'S' => result.static_fn = true,
140                's' => result.static_perl = true,
141                'i' => result.inline = true,
142                'I' => result.force_inline = true,
143                'm' => result.is_macro = true,
144                'M' => result.custom_macro = true,
145                'T' => result.no_thread_ctx = true,
146
147                // ドキュメント
148                'd' => result.documented = true,
149                'h' => result.hide_docs = true,
150                'U' => result.no_usage = true,
151
152                // 属性
153                'a' => {
154                    result.allocates = true;
155                    result.return_required = true; // 'a' implies 'R'
156                }
157                'P' => {
158                    result.pure = true;
159                    result.return_required = true; // 'P' implies 'R'
160                }
161                'R' => result.return_required = true,
162                'r' => result.no_return = true,
163                'D' => result.deprecated = true,
164                'b' => result.compat = true,
165
166                // その他
167                'f' => result.format_string = true,
168                'F' => result.varargs_no_fmt = true,
169                'n' => result.no_args = true,
170                'u' => result.unorthodox = true,
171                'x' => result.experimental = true,
172                'y' => result.is_typedef = true,
173
174                // 特殊文字(無視)
175                'G' | 'N' | 'O' | 'o' | 'v' | 'W' | ';' | '#' | '?' => {}
176
177                // 未知のフラグは無視
178                _ => {}
179            }
180        }
181
182        result
183    }
184}
185
186impl ApidocArg {
187    /// 引数文字列をパース (例: "NN SV *sv", "NULLOK const char *name")
188    pub fn parse(arg: &str) -> Option<Self> {
189        let raw = arg.to_string();
190        let trimmed = arg.trim();
191
192        if trimmed.is_empty() {
193            return None;
194        }
195
196        let mut nullability = Nullability::Unspecified;
197        let mut non_zero = false;
198        let mut remaining = trimmed;
199
200        // プレフィックスを処理
201        loop {
202            if remaining.starts_with("NN ") {
203                nullability = Nullability::NotNull;
204                remaining = remaining[3..].trim_start();
205            } else if remaining.starts_with("NULLOK ") {
206                nullability = Nullability::Nullable;
207                remaining = remaining[7..].trim_start();
208            } else if remaining.starts_with("NZ ") {
209                non_zero = true;
210                remaining = remaining[3..].trim_start();
211            } else {
212                break;
213            }
214        }
215
216        // 型と名前を分離
217        // C言語の引数は "type name" の形式
218        // ポインタの場合は "type *name" や "type * name" もありうる
219        let (ty, name) = Self::split_type_and_name(remaining);
220
221        Some(Self {
222            nullability,
223            non_zero,
224            ty,
225            name,
226            raw,
227        })
228    }
229
230    /// 型と名前を分離
231    fn split_type_and_name(s: &str) -> (String, String) {
232        let s = s.trim();
233
234        // 特殊なケース: "..." (可変引数)
235        if s == "..." {
236            return ("...".to_string(), String::new());
237        }
238
239        // 特殊なケース: 型のみ (type, cast, block, number, token, "string")
240        // これらは名前がない
241        if s == "type" || s == "cast" || s == "SP" || s == "block"
242            || s == "number" || s == "token" || s.starts_with('"')
243        {
244            return (s.to_string(), String::new());
245        }
246
247        // 最後の識別子を名前として取り出す
248        // 例: "const char * const name" -> ty="const char * const", name="name"
249        // 例: "SV *sv" -> ty="SV *", name="sv"
250        // 例: "int method" -> ty="int", name="method"
251
252        // 末尾から識別子を探す
253        let bytes = s.as_bytes();
254        let mut name_end = bytes.len();
255        let mut name_start;
256
257        // 末尾の空白をスキップ
258        while name_end > 0 && bytes[name_end - 1].is_ascii_whitespace() {
259            name_end -= 1;
260        }
261
262        // 識別子を後ろから取得
263        name_start = name_end;
264        while name_start > 0 {
265            let ch = bytes[name_start - 1];
266            if ch.is_ascii_alphanumeric() || ch == b'_' {
267                name_start -= 1;
268            } else {
269                break;
270            }
271        }
272
273        if name_start == name_end {
274            // 名前が見つからない場合、全体を型として扱う
275            return (s.to_string(), String::new());
276        }
277
278        let name = &s[name_start..name_end];
279        let ty = s[..name_start].trim_end();
280
281        // 型がポインタで終わる場合("SV *")、末尾の空白は除去済み
282        // ただし "const" だけで終わるようなケースを避ける
283
284        // 型が空の場合や "const" "struct" などで終わる場合は
285        // 名前を型として戻す(型名のみのケース)
286        if ty.is_empty() {
287            return (name.to_string(), String::new());
288        }
289
290        // 型が予約語のみの場合は名前を型とする
291        let type_keywords = ["const", "struct", "union", "enum", "unsigned", "signed", "volatile"];
292        let ty_lower = ty.to_lowercase();
293        for kw in &type_keywords {
294            if ty_lower == *kw {
295                // "const name" のようなケースは "const name" を型とする
296                return (s.to_string(), String::new());
297            }
298        }
299
300        (ty.to_string(), name.to_string())
301    }
302}
303
304impl ApidocEntry {
305    /// 単一行をパース(データ行のみ、コメントはNone)
306    /// 形式: flags|return_type|name|arg1|arg2|...|argN
307    pub fn parse_line(line: &str) -> Option<Self> {
308        let trimmed = line.trim();
309
310        // コメント行はスキップ
311        if trimmed.starts_with(": ") || trimmed == ":" || trimmed.is_empty() {
312            return None;
313        }
314
315        Self::parse_fields(trimmed)
316    }
317
318    /// =for apidoc 行をパース
319    /// 形式: =for apidoc name
320    /// または: =for apidoc flags|return_type|name|arg1|...
321    pub fn parse_apidoc_line(line: &str) -> Option<Self> {
322        let trimmed = line.trim();
323
324        // =for apidoc または =for apidoc_item で始まるか確認
325        let rest = if let Some(rest) = trimmed.strip_prefix("=for apidoc_item") {
326            rest.trim()
327        } else if let Some(rest) = trimmed.strip_prefix("=for apidoc") {
328            rest.trim()
329        } else {
330            return None;
331        };
332
333        if rest.is_empty() {
334            return None;
335        }
336
337        // パイプを含む場合は完全形式
338        if rest.contains('|') {
339            Self::parse_fields(rest)
340        } else {
341            // 名前のみの場合
342            Some(Self {
343                flags: ApidocFlags::default(),
344                return_type: None,
345                name: rest.to_string(),
346                args: Vec::new(),
347                source_file: None,
348                line_number: None,
349                has_token_pasting: false,
350            })
351        }
352    }
353
354    /// フィールド形式をパース
355    fn parse_fields(s: &str) -> Option<Self> {
356        let fields: Vec<&str> = s.split('|').collect();
357
358        if fields.len() < 3 {
359            return None;
360        }
361
362        let flags = ApidocFlags::parse(fields[0].trim());
363        let return_type = {
364            let rt = fields[1].trim();
365            if rt.is_empty() {
366                None
367            } else {
368                Some(rt.to_string())
369            }
370        };
371        let name = fields[2].trim().to_string();
372
373        if name.is_empty() {
374            return None;
375        }
376
377        let args: Vec<ApidocArg> = fields[3..]
378            .iter()
379            .filter_map(|arg| ApidocArg::parse(arg))
380            .collect();
381
382        // トークン合成マクロかどうかをチェック
383        // 引数型が `"name"` のような引用符で囲まれた文字列の場合はトークン合成用
384        let has_token_pasting = args.iter().any(|arg| {
385            arg.ty.starts_with('"') && arg.ty.ends_with('"')
386        });
387
388        Some(Self {
389            flags,
390            return_type,
391            name,
392            args,
393            source_file: None,
394            line_number: None,
395            has_token_pasting,
396        })
397    }
398
399    /// この関数がAPI公開かどうか
400    pub fn is_public_api(&self) -> bool {
401        self.flags.api
402    }
403
404    /// この関数がマクロかどうか
405    pub fn is_macro(&self) -> bool {
406        self.flags.is_macro
407    }
408
409    /// この関数がインラインかどうか
410    pub fn is_inline(&self) -> bool {
411        self.flags.inline || self.flags.force_inline
412    }
413
414    /// 型パラメータキーワードかどうかを判定
415    ///
416    /// apidoc で `type` や `cast` は特殊な引数で、C の型名を表す。
417    /// Rust では generic 型パラメータとして扱う。
418    pub fn is_type_param_keyword(ty: &str) -> bool {
419        ty == "type" || ty == "cast"
420    }
421
422    /// `type`/`cast` パラメータのインデックスを返す
423    pub fn type_param_indices(&self) -> Vec<usize> {
424        self.args
425            .iter()
426            .enumerate()
427            .filter(|(_, arg)| Self::is_type_param_keyword(&arg.ty))
428            .map(|(i, _)| i)
429            .collect()
430    }
431
432    /// 戻り値型が `type` または `cast` かどうか
433    pub fn returns_type_param(&self) -> bool {
434        self.return_type
435            .as_ref()
436            .map_or(false, |t| Self::is_type_param_keyword(t))
437    }
438
439    /// ジェネリック関数として生成すべきか
440    pub fn is_generic(&self) -> bool {
441        self.returns_type_param() || !self.type_param_indices().is_empty()
442    }
443
444    /// 引数がリテラル文字列型かどうか(apidoc で `"..."` 形式の引数)
445    ///
446    /// 例: `"literal string"`, `"key"`, `"message"`
447    /// Rust では `&str` として扱う。
448    pub fn is_literal_string_keyword(ty: &str) -> bool {
449        ty.starts_with('"')
450    }
451
452    /// 引数に `token` 型を持つかどうか
453    ///
454    /// `token` 型の引数は `##` によるトークン合成に使われるため、
455    /// このマクロは展開が必要(explicit_expand に追加すべき)
456    pub fn has_token_arg(&self) -> bool {
457        self.args.iter().any(|arg| arg.ty == "token")
458    }
459}
460
461impl ApidocDict {
462    /// 新しい辞書を作成
463    pub fn new() -> Self {
464        Self::default()
465    }
466
467    /// エントリを追加
468    pub fn insert(&mut self, name: String, entry: ApidocEntry) {
469        self.entries.insert(name, entry);
470    }
471
472    /// embed.fncファイルをパース
473    pub fn parse_embed_fnc<P: AsRef<Path>>(path: P) -> io::Result<Self> {
474        let content = fs::read_to_string(path)?;
475        Ok(Self::parse_embed_fnc_str(&content))
476    }
477
478    /// 文字列からembed.fnc形式をパース
479    pub fn parse_embed_fnc_str(content: &str) -> Self {
480        let mut dict = Self::new();
481        let mut continued_line = String::new();
482        let mut line_number = 0usize;
483
484        for line in content.lines() {
485            line_number += 1;
486
487            // 行継続の処理
488            if line.ends_with('\\') {
489                // 末尾のバックスラッシュを除去して追加
490                continued_line.push_str(line.trim_end_matches('\\'));
491                continued_line.push(' ');
492                continue;
493            }
494
495            let full_line = if continued_line.is_empty() {
496                line.to_string()
497            } else {
498                continued_line.push_str(line);
499                let result = continued_line.clone();
500                continued_line.clear();
501                result
502            };
503
504            if let Some(mut entry) = ApidocEntry::parse_line(&full_line) {
505                entry.line_number = Some(line_number);
506                dict.entries.insert(entry.name.clone(), entry);
507            }
508        }
509
510        dict
511    }
512
513    /// ヘッダーファイルから =for apidoc コメントを抽出
514    pub fn parse_header_apidoc<P: AsRef<Path>>(path: P) -> io::Result<Self> {
515        let content = fs::read_to_string(&path)?;
516        let mut dict = Self::parse_header_apidoc_str(&content);
517
518        // ソースファイル情報を設定
519        let path_str = path.as_ref().to_string_lossy().to_string();
520        for entry in dict.entries.values_mut() {
521            entry.source_file = Some(path_str.clone());
522        }
523
524        Ok(dict)
525    }
526
527    /// 文字列からヘッダーのapidocコメントをパース
528    pub fn parse_header_apidoc_str(content: &str) -> Self {
529        let mut dict = Self::new();
530        let mut line_number = 0usize;
531
532        for line in content.lines() {
533            line_number += 1;
534
535            // =for apidoc を探す
536            if let Some(idx) = line.find("=for apidoc") {
537                let apidoc_part = &line[idx..];
538                if let Some(mut entry) = ApidocEntry::parse_apidoc_line(apidoc_part) {
539                    entry.line_number = Some(line_number);
540                    dict.entries.insert(entry.name.clone(), entry);
541                }
542            }
543        }
544
545        dict
546    }
547
548    /// 別の辞書をマージ
549    pub fn merge(&mut self, other: Self) {
550        for (name, entry) in other.entries {
551            self.entries.entry(name).or_insert(entry);
552        }
553    }
554
555    /// エントリ数を取得
556    pub fn len(&self) -> usize {
557        self.entries.len()
558    }
559
560    /// 辞書が空かどうか
561    pub fn is_empty(&self) -> bool {
562        self.entries.is_empty()
563    }
564
565    /// 名前でエントリを検索
566    pub fn get(&self, name: &str) -> Option<&ApidocEntry> {
567        self.entries.get(name)
568    }
569
570    /// 名前でエントリを mutable で検索(apidoc_patches で return_type 等を上書きする用途)
571    pub fn get_mut(&mut self, name: &str) -> Option<&mut ApidocEntry> {
572        self.entries.get_mut(name)
573    }
574
575    /// イテレータを取得
576    pub fn iter(&self) -> impl Iterator<Item = (&String, &ApidocEntry)> {
577        self.entries.iter()
578    }
579
580    /// 関数のみをイテレート(マクロを除く)
581    pub fn functions(&self) -> impl Iterator<Item = (&String, &ApidocEntry)> {
582        self.entries.iter().filter(|(_, e)| !e.is_macro())
583    }
584
585    /// マクロのみをイテレート
586    pub fn macros(&self) -> impl Iterator<Item = (&String, &ApidocEntry)> {
587        self.entries.iter().filter(|(_, e)| e.is_macro())
588    }
589
590    /// フィルタにマッチするエントリをダンプ(デバッグ用)
591    ///
592    /// filter が空文字列の場合は全エントリを出力。
593    /// filter に文字列が指定された場合は、名前にその文字列を含むエントリのみ出力。
594    pub fn dump_filtered(&self, filter: &str) {
595        let mut names: Vec<_> = self.entries.keys().collect();
596        names.sort();
597
598        for name in names {
599            // フィルタが空でない場合、名前にフィルタ文字列を含むかチェック
600            if !filter.is_empty() && !name.contains(filter) {
601                continue;
602            }
603
604            if let Some(entry) = self.entries.get(name) {
605                eprintln!("{}:", name);
606                eprintln!("  flags: {}", entry.flags.raw);
607                if let Some(ref ret) = entry.return_type {
608                    eprintln!("  return_type: {}", ret);
609                } else {
610                    eprintln!("  return_type: (none)");
611                }
612                eprintln!("  args:");
613                for (i, arg) in entry.args.iter().enumerate() {
614                    eprintln!("    [{}] {} {} ({:?}{})",
615                        i,
616                        arg.ty,
617                        arg.name,
618                        arg.nullability,
619                        if arg.non_zero { ", NZ" } else { "" }
620                    );
621                }
622                if let Some(ref src) = entry.source_file {
623                    eprintln!("  source: {}:{}", src, entry.line_number.unwrap_or(0));
624                }
625                eprintln!();
626            }
627        }
628    }
629
630    /// 統計情報を出力
631    pub fn stats(&self) -> ApidocStats {
632        let mut stats = ApidocStats::default();
633
634        for entry in self.entries.values() {
635            if entry.is_macro() {
636                stats.macro_count += 1;
637            } else if entry.is_inline() {
638                stats.inline_count += 1;
639            } else {
640                stats.function_count += 1;
641            }
642
643            if entry.is_public_api() {
644                stats.api_count += 1;
645            }
646        }
647
648        stats.total = self.entries.len();
649        stats
650    }
651
652    /// JSONファイルに保存
653    pub fn save_json<P: AsRef<Path>>(&self, path: P) -> io::Result<()> {
654        let json = serde_json::to_string_pretty(self)
655            .map_err(|e| io::Error::new(io::ErrorKind::Other, e))?;
656        fs::write(path, json)
657    }
658
659    /// JSON文字列にシリアライズ
660    pub fn to_json(&self) -> Result<String, serde_json::Error> {
661        serde_json::to_string_pretty(self)
662    }
663
664    /// JSONファイルから読み込み
665    pub fn load_json<P: AsRef<Path>>(path: P) -> io::Result<Self> {
666        let content = fs::read_to_string(path)?;
667        Self::from_json(&content)
668            .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))
669    }
670
671    /// JSON文字列からデシリアライズ
672    pub fn from_json(json: &str) -> Result<Self, serde_json::Error> {
673        serde_json::from_str(json)
674    }
675
676    /// ファイル拡張子に基づいて適切な形式で読み込み
677    /// - .json -> JSON形式
678    /// - それ以外 -> embed.fnc形式
679    pub fn load_auto<P: AsRef<Path>>(path: P) -> io::Result<Self> {
680        let path_ref = path.as_ref();
681        if path_ref.extension().is_some_and(|ext| ext == "json") {
682            Self::load_json(path_ref)
683        } else {
684            Self::parse_embed_fnc(path_ref)
685        }
686    }
687
688    /// 指定バージョン用の JSON ファイルパスを検索
689    ///
690    /// apidoc/v{major}.{minor}.json が存在すれば Some(path)、なければ None
691    /// フォールバックは行わない(完全一致のみ)
692    pub fn find_json_for_version<P: AsRef<Path>>(
693        apidoc_dir: P,
694        major: u32,
695        minor: u32,
696    ) -> Option<std::path::PathBuf> {
697        let filename = format!("v{}.{}.json", major, minor);
698        let path = apidoc_dir.as_ref().join(&filename);
699        if path.exists() {
700            Some(path)
701        } else {
702            None
703        }
704    }
705
706    /// Perl バージョンに基づいて apidoc を自動ロード
707    ///
708    /// apidoc_dir: apidoc/ ディレクトリのパス
709    /// 成功時: 対応する JSON からロードした ApidocDict
710    /// 失敗時: io::Error(ファイルが見つからない場合など)
711    pub fn load_for_perl_version<P: AsRef<Path>>(
712        apidoc_dir: P,
713        major: u32,
714        minor: u32,
715    ) -> io::Result<Self> {
716        let path = Self::find_json_for_version(&apidoc_dir, major, minor).ok_or_else(|| {
717            io::Error::new(
718                io::ErrorKind::NotFound,
719                format!(
720                    "{}/v{}.{}.json not found for Perl {}.{}.\n\
721                     Please specify --apidoc explicitly or add the JSON file.",
722                    apidoc_dir.as_ref().display(),
723                    major,
724                    minor,
725                    major,
726                    minor
727                ),
728            )
729        })?;
730        Self::load_json(&path)
731    }
732
733    /// apidoc 内の型文字列に含まれるマクロを展開する
734    ///
735    /// C ヘッダー解析完了後に呼び出す。
736    /// Off_t → off_t, Size_t → size_t などの型マクロを展開する。
737    pub fn expand_type_macros(&mut self, macro_table: &MacroTable, interner: &StringInterner) {
738        for entry in self.entries.values_mut() {
739            // 戻り値型を展開
740            if let Some(ref mut return_type) = entry.return_type {
741                *return_type = expand_type_string(return_type, macro_table, interner);
742            }
743            // パラメータ型を展開
744            for arg in &mut entry.args {
745                arg.ty = expand_type_string(&arg.ty, macro_table, interner);
746            }
747        }
748    }
749}
750
751/// 型文字列内の識別子をマクロ展開する
752///
753/// 型文字列をトークン化して、識別子がオブジェクトマクロなら展開。
754/// ポインタ (*) や const などは保持。
755fn expand_type_string(
756    type_str: &str,
757    macro_table: &MacroTable,
758    interner: &StringInterner,
759) -> String {
760    let mut result = String::new();
761    let mut chars = type_str.chars().peekable();
762
763    while let Some(c) = chars.next() {
764        if c.is_alphabetic() || c == '_' {
765            // 識別子を収集
766            let mut ident = String::from(c);
767            while let Some(&nc) = chars.peek() {
768                if nc.is_alphanumeric() || nc == '_' {
769                    ident.push(chars.next().unwrap());
770                } else {
771                    break;
772                }
773            }
774
775            // マクロ展開を試みる
776            if let Some(interned) = interner.lookup(&ident) {
777                if let Some(macro_def) = macro_table.get(interned) {
778                    if matches!(macro_def.kind, MacroKind::Object) && !macro_def.body.is_empty() {
779                        // オブジェクトマクロ: 本体を展開
780                        let expanded: String = macro_def
781                            .body
782                            .iter()
783                            .map(|t| t.kind.format(interner))
784                            .collect::<Vec<_>>()
785                            .join("");
786                        result.push_str(&expanded);
787                        continue;
788                    }
789                }
790            }
791            // マクロでなければそのまま
792            result.push_str(&ident);
793        } else {
794            result.push(c);
795        }
796    }
797
798    result
799}
800
801/// apidoc 解決エラー
802#[derive(Debug)]
803pub enum ApidocResolveError {
804    /// 開発版 Perl(奇数マイナーバージョン)
805    DevelopmentVersion { major: u32, minor: u32 },
806    /// apidoc ディレクトリが見つからない
807    DirectoryNotFound,
808    /// 対応する JSON ファイルが見つからない
809    JsonNotFound { path: PathBuf, major: u32, minor: u32 },
810    /// Perl バージョン取得エラー
811    VersionError(PerlConfigError),
812}
813
814impl std::fmt::Display for ApidocResolveError {
815    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
816        match self {
817            ApidocResolveError::DevelopmentVersion { major, minor } => {
818                write!(
819                    f,
820                    "Perl {}.{} is a development version.\n\
821                     Please specify --apidoc explicitly (e.g., --apidoc path/to/embed.fnc)",
822                    major, minor
823                )
824            }
825            ApidocResolveError::DirectoryNotFound => {
826                write!(
827                    f,
828                    "apidoc directory not found.\n\
829                     Please specify --apidoc explicitly."
830                )
831            }
832            ApidocResolveError::JsonNotFound { path, major, minor } => {
833                write!(
834                    f,
835                    "{}/v{}.{}.json not found for Perl {}.{}.\n\
836                     Please specify --apidoc explicitly or add the JSON file.",
837                    path.display(),
838                    major, minor, major, minor
839                )
840            }
841            ApidocResolveError::VersionError(e) => {
842                write!(f, "Failed to get Perl version: {}", e)
843            }
844        }
845    }
846}
847
848impl std::error::Error for ApidocResolveError {
849    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
850        match self {
851            ApidocResolveError::VersionError(e) => Some(e),
852            _ => None,
853        }
854    }
855}
856
857impl From<PerlConfigError> for ApidocResolveError {
858    fn from(e: PerlConfigError) -> Self {
859        ApidocResolveError::VersionError(e)
860    }
861}
862
863/// apidoc ディレクトリを検索
864///
865/// 検索順序:
866/// 1. 指定されたベースディレクトリの apidoc/(base_dir が Some の場合)
867/// 2. 埋め込みデータのキャッシュディレクトリ(ライブラリ配布時)
868/// 3. 実行ファイルと同じディレクトリの apidoc/
869/// 4. 実行ファイルの親ディレクトリの apidoc/ (開発時: target/debug/../apidoc)
870/// 5. カレントディレクトリの apidoc/
871pub fn find_apidoc_dir_from(base_dir: Option<&Path>) -> Option<PathBuf> {
872    // 1. 指定されたベースディレクトリ
873    if let Some(base) = base_dir {
874        let apidoc_dir = base.join("apidoc");
875        if apidoc_dir.is_dir() {
876            return Some(apidoc_dir);
877        }
878        // base_dir 自体が apidoc ディレクトリかもしれない
879        if base.is_dir() && base.file_name().is_some_and(|n| n == "apidoc") {
880            return Some(base.to_path_buf());
881        }
882    }
883
884    // 2. 埋め込みデータのキャッシュディレクトリ
885    if let Some(embedded_dir) = crate::apidoc_data::get_apidoc_dir() {
886        if embedded_dir.is_dir() {
887            return Some(embedded_dir);
888        }
889    }
890
891    // 3. 実行ファイルと同じディレクトリ
892    if let Ok(exe_path) = std::env::current_exe() {
893        if let Some(exe_dir) = exe_path.parent() {
894            let apidoc_dir = exe_dir.join("apidoc");
895            if apidoc_dir.is_dir() {
896                return Some(apidoc_dir);
897            }
898
899            // 4. 実行ファイルの親ディレクトリ (開発時: target/debug/../apidoc -> project/apidoc)
900            if let Some(parent_dir) = exe_dir.parent() {
901                let apidoc_dir = parent_dir.join("apidoc");
902                if apidoc_dir.is_dir() {
903                    return Some(apidoc_dir);
904                }
905
906                // target/debug の場合は 2階層上
907                if let Some(grandparent_dir) = parent_dir.parent() {
908                    let apidoc_dir = grandparent_dir.join("apidoc");
909                    if apidoc_dir.is_dir() {
910                        return Some(apidoc_dir);
911                    }
912                }
913            }
914        }
915    }
916
917    // 5. カレントディレクトリ
918    if let Ok(cwd) = std::env::current_dir() {
919        let apidoc_dir = cwd.join("apidoc");
920        if apidoc_dir.is_dir() {
921            return Some(apidoc_dir);
922        }
923    }
924
925    None
926}
927
928/// apidoc ファイルのパスを解決
929///
930/// - explicit_path が Some なら: そのまま返す
931/// - explicit_path が None で auto_mode なら: Perl バージョンから自動検索
932/// - それ以外: None を返す
933///
934/// # Arguments
935/// - `explicit_path`: 明示的に指定されたパス
936/// - `auto_mode`: 自動モード(Perl バージョンから検索)
937/// - `apidoc_dir`: 検索対象ディレクトリ(None なら find_apidoc_dir_from() で検索)
938///
939/// # Returns
940/// - `Ok(Some(path))`: 解決されたパス
941/// - `Ok(None)`: 自動モードでなく、明示的パスもない場合
942/// - `Err(...)`: 自動モードで解決に失敗した場合
943pub fn resolve_apidoc_path(
944    explicit_path: Option<&Path>,
945    auto_mode: bool,
946    apidoc_dir: Option<&Path>,
947) -> Result<Option<PathBuf>, ApidocResolveError> {
948    // 明示的に指定されている場合
949    if let Some(path) = explicit_path {
950        return Ok(Some(path.to_path_buf()));
951    }
952
953    // 自動モードでない場合
954    if !auto_mode {
955        return Ok(None);
956    }
957
958    // 自動モード: Perl バージョンから検索
959    let (major, minor) = get_perl_version()?;
960
961    // 奇数マイナーバージョン(開発版)はエラー
962    if minor % 2 == 1 {
963        return Err(ApidocResolveError::DevelopmentVersion { major, minor });
964    }
965
966    // apidoc ディレクトリを検索
967    let resolved_apidoc_dir = find_apidoc_dir_from(apidoc_dir)
968        .ok_or(ApidocResolveError::DirectoryNotFound)?;
969
970    // バージョンに対応する JSON ファイルを検索
971    let json_path = ApidocDict::find_json_for_version(&resolved_apidoc_dir, major, minor)
972        .ok_or_else(|| ApidocResolveError::JsonNotFound {
973            path: resolved_apidoc_dir,
974            major,
975            minor,
976        })?;
977
978    Ok(Some(json_path))
979}
980
981/// 統計情報
982#[derive(Debug, Default, Serialize, Deserialize)]
983pub struct ApidocStats {
984    pub total: usize,
985    pub function_count: usize,
986    pub macro_count: usize,
987    pub inline_count: usize,
988    pub api_count: usize,
989}
990
991/// コメントから apidoc を抽出するコレクター
992///
993/// Preprocessor の CommentCallback として登録し、
994/// `=for apidoc` を含むコメントを見つけたら辞書に登録する。
995pub struct ApidocCollector {
996    entries: HashMap<String, ApidocEntry>,
997    /// token 型の引数を持つマクロ名(展開が必要なマクロ)
998    token_type_macros: Vec<String>,
999}
1000
1001impl ApidocCollector {
1002    /// 新しいコレクターを作成
1003    pub fn new() -> Self {
1004        Self {
1005            entries: HashMap::new(),
1006            token_type_macros: Vec::new(),
1007        }
1008    }
1009
1010    /// 収集した apidoc を ApidocDict にマージ
1011    pub fn merge_into(self, dict: &mut ApidocDict) {
1012        // デバッグ: LIBPERL_MACROGEN_DEBUG_APIDOC=1 のとき、共通 patches で関心のある
1013        // 名前(typ. RCPV_*)が inline =for apidoc から拾えているかを cargo:warning= で
1014        // CI ログに可視化する。
1015        if crate::apidoc_patches::is_apidoc_debug_enabled() {
1016            let total = self.entries.len();
1017            let rcpv: Vec<String> = self.entries.iter()
1018                .filter(|(k, _)| k.starts_with("RCPV"))
1019                .map(|(k, e)| format!("{}->{}", k, e.return_type.as_deref().unwrap_or("?")))
1020                .collect();
1021            crate::apidoc_patches::cargo_warning(&format!(
1022                "[apidoc-collector] merge_into: {} entries from inline =for apidoc; \
1023                 RCPV-named ({}): {:?}",
1024                total, rcpv.len(), rcpv
1025            ));
1026        }
1027        for (name, entry) in self.entries {
1028            dict.insert(name, entry);
1029        }
1030    }
1031
1032    /// 収集数を返す
1033    pub fn len(&self) -> usize {
1034        self.entries.len()
1035    }
1036
1037    /// 空かどうか
1038    pub fn is_empty(&self) -> bool {
1039        self.entries.is_empty()
1040    }
1041
1042    /// 検出した token 型マクロ名を返す
1043    ///
1044    /// これらのマクロはトークン合成(`##`)を使用するため、
1045    /// TokenExpander で展開が必要。
1046    pub fn token_type_macros(&self) -> &[String] {
1047        &self.token_type_macros
1048    }
1049}
1050
1051impl Default for ApidocCollector {
1052    fn default() -> Self {
1053        Self::new()
1054    }
1055}
1056
1057impl CommentCallback for ApidocCollector {
1058    fn on_comment(&mut self, comment: &Comment, _file_id: FileId, _is_target: bool) {
1059        // コメント内の各行を処理
1060        // (is_target チェックは呼び出し側で行われるため、ここでは常に処理)
1061        for line in comment.text.lines() {
1062            if let Some(entry) = ApidocEntry::parse_apidoc_line(line) {
1063                // token 型の引数を持つマクロを記録
1064                if entry.has_token_arg() {
1065                    self.token_type_macros.push(entry.name.clone());
1066                }
1067                self.entries.insert(entry.name.clone(), entry);
1068            }
1069        }
1070    }
1071
1072    fn into_any(self: Box<Self>) -> Box<dyn Any> {
1073        self
1074    }
1075}
1076
1077#[cfg(test)]
1078mod tests {
1079    use super::*;
1080
1081    #[test]
1082    fn test_parse_flags() {
1083        let flags = ApidocFlags::parse("Adp");
1084        assert!(flags.api);
1085        assert!(flags.documented);
1086        assert!(flags.perl_prefix);
1087        assert!(!flags.is_macro);
1088    }
1089
1090    #[test]
1091    fn test_parse_flags_macro() {
1092        let flags = ApidocFlags::parse("ARdm");
1093        assert!(flags.api);
1094        assert!(flags.return_required);
1095        assert!(flags.documented);
1096        assert!(flags.is_macro);
1097    }
1098
1099    #[test]
1100    fn test_parse_flags_allocates_implies_r() {
1101        let flags = ApidocFlags::parse("a");
1102        assert!(flags.allocates);
1103        assert!(flags.return_required);
1104    }
1105
1106    #[test]
1107    fn test_parse_arg_simple() {
1108        let arg = ApidocArg::parse("int method").unwrap();
1109        assert_eq!(arg.nullability, Nullability::Unspecified);
1110        assert!(!arg.non_zero);
1111        assert_eq!(arg.ty, "int");
1112        assert_eq!(arg.name, "method");
1113    }
1114
1115    #[test]
1116    fn test_parse_arg_pointer() {
1117        let arg = ApidocArg::parse("SV *sv").unwrap();
1118        assert_eq!(arg.ty, "SV *");
1119        assert_eq!(arg.name, "sv");
1120    }
1121
1122    #[test]
1123    fn test_parse_arg_not_null() {
1124        let arg = ApidocArg::parse("NN SV *sv").unwrap();
1125        assert_eq!(arg.nullability, Nullability::NotNull);
1126        assert_eq!(arg.ty, "SV *");
1127        assert_eq!(arg.name, "sv");
1128    }
1129
1130    #[test]
1131    fn test_parse_arg_nullok() {
1132        let arg = ApidocArg::parse("NULLOK SV *sv").unwrap();
1133        assert_eq!(arg.nullability, Nullability::Nullable);
1134        assert_eq!(arg.ty, "SV *");
1135        assert_eq!(arg.name, "sv");
1136    }
1137
1138    #[test]
1139    fn test_parse_arg_const_pointer() {
1140        let arg = ApidocArg::parse("NN const char * const name").unwrap();
1141        assert_eq!(arg.nullability, Nullability::NotNull);
1142        assert_eq!(arg.ty, "const char * const");
1143        assert_eq!(arg.name, "name");
1144    }
1145
1146    #[test]
1147    fn test_parse_arg_varargs() {
1148        let arg = ApidocArg::parse("...").unwrap();
1149        assert_eq!(arg.ty, "...");
1150        assert_eq!(arg.name, "");
1151    }
1152
1153    #[test]
1154    fn test_parse_line_simple() {
1155        let entry = ApidocEntry::parse_line("Adp	|SV *	|av_pop 	|NN AV *av").unwrap();
1156        assert!(entry.flags.api);
1157        assert!(entry.flags.documented);
1158        assert!(entry.flags.perl_prefix);
1159        assert_eq!(entry.return_type, Some("SV *".to_string()));
1160        assert_eq!(entry.name, "av_pop");
1161        assert_eq!(entry.args.len(), 1);
1162        assert_eq!(entry.args[0].ty, "AV *");
1163        assert_eq!(entry.args[0].name, "av");
1164        assert_eq!(entry.args[0].nullability, Nullability::NotNull);
1165    }
1166
1167    #[test]
1168    fn test_parse_line_comment() {
1169        assert!(ApidocEntry::parse_line(": This is a comment").is_none());
1170        assert!(ApidocEntry::parse_line(":").is_none());
1171        assert!(ApidocEntry::parse_line("").is_none());
1172    }
1173
1174    #[test]
1175    fn test_parse_line_macro() {
1176        let entry = ApidocEntry::parse_line("ARdm	|SSize_t|av_tindex	|NN AV *av").unwrap();
1177        assert!(entry.flags.is_macro);
1178        assert!(entry.flags.return_required);
1179        assert_eq!(entry.name, "av_tindex");
1180    }
1181
1182    #[test]
1183    fn test_parse_line_multiple_args() {
1184        let entry = ApidocEntry::parse_line(
1185            "Adp	|SV *	|amagic_call	|NN SV *left	|NN SV *right	|int method	|int dir"
1186        ).unwrap();
1187        assert_eq!(entry.args.len(), 4);
1188        assert_eq!(entry.args[0].name, "left");
1189        assert_eq!(entry.args[1].name, "right");
1190        assert_eq!(entry.args[2].name, "method");
1191        assert_eq!(entry.args[3].name, "dir");
1192    }
1193
1194    #[test]
1195    fn test_parse_apidoc_line_name_only() {
1196        let entry = ApidocEntry::parse_apidoc_line("=for apidoc av_pop").unwrap();
1197        assert_eq!(entry.name, "av_pop");
1198        assert!(entry.return_type.is_none());
1199        assert!(entry.args.is_empty());
1200    }
1201
1202    #[test]
1203    fn test_parse_apidoc_line_full() {
1204        let entry = ApidocEntry::parse_apidoc_line(
1205            "=for apidoc Am|char*|SvPV|SV* sv|STRLEN len"
1206        ).unwrap();
1207        assert!(entry.flags.api);
1208        assert!(entry.flags.is_macro);
1209        assert_eq!(entry.return_type, Some("char*".to_string()));
1210        assert_eq!(entry.name, "SvPV");
1211        assert_eq!(entry.args.len(), 2);
1212    }
1213
1214    #[test]
1215    fn test_parse_apidoc_item() {
1216        let entry = ApidocEntry::parse_apidoc_line(
1217            "=for apidoc_item |const char*|SvPV_const|SV* sv|STRLEN len"
1218        ).unwrap();
1219        assert_eq!(entry.return_type, Some("const char*".to_string()));
1220        assert_eq!(entry.name, "SvPV_const");
1221    }
1222
1223    #[test]
1224    fn test_embed_fnc_str() {
1225        let content = r#"
1226: This is a comment
1227Adp	|SV *	|av_pop 	|NN AV *av
1228ARdm	|SSize_t|av_tindex	|NN AV *av
1229"#;
1230        let dict = ApidocDict::parse_embed_fnc_str(content);
1231        assert_eq!(dict.len(), 2);
1232        assert!(dict.get("av_pop").is_some());
1233        assert!(dict.get("av_tindex").is_some());
1234    }
1235
1236    #[test]
1237    fn test_embed_fnc_continuation() {
1238        let content = r#"
1239pr	|void	|abort_execution|NULLOK SV *msg_sv			\
1240				|NN const char * const name
1241"#;
1242        let dict = ApidocDict::parse_embed_fnc_str(content);
1243        assert_eq!(dict.len(), 1);
1244        let entry = dict.get("abort_execution").unwrap();
1245        assert_eq!(entry.args.len(), 2);
1246        assert_eq!(entry.args[0].nullability, Nullability::Nullable);
1247        assert_eq!(entry.args[1].nullability, Nullability::NotNull);
1248    }
1249
1250    #[test]
1251    fn test_header_apidoc_str() {
1252        let content = r#"
1253/*
1254=for apidoc Am|char*|SvPV|SV* sv|STRLEN len
1255
1256Returns a pointer to the string value of the SV.
1257
1258=cut
1259*/
1260"#;
1261        let dict = ApidocDict::parse_header_apidoc_str(content);
1262        assert_eq!(dict.len(), 1);
1263        assert!(dict.get("SvPV").is_some());
1264    }
1265
1266    #[test]
1267    fn test_dict_stats() {
1268        let content = r#"
1269Adp	|SV *	|av_pop 	|NN AV *av
1270ARdm	|SSize_t|av_tindex	|NN AV *av
1271ARdip	|Size_t |av_count	|NN AV *av
1272Cp	|void	|internal_fn	|int x
1273"#;
1274        let dict = ApidocDict::parse_embed_fnc_str(content);
1275        let stats = dict.stats();
1276        assert_eq!(stats.total, 4);
1277        assert_eq!(stats.macro_count, 1);
1278        assert_eq!(stats.inline_count, 1);
1279        assert_eq!(stats.function_count, 2);
1280        assert_eq!(stats.api_count, 3);
1281    }
1282
1283    #[test]
1284    fn test_dict_merge() {
1285        let content1 = "Adp	|SV *	|av_pop 	|NN AV *av";
1286        let content2 = "ARdm	|SSize_t|av_tindex	|NN AV *av";
1287
1288        let mut dict1 = ApidocDict::parse_embed_fnc_str(content1);
1289        let dict2 = ApidocDict::parse_embed_fnc_str(content2);
1290
1291        dict1.merge(dict2);
1292        assert_eq!(dict1.len(), 2);
1293    }
1294
1295    #[test]
1296    fn test_has_token_arg() {
1297        // XopENTRYCUSTOM has a token argument
1298        let entry = ApidocEntry::parse_apidoc_line(
1299            "=for apidoc Amu||XopENTRYCUSTOM|const OP *o|token which"
1300        ).unwrap();
1301        assert!(entry.has_token_arg());
1302        assert_eq!(entry.name, "XopENTRYCUSTOM");
1303
1304        // Regular macro without token argument
1305        let entry2 = ApidocEntry::parse_apidoc_line(
1306            "=for apidoc Am|char*|SvPV|SV* sv|STRLEN len"
1307        ).unwrap();
1308        assert!(!entry2.has_token_arg());
1309    }
1310
1311    #[test]
1312    fn test_has_token_arg_embed_fnc() {
1313        // Test embed.fnc style with token argument
1314        let entry = ApidocEntry::parse_line("Amu	|	|XopENTRYCUSTOM	|const OP *o	|token which").unwrap();
1315        assert!(entry.has_token_arg());
1316        assert_eq!(entry.name, "XopENTRYCUSTOM");
1317    }
1318
1319    #[test]
1320    fn test_expand_type_macros() {
1321        use crate::macro_def::MacroDef;
1322        use crate::source::SourceLocation;
1323        use crate::token::{Token, TokenKind};
1324
1325        // apidoc を作成
1326        let content = r#"
1327Adp	|Off_t	|PerlIO_tell	|NN PerlIO *f
1328Adp	|Size_t	|PerlIO_read	|NN PerlIO *f	|NN void *buf	|Size_t count
1329Adm	|STDCHAR *	|SvPVX	|NN SV *sv
1330"#;
1331        let mut dict = ApidocDict::parse_embed_fnc_str(content);
1332
1333        // マクロテーブルとinternerを作成
1334        let mut interner = crate::intern::StringInterner::new();
1335        let mut macro_table = MacroTable::new();
1336        let loc = SourceLocation::default();
1337
1338        // Off_t → off_t
1339        let off_t_name = interner.intern("Off_t");
1340        let off_t_body = vec![Token::new(TokenKind::Ident(interner.intern("off_t")), loc.clone())];
1341        macro_table.define(MacroDef::object(off_t_name, off_t_body, loc.clone()), &interner);
1342
1343        // Size_t → size_t
1344        let size_t_name = interner.intern("Size_t");
1345        let size_t_body = vec![Token::new(TokenKind::Ident(interner.intern("size_t")), loc.clone())];
1346        macro_table.define(MacroDef::object(size_t_name, size_t_body, loc.clone()), &interner);
1347
1348        // STDCHAR → char
1349        let stdchar_name = interner.intern("STDCHAR");
1350        let stdchar_body = vec![Token::new(TokenKind::Ident(interner.intern("char")), loc.clone())];
1351        macro_table.define(MacroDef::object(stdchar_name, stdchar_body, loc), &interner);
1352
1353        // 展開を実行
1354        dict.expand_type_macros(&macro_table, &interner);
1355
1356        // 検証: Off_t → off_t
1357        let tell = dict.get("PerlIO_tell").unwrap();
1358        assert_eq!(tell.return_type, Some("off_t".to_string()));
1359
1360        // 検証: Size_t → size_t (戻り値)
1361        let read = dict.get("PerlIO_read").unwrap();
1362        assert_eq!(read.return_type, Some("size_t".to_string()));
1363
1364        // 検証: Size_t → size_t (引数)
1365        assert_eq!(read.args[2].ty, "size_t");
1366
1367        // 検証: STDCHAR * → char *
1368        let svpvx = dict.get("SvPVX").unwrap();
1369        assert_eq!(svpvx.return_type, Some("char *".to_string()));
1370    }
1371
1372    #[test]
1373    fn test_expand_type_string_preserves_non_macros() {
1374        use crate::macro_def::MacroDef;
1375        use crate::source::SourceLocation;
1376        use crate::token::{Token, TokenKind};
1377
1378        let mut interner = crate::intern::StringInterner::new();
1379        let mut macro_table = MacroTable::new();
1380        let loc = SourceLocation::default();
1381
1382        // Off_t → off_t のみ定義
1383        let off_t_name = interner.intern("Off_t");
1384        let off_t_body = vec![Token::new(TokenKind::Ident(interner.intern("off_t")), loc.clone())];
1385        macro_table.define(MacroDef::object(off_t_name, off_t_body, loc), &interner);
1386
1387        // SV * はマクロではないのでそのまま
1388        let result = super::expand_type_string("SV *", &macro_table, &interner);
1389        assert_eq!(result, "SV *");
1390
1391        // const char * もそのまま
1392        let result = super::expand_type_string("const char *", &macro_table, &interner);
1393        assert_eq!(result, "const char *");
1394
1395        // Off_t * は展開される
1396        let result = super::expand_type_string("Off_t *", &macro_table, &interner);
1397        assert_eq!(result, "off_t *");
1398
1399        // const Off_t * は展開される
1400        let result = super::expand_type_string("const Off_t *", &macro_table, &interner);
1401        assert_eq!(result, "const off_t *");
1402    }
1403}