import type { AttributeBlock, AttributeFacts, AttributeItem, CfgPredicate, ClassBlock, CodeBlock, EnumBlock, FunctionBlock, ImplBlock, ImportBlock, Inline, ModuleBlock, PropertyItem, PropertyRow, SourceSpan, TokenRole, TraitBlock } from '../contract' import type { RustVisibility } from './rustTree' import { bodyFunctions, docSentence, isMethod, matchArms, oneLine, returnText, signatureParams, structFields, variantPayload } from './rustTree' /** 把类型、名字画成原文。翻译插件传入时,名字带源码下标。 */ export interface RustSay { name: (node: import('../contract').Node, role: TokenRole) => Inline[] source: (node: import('../contract').Node, role?: TokenRole) => Inline[] } const VISIBILITY: Record = { public: '公开', module: '本模块', super: '父模块', crate: '本 crate', } /** 块上的可见性:属性盖过 facts,没写是本模块。 */ export function visibilityWord(block: CodeBlock): string { const overridden = block.attributes?.access if (overridden && overridden in VISIBILITY) return VISIBILITY[overridden as RustVisibility] if ('facts' in block && 'access' in block.facts && block.facts.access === 'public') return VISIBILITY.public return VISIBILITY.module } export function importRows(block: ImportBlock, say: RustSay): PropertyRow[] { const [source] = block.facts.sources return [ { label: '来源', value: source ? say.source(source, 'type') : [hint('—')] }, { label: '导入项', value: [hint(`${block.facts.items.length} 个`)], columns: ['名字', '别名'], items: block.facts.items.map((item) => { if (item.kind === 'namespace') return { cells: [[hint('*')], [hint('—')]] } if (item.kind !== 'named') return { cells: [[hint('—')], [hint('—')]] } return { cells: [say.name(item.name, 'type'), item.alias ? say.name(item.alias, 'type') : [hint('—')]], defines: range(item.name), } }), }, ] } export function structRows(block: ClassBlock, say: RustSay): PropertyRow[] { const fields = structFields(block.node) return [ nameRow(block.facts.name, say, 'type'), ...docRow(block.facts.doc), visibilityRow(block), ...tableRow('字段', ['字段', '类型', '可见性'], fields.map(field => ({ cells: [ say.name(field.name, 'variable'), say.source(field.type, 'type'), [keyword(VISIBILITY[field.visibility])], ], defines: range(field.name), }))), ] } export function moduleRows(block: ModuleBlock, say: RustSay): PropertyRow[] { return [ nameRow(block.facts.name, say, 'variable'), ...docRow(block.facts.doc), visibilityRow(block), ] } const LINT_LEVELS: Record = { allow: '放行', warn: '警告', deny: '报错', forbid: '强制报错(内层不能再放宽)' } /** * 编译属性 `#![…]`:属性(照原文着色)/ 生效条件(只有 cfg_attr)/ 作用范围 / 说明(认得的属性才有)。 * cfg_attr 里写了几个属性时「属性」是一张「属性 | 说明」表,不再另起「说明」一行。 * original 把源码的一段照原文着色;crateRoot 是这份文件是不是 crate 根文件,不知道文件名时没有。 */ export function attributeRows(block: AttributeBlock, original: (span: SourceSpan) => Inline[], crateRoot: boolean | undefined): PropertyRow[] { const { attributes, condition, placement } = block.facts const single = attributes.length === 1 ? attributes[0] : undefined const description = single && attributeNote(single) return [ single ? { label: '属性', value: original(single.span) } : { label: '属性', value: [hint(`${attributes.length} 个`)], columns: ['属性', '说明'], items: attributes.map(item => ({ cells: [original(item.span), attributeNote(item) ?? [hint('—')]] })), }, ...(condition ? [{ label: '生效条件', value: cfgText(condition, false) }] : []), { label: '作用范围', value: [keyword(attributeScope(placement, crateRoot))] }, ...(description ? [{ label: '说明', value: description }] : []), ] } function attributeScope(placement: AttributeFacts['placement'], crateRoot: boolean | undefined): string { switch (placement) { case 'file': return crateRoot === undefined ? '所在模块' : crateRoot ? '整个 crate' : '本模块' case 'module': return '本模块' case 'impl': return '本 impl 块' default: return '所在模块' } } /** 认得的属性的书面说明;认不得的没有。 */ function attributeNote(item: AttributeItem): Inline[] | undefined { const names = item.arguments.flatMap((name, i) => [...(i > 0 ? [note('、')] : []), { text: name, role: 'variable' as const }]) const level = LINT_LEVELS[item.name] if (level) return names.length > 0 ? [note(`编译检查(lint)级别设为${level}:`), ...names] : undefined switch (item.name) { case 'windows_subsystem': if (item.value === 'windows') return [note('在 Windows 上编译为图形界面程序(windows 子系统),启动时不弹出控制台窗口')] return item.value === 'console' ? [note('在 Windows 上编译为控制台程序(console 子系统),启动时打开控制台窗口')] : undefined case 'feature': return names.length > 0 ? [note('启用实验特性(需 nightly 编译器):'), ...names] : undefined case 'no_std': return [note('不链接标准库 std,只使用核心库 core')] case 'no_main': return [note('不使用标准的 main 入口函数,程序入口另行提供')] case 'recursion_limit': return item.value ? [note(`宏展开等编译期递归的深度上限设为 ${item.value}(默认 128)`)] : undefined default: return undefined } } const CFG_OPTIONS: Record = { debug_assertions: '调试构建', test: '测试构建' } const CFG_VALUES: Record = { target_os: '目标系统', feature: '启用特性' } /** cfg 条件读成中文:not 是「非」,all 用「并且」、any 用「或者」连起来,套在别的条件里的加括号;认不得的照原文。 */ function cfgText(predicate: CfgPredicate, nested: boolean): Inline[] { switch (predicate.kind) { case 'not': return [operator('非'), plain(' '), ...cfgText(predicate.operand, true)] case 'all': case 'any': { if (predicate.operands.length === 0) return [hint(predicate.kind === 'all' ? '总是成立' : '从不成立')] const joiner = operator(predicate.kind === 'all' ? '并且' : '或者') const parts = predicate.operands.flatMap((operand, i) => [...(i > 0 ? [plain(' '), joiner, plain(' ')] : []), ...cfgText(operand, true)]) return nested && predicate.operands.length > 1 ? [plain('('), ...parts, plain(')')] : parts } case 'option': { const { name, value } = predicate if (value === undefined) return [plain(CFG_OPTIONS[name] ?? name)] const label = CFG_VALUES[name] return label ? [plain(`${label} `), { text: value, role: 'string' }] : [plain(`${name} = `), { text: `"${value}"`, role: 'string' }] } case 'other': return [plain(predicate.text)] } } export function enumRows(block: EnumBlock, say: RustSay): PropertyRow[] { return [ nameRow(block.facts.name, say, 'type'), ...docRow(block.facts.doc), ...tableRow('变体', ['变体', '携带的数据'], block.facts.members.map(member => ({ cells: [say.name(member.name, 'type'), [hint(variantPayload(member.value) ?? '—')]], defines: range(member.name), }))), ] } export function functionRows(block: FunctionBlock, say: RustSay): PropertyRow[] { const arms = matchArms(block.node) return [ nameRow(block.facts.name, say, 'function'), ...docRow(block.facts.doc), ...(block.facts.async ? [{ label: '异步', value: [keyword('是')] }] : []), visibilityRow(block), ...tableRow(isMethod(block.node) ? '参数' : '输入参数', ['名称', '类型'], block.facts.parameters.map(param => ({ cells: [say.name(param.name, 'variable'), param.type ? say.source(param.type, 'type') : [hint('—')]], defines: range(param.name), }))), { label: '返回', value: [plain(returnText(block.node))] }, ...tableRow('分情况', ['分支', '做什么'], arms.map(arm => ({ cells: [[plain(arm.pattern)], [hint(arm.does)]], })), '支'), ] } export function traitRows(block: TraitBlock, say: RustSay): PropertyRow[] { const methods = bodyFunctions(block.node) return [ nameRow(block.facts.name, say, 'type'), ...docRow(block.facts.doc), ...tableRow('方法', ['名称', '参数', '返回'], methods.map((method) => { const name = method.childForFieldName('name') return { cells: [ name ? say.name(name, 'function') : [hint('匿名')], [plain(signatureParams(method))], [plain(returnText(method))], ], ...(name && { defines: range(name) }), } })), ] } export function implRows(block: ImplBlock, say: RustSay): PropertyRow[] { const methods = block.children.filter((child): child is FunctionBlock => child.kind === 'function') const title: Inline[] = block.facts.name ? [...say.name(block.facts.name, 'type'), plain(' 的方法')] : [hint('实现')] return [ { label: '名称', value: title, ...(block.facts.name && { defines: range(block.facts.name) }) }, ...docRow(block.facts.doc), ...tableRow('方法', ['名称', '参数', '返回'], methods.map((method) => { const name = method.facts.name return { cells: [ name ? say.name(name, 'function') : [hint('匿名')], [plain(signatureParams(method.node))], [plain(returnText(method.node))], ], ...(name && { defines: range(name) }), } })), ] } function nameRow(name: import('../contract').Node | undefined, say: RustSay, role: TokenRole): PropertyRow { return { label: '名称', value: name ? say.name(name, role) : [hint('匿名')], ...(name && { defines: range(name) }), } } function docRow(doc: import('../contract').Node | undefined): PropertyRow[] { const sentence = docSentence(doc) return sentence ? [{ label: '说明', value: { markdown: sentence } }] : [] } function visibilityRow(block: CodeBlock): PropertyRow { return { label: '可见性', value: [keyword(visibilityWord(block))] } } /** 没有行就不占一行。countUnit 默认「个」,分情况用「支」。 */ function tableRow(label: string, columns: string[], items: PropertyItem[], countUnit = '个'): PropertyRow[] { if (items.length === 0) return [] return [{ label, value: [hint(`${items.length} ${countUnit}`)], columns, items }] } function hint(text: string): Inline { return { text, role: 'hint' } } function plain(text: string): Inline { return { text, role: 'plain' } } function keyword(text: string): Inline { return { text, role: 'keyword' } } function operator(text: string): Inline { return { text, role: 'operator' } } function note(text: string): Inline { return { text, role: 'comment' } } function range(node: import('../contract').Node): { startIndex: number, endIndex: number } { return { startIndex: node.startIndex, endIndex: node.endIndex } } /** 名字、类型照抄源码,名字带上下标,点了能跳到定义。 */ export function plainSay(): RustSay { return { name: (node, role) => [{ text: node.text, role, offset: node.startIndex }], source: (node, role = 'type') => [{ text: oneLine(node.text), role, offset: node.startIndex }], } }