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}