Skip to main content

nichlink_macro/
lib.rs

1//! Face field normalisation for the authoring macros.
2//! 为作者侧宏归一化注册面字段。
3//!
4//! A `macro_rules!` matcher can only fail with "no rules expected `...`", and it
5//! cannot reorder fields, cannot compare field names, and cannot attach a span
6//! to a message of its own. This front end receives the author's tokens with
7//! their spans, so it accepts `,` or `;` separators, a forgotten separator and
8//! any field order, and still reports an unknown or repeated field on the exact
9//! token that carries it.
10//! `macro_rules!` 匹配失败只会说 "no rules expected `...`",既不能重排字段、不能
11//! 比较字段名,也无法把自己的消息挂到具体 token 上。本前端拿到的是带 span 的作者
12//! token,因此接受 `,`/`;`、漏写分隔符与任意顺序,并把"未知字段/重复字段"精确报在
13//! 出问题的那个 token 上。
14//!
15//! Splitting is shared with the build-time reader (`split_face_fields`): the
16//! compiler and the build step must read one declaration the same way, so they
17//! use the same tolerant splitter.
18//! 切分逻辑与构建期读取器共用(`split_face_fields`):编译器与构建步骤必须对同一份
19//! 声明读出一致的字段,因此两者使用同一个宽容切分器。
20//!
21//! The normalised declaration goes back through `__nichlink_object!`, the
22//! exported entry point of the runtime crate, so the collector mode the caller
23//! chose survives the round trip. This front end is only reached when
24//! `__control_object!`'s single arm has already declined the declaration — a
25//! field out of order, `;` separators, a misspelled name — so a well-formed face
26//! expands exactly as it did before.
27//! 归一化后的声明经 `__nichlink_object!`(运行时 crate 的公开入口)回到宏阶梯,
28//! 调用方选择的 collector 模式因此得以保留。只有当 `__control_object!` 那唯一一条 arm
29//! 不接受时(字段顺序不同、用 `;` 分隔、字段名拼错)才会走到本前端,因此合法注册面的展开
30//! 与从前完全一致。
31//!
32//! The proc-macro entry points plus `normalise` stay on this page; the mirror
33//! emitter lives in `mirror` and the field/argument parsing helpers in
34//! `front_end`.
35//! 过程宏入口与 `normalise` 留在本页;镜像发射器位于 `mirror`,字段 / 参数解析辅助
36//! 函数位于 `front_end`。
37
38// The published surface must be readable on docs.rs without leaving the page,
39// so the lint is on for the whole crate; `clippy -D warnings` makes a new
40// undocumented public item a failure.
41// 发布表面必须能在 docs.rs 上不跳页读懂,因此 lint 开在整个 crate 上;
42// `clippy -D warnings` 会让新增的、没有文档的公开项变成失败。
43#![warn(missing_docs)]
44
45use proc_macro::TokenStream;
46use proc_macro2::{Delimiter, Group, Ident, Spacing, Span, TokenStream as Tokens, TokenTree};
47
48use nichlink::lexicon;
49
50use nichlink::registry_core::declaration::FACE_FIELD_ORDER;
51use nichlink::registry_core::syntax::split_face_fields;
52
53use crate::front_end::{error_at, render, splice, split_mirror_fields, split_semicolons};
54use crate::mirror::{Field, mirror_item, punct};
55
56#[path = "front_end.rs"]
57mod front_end;
58#[path = "mirror.rs"]
59mod mirror;
60
61/// Rewrite one face declaration's field list into its accepted order, with
62/// tolerant separators and spanned diagnostics.
63/// 把一条注册面声明的字段列表改写成它接受的顺序,容忍分隔符差异并给出带 span 的诊断。
64///
65/// The macro is the compile-time front end of the kernel's face grammar: it
66/// parses the same tokens the authoring parser does, so the compiler and the
67/// tooling reject exactly the same declarations.
68/// 本宏是内核注册面语法的编译期前端:它解析与创作解析器相同的 token,因此编译器与工具
69/// 拒绝的是完全相同的声明。
70#[proc_macro]
71pub fn face_fields(input: TokenStream) -> TokenStream {
72    normalise(Tokens::from(input))
73        .unwrap_or_else(|error| error)
74        .into()
75}
76
77/// The `registry_rule` a face gets when it does not write one.
78/// 注册面没有写 `registry_rule` 时得到什么。
79///
80/// The input is `<fallback> ; <needs_registry> ; <author expression?>`. The
81/// author's expression always wins. A face that owns a registry
82/// (`needs_registry: true`) takes the **canonical sibling rule** — the rule
83/// module the authoring layout keeps beside the face folder — and every other
84/// face keeps the permissive fallback the declarative layer passes in.
85/// 输入是 `<fallback> ; <needs_registry> ; <作者表达式?>`。作者写下的表达式永远最优先。
86/// 拥有注册机的面(`needs_registry: true`)取**同目录规范规则**——创作布局放在注册面
87/// 目录旁的那个规则模块——其余注册面保留声明层传进来的宽松默认值。
88///
89/// The path is relative (`super::registry_rule`) on purpose, and that is why this
90/// has to be a proc macro: a folder face file is loaded as the inner module of
91/// its folder, so the rule module is its *sibling*, and `module_path!()` is a
92/// string that cannot become a path. Tokens built here carry `Span::call_site()`,
93/// so the relative path resolves in the author's own module.
94/// 路径是相对的(`super::registry_rule`)而不是绝对的,这也正是它必须是过程宏的原因:
95/// 文件夹注册面文件被载入为其文件夹的内层模块,因此规则模块是它的**兄弟**;而
96/// `module_path!()` 是字符串,无法变成路径。这里生成的 token 带 `Span::call_site()`,
97/// 因此相对路径在作者自己的模块里解析。
98#[proc_macro]
99pub fn face_rule_or(input: TokenStream) -> TokenStream {
100    let mut parts = split_semicolons(Tokens::from(input)).into_iter();
101    let fallback = parts.next().unwrap_or_default();
102    let needs_registry = parts.next().unwrap_or_default();
103    let authored = parts.next().unwrap_or_default();
104    if !authored.is_empty() {
105        return authored.into();
106    }
107    let owns_registry = needs_registry
108        .into_iter()
109        .map(|token| token.to_string())
110        .collect::<String>();
111    if owns_registry == "true" {
112        // The canonical sibling path is relative, and the IDE's view of a nested
113        // face is a crate-root shadow — rust-analyzer applies `#[path]` only at
114        // the top level, which is why the shadow exists at all — so `super` is
115        // the crate root there and no `registry_rule` module is in scope. A block
116        // that picks the real path for `rustc` and the caller's fallback for the
117        // IDE keeps the mirror type-correct without changing what `rustc`
118        // compiles; the fallback tokens are spliced in verbatim so their
119        // `$crate` keeps the hygiene the caller gave it.
120        // 规范的同目录路径是相对路径,而嵌套面在 IDE 眼里的视图是 crate 根影子——
121        // rust-analyzer 只在顶层应用 `#[path]`,影子正因此存在——那里 `super` 就是 crate
122        // 根,作用域内没有 `registry_rule` 模块。用一个块为 `rustc` 选真实路径、为 IDE 选
123        // 调用方的兜底值,即可让镜像保持类型正确,同时不改变 `rustc` 编译的东西;兜底 token
124        // 原样拼接,因此它的 `$crate` 保留调用方给的卫生性。
125        // The template parses as a whole; the caller's fallback is spliced in for
126        // the placeholder, so nothing about its tokens is rewritten.
127        // 模板整体可解析;调用方的兜底值被拼进占位符的位置,因此它的 token 一个字节都没有被
128        // 改写。
129        // `let` rather than `use`: the fallback is a struct's associated constant,
130        // and `use` cannot import one. A `let` binding in a const initializer needs
131        // no type annotation, which is what keeps this template free of a type the
132        // resolver would have to name.
133        // 用 `let` 而不是 `use`:兜底值是结构体的关联常量,而 `use` 导不进来。const 初始化
134        // 器里的 `let` 绑定不需要类型标注,这正是让模板不必说出一个解析器无从命名的类型的原因。
135        let template: Tokens = "{ #[cfg(not(rust_analyzer))] let __nichlink_rule = super::registry_rule::REGISTRATION_RULE; #[cfg(rust_analyzer)] let __nichlink_rule = __NICHLINK_IDE_FALLBACK; __nichlink_rule }"
136            .parse()
137            .expect("the derived rule template is static");
138        return splice(template, "__NICHLINK_IDE_FALLBACK", &fallback).into();
139    }
140    fallback.into()
141}
142
143/// Labels for the traits a face declares, derived from the paths it wrote.
144/// 注册面声明实现的 trait 标签,从其写下的路径派生。
145///
146/// `face_trait_labels_or!([path, …]; [label, …])` answers with the last path
147/// segment of every path when at least one path was given, and with the labels
148/// verbatim when none was. That is the same rule the authoring applier
149/// (`apply_trait_contract`) already implements, so the file form and the compiled
150/// form cannot answer "which interfaces does this face implement" differently —
151/// and a face that states a compiler-checked path never has to state the label
152/// twice.
153/// `face_trait_labels_or!([路径, …]; [标签, …])`:只要给出至少一个路径,就用每个路径的
154/// 最后一段作答;一个路径都没有时,原样交回标签。这与创作应用器
155/// (`apply_trait_contract`)已经实现的规则相同,因此文件形式与编译形式对"本注册面实现了
156/// 哪些接口"不可能给出不同答案——写了参与编译检查的路径的注册面也不必再写一遍标签。
157#[proc_macro]
158pub fn face_trait_labels_or(input: TokenStream) -> TokenStream {
159    let mut parts = split_semicolons(Tokens::from(input)).into_iter();
160    let paths = parts.next().unwrap_or_default();
161    let labels = parts.next().unwrap_or_default();
162    let derived = bracket_items(&paths)
163        .and_then(|items| items.iter().map(last_segment).collect::<Option<Vec<_>>>())
164        .filter(|names| !names.is_empty());
165    let Some(derived) = derived else {
166        // No compiler-checked path: the author's labels stand as written, which
167        // is what `__string_list!` produced before this macro existed.
168        // 没有参与编译检查的路径:作者的标签原样成立,这正是本宏出现之前
169        // `__string_list!` 产出的东西。
170        let fallback = format!("&{labels}");
171        return fallback
172            .parse::<Tokens>()
173            .map_or_else(|_| labels.into(), TokenStream::from);
174    };
175    let literal = format!(
176        "&[{}]",
177        derived
178            .iter()
179            .map(|name| format!("{name:?}"))
180            .collect::<Vec<_>>()
181            .join(", ")
182    );
183    literal
184        .parse::<Tokens>()
185        .map_or_else(|_| labels.into(), TokenStream::from)
186}
187
188/// The comma-separated items of the first bracketed group, if there is one.
189/// 第一个方括号分组里以逗号分隔的条目(若有)。
190fn bracket_items(tokens: &Tokens) -> Option<Vec<Tokens>> {
191    let group = tokens.clone().into_iter().find_map(|token| match token {
192        TokenTree::Group(group) if group.delimiter() == Delimiter::Bracket => Some(group),
193        _ => None,
194    })?;
195    let mut items = vec![Tokens::new()];
196    for token in group.stream() {
197        let separator = matches!(&token, TokenTree::Punct(punct) if punct.as_char() == ',');
198        if separator {
199            items.push(Tokens::new());
200        } else {
201            items.last_mut()?.extend([token]);
202        }
203    }
204    Some(items.into_iter().filter(|item| !item.is_empty()).collect())
205}
206
207/// The last `::`-separated segment of a path, when it is a plain identifier.
208/// 路径最后一段 `::` 之后的名字,且它必须是普通标识符。
209fn last_segment(path: &Tokens) -> Option<String> {
210    let text = path.to_string().replace(' ', "");
211    let name = text.rsplit("::").next()?.to_owned();
212    let mut characters = name.chars();
213    let first = characters.next()?;
214    if !(first.is_alphabetic() || first == '_')
215        || !characters.all(|c| c.is_alphanumeric() || c == '_')
216    {
217        return None;
218    }
219    Some(name)
220}
221
222/// Which macro the normalised declaration goes back to.
223/// 归一化后的声明要回到哪个宏。
224#[derive(Clone, Copy, PartialEq, Eq)]
225enum Target {
226    /// The generated host aliases' path.
227    /// 生成的宿主别名那条路。
228    Control,
229    /// `external_object!`, whose matcher names the source file first.
230    /// `external_object!`——它的 matcher 首要指出源文件。
231    External,
232}
233
234pub(crate) fn normalise(input: Tokens) -> Result<Tokens, Tokens> {
235    let mut list = input.into_iter().collect::<Vec<_>>();
236    let mut target = Target::Control;
237    if let (Some(TokenTree::Punct(at)), Some(TokenTree::Ident(name))) = (list.first(), list.get(1))
238        && at.as_char() == '@'
239    {
240        target = match name.to_string().as_str() {
241            "control" => Target::Control,
242            "external" => Target::External,
243            other => {
244                return Err(error_at(
245                    name.span(),
246                    format!("unknown face field target `{other}`"),
247                ));
248            }
249        };
250        list.drain(0..2);
251    }
252    let input = list.into_iter().collect::<Tokens>();
253    let original = input.to_string();
254    let mut collector: Option<TokenTree> = None;
255    let mut fields: Vec<Field> = Vec::new();
256    for field in split_face_fields(input) {
257        let tokens = field.into_iter().collect::<Vec<_>>();
258        let Some(TokenTree::Ident(name)) = tokens.first().cloned() else {
259            let span = tokens.first().map_or_else(Span::call_site, TokenTree::span);
260            return Err(error_at(
261                span,
262                format!("expected a face field name, found `{}`", render(&tokens)),
263            ));
264        };
265        match tokens.get(1) {
266            Some(TokenTree::Punct(colon)) if colon.as_char() == ':' => {}
267            _ => {
268                return Err(error_at(
269                    name.span(),
270                    format!("expected `:` after face field `{name}`"),
271                ));
272            }
273        }
274        if tokens.len() == 2 {
275            return Err(error_at(
276                name.span(),
277                format!("face field `{name}` has no value"),
278            ));
279        }
280        if name == "source" && target == Target::Control {
281            return Err(error_at(
282                name.span(),
283                "`source` belongs to `external_object!`; a generated host alias                  records the file itself"
284                    .to_owned(),
285            ));
286        }
287        if name == lexicon::FACE_FIELD_COLLECTOR {
288            if collector.is_some() {
289                return Err(error_at(
290                    name.span(),
291                    "face field `collector` is given twice".to_owned(),
292                ));
293            }
294            collector = Some(tokens[2].clone());
295            continue;
296        }
297        fields.push(Field {
298            key: name.to_string(),
299            span: name.span(),
300            value_span: tokens.get(1).map_or_else(|| name.span(), TokenTree::span),
301            tokens: tokens.into_iter().collect(),
302        });
303    }
304
305    let position = FACE_FIELD_ORDER
306        .iter()
307        .enumerate()
308        .map(|(index, key)| (*key, index))
309        .collect::<std::collections::BTreeMap<_, _>>();
310    let mut seen: Vec<&str> = Vec::with_capacity(fields.len());
311    for field in &fields {
312        if !position.contains_key(field.key.as_str()) {
313            return Err(error_at(
314                field.span,
315                format!(
316                    "unknown face field `{}`; expected one of: {}",
317                    field.key,
318                    FACE_FIELD_ORDER.join(", ")
319                ),
320            ));
321        }
322        if seen.contains(&field.key.as_str()) {
323            return Err(error_at(
324                field.span,
325                format!("face field `{}` is given twice", field.key),
326            ));
327        }
328        seen.push(&field.key);
329    }
330    let Some(collector) = collector else {
331        return Err(error_at(
332            Span::call_site(),
333            "a face declaration must carry `collector:`".to_owned(),
334        ));
335    };
336    fields.sort_by_key(|field| position[field.key.as_str()]);
337
338    let mut body = Tokens::new();
339    body.extend([
340        TokenTree::Ident(Ident::new(lexicon::FACE_FIELD_COLLECTOR, Span::call_site())),
341        punct(':', Spacing::Alone),
342        collector,
343        punct(',', Spacing::Alone),
344    ]);
345    for field in &fields {
346        body.extend(field.tokens.clone());
347        body.extend([punct(',', Spacing::Alone)]);
348    }
349    if target == Target::Control && body.to_string() == original {
350        // Reordering and re-emitting produced this very token stream, so another
351        // round would only recurse: the declaration already is in the accepted
352        // order and still did not match, which means a field's shape is wrong.
353        // 重排与重发得到的正是这串 token,再走一轮只会递归:声明已在接受顺序上却仍未
354        // 匹配,说明某个字段的写法不对。
355        return Err(error_at(
356            Span::call_site(),
357            "these face fields are in the accepted order but the declaration still did not match; \
358             check each field's shape against the field list in the macro's documentation"
359                .to_owned(),
360        ));
361    }
362
363    let mut output = mirror_item(&fields);
364    output.extend([
365        punct(':', Spacing::Joint),
366        punct(':', Spacing::Alone),
367        TokenTree::Ident(Ident::new(lexicon::RUN_METHOD_CRATE, Span::call_site())),
368        punct(':', Spacing::Joint),
369        punct(':', Spacing::Alone),
370        TokenTree::Ident(Ident::new(
371            match target {
372                Target::Control => "__nichlink_object",
373                Target::External => "__external_object",
374            },
375            Span::call_site(),
376        )),
377        punct('!', Spacing::Alone),
378        TokenTree::Group(Group::new(Delimiter::Brace, body)),
379    ]);
380    Ok(output)
381}
382
383/// The editor-only field mirror for a declaration the caller already split.
384/// 为调用方已切分好的声明生成仅供编辑器的字段镜像。
385///
386/// The generated alias reaches this through `face_fields_mirror!`, because its
387/// token tree is opaque to an editor and the alias may be written with `;` or in
388/// any order. Unlike the full front end it tolerates a field the author has not
389/// finished (`kind:` with no value) and never re-dispatches to the runtime.
390/// 生成的别名通过 `face_fields_mirror!` 走到这里:它的 token 树对编辑器不透明,而且别名
391/// 可能用 `;` 或任意顺序书写。与完整前端不同,它容忍作者还没写完的字段(`kind:` 后面
392/// 还没有值),也绝不回派到运行期。
393#[proc_macro]
394pub fn face_fields_mirror(input: TokenStream) -> TokenStream {
395    match split_mirror_fields(Tokens::from(input)) {
396        Ok(fields) => mirror_item(&fields).into(),
397        // A declaration this cannot split is one the runtime ladder will report
398        // on its own; the mirror stays silent rather than adding a second error.
399        // 切分不了的声明由运行期阶梯自己报错;镜像保持沉默,不再添一个错误。
400        Err(_) => Tokens::new().into(),
401    }
402}