Files
superwork/vscode/src/codeReader/rules/rustRows.ts
T

290 lines
12 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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<RustVisibility, string> = {
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<string, string> = { 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<string, string> = { debug_assertions: '调试构建', test: '测试构建' }
const CFG_VALUES: Record<string, string> = { 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 }],
}
}