import type { AttributeNames, CodeBlock, ImportFacts, Inline, LibraryRule, Node, PropertyItem, PropertyRow, RuleBlock, RuleContext, RuleLocation, RuleReference, RuleWorkspace, Segment } from './contract' /** 宿主这边怎么用库规则:什么时候启用、问哪个规则、规则出错时怎么记。规则本身只经 contract 拿信息。 */ /** 这份源码里各处导入的 facts,照源码顺序(先父块、再子块)。 */ export function importsIn(blocks: readonly CodeBlock[]): ImportFacts[] { const found: ImportFacts[] = [] const visit = (list: readonly CodeBlock[]): void => { for (const block of list) { if (block.kind === 'import') found.push(block.facts) visit(block.children) } } visit(blocks) return found } /** 来源模块的写法去掉首尾引号(TypeScript 的来源是字符串字面量)。 */ function moduleName(source: Node): string { return source.text.replace(/^(['"`])([\s\S]*)\1$/, '$2') } /** 模块是这个库或它的子模块:等于库名,或以「库名.」(Python 的点分模块名)「库名/」(JavaScript 的子路径)开头。 */ export function fromLibrary(module: string, library: string): boolean { return module === library || module.startsWith(`${library}.`) || module.startsWith(`${library}/`) || module.startsWith(`${library}::`) } /** 这份源码启用的规则,保持注册顺序:导入了规则关心的某个库,或规则不看导入(libraries 是空的)。 */ export function activeRules(rules: readonly LibraryRule[], imports: readonly ImportFacts[]): LibraryRule[] { const modules = imports.flatMap(facts => facts.sources.map(moduleName)) return rules.filter(rule => rule.libraries.length === 0 || rule.libraries.some(library => modules.some(module => fromLibrary(module, library)))) } /** 一块被哪个规则认领、替身是什么。 */ export interface Claim { rule: LibraryRule block: RuleBlock } /** 替身不合 RuleBlock 的约定时说哪儿不对。 */ function problemOf(rule: LibraryRule, block: CodeBlock, claimed: RuleBlock): string | undefined { if ('omit' in claimed) return block.children.length > 0 ? '要略去的块还有子块' : block.comments ? '要略去的块挂着注释' : undefined if (claimed.absorbs.some(child => !block.children.includes(child))) return '要并进来的块不是这块的子块' if ('as' in claimed) return undefined if (!claimed.kind.startsWith(`${rule.id}.`)) return `种类 id「${claimed.kind}」没以「${rule.id}.」开头` const slots = claimed.slots ?? [] if (slots.some(slot => !block.children.includes(slot.block) || claimed.absorbs.includes(slot.block))) return '挂在子行上的块不是留下的子块' if (new Set(slots.map(slot => slot.block)).size < slots.length) return '同一块挂在了卡片里的几处' for (const { row, item } of slots) { if (!claimed.rows[row]) return `挂在卡片第 ${row} 行的块,卡片没有这一行` if (!claimed.rows[row].items?.[item]) return `挂在卡片第 ${row} 行第 ${item} 个子行的块,这一行没有这个子行` } for (const { reference, offset } of referencedSegments(claimed.rows)) { if (reference?.rule !== rule.id) return `引用「${reference?.key}」标的规则是「${reference?.rule}」,不是自己` if (!rule.resolve) return `标了引用「${reference.key}」却没给 resolve` if (offset === undefined) return `引用「${reference.key}」没带源码下标` } return undefined } /** * 按注册顺序问启用的规则,第一个认领的算。规则抛错、给的替身不合约定时交给 report(记日志),当它没认、接着问下一个; * 都没认领就是 undefined,这块照常显示。 */ export function claimBlock(block: CodeBlock, rules: readonly LibraryRule[], context: RuleContext, report: (message: string) => void): Claim | undefined { const where = `第 ${block.header.startRow + 1} 行的 ${block.kind} 块` for (const rule of rules) { let claimed: RuleBlock | undefined try { claimed = rule.claim(block, context) } catch (err) { report(`库规则 ${rule.id} 看${where}时出错,这块照常显示:${err instanceof Error ? err.message : String(err)}`) continue } if (!claimed) continue const problem = problemOf(rule, block, claimed) if (problem) { report(`库规则 ${rule.id} 给${where}的替身不合约定,这块照常显示:${problem}`) continue } return { rule, block: claimed } } return undefined } /** 各规则给自己的筛选属性起的名字(LibraryRule.attributeNames),按注册顺序;规则抛错时交给 report(记日志),当它没给。 */ export function ruleAttributeNames(rules: readonly LibraryRule[], displayLanguage: string | undefined, report: (message: string) => void): AttributeNames[] { return rules.flatMap((rule) => { try { return rule.attributeNames ? [rule.attributeNames(displayLanguage)] : [] } catch (err) { report(`库规则 ${rule.id} 给筛选属性起名时出错,筛选栏照写 id:${err instanceof Error ? err.message : String(err)}`) return [] } }) } /** 文字里(连框里)带引用的段。 */ function referencesInText(pieces: readonly Inline[]): Segment[] { return pieces.flatMap(piece => 'role' in piece ? (piece.reference ? [piece] : []) : referencesInText(piece.segments)) } function referencesInItems(items: readonly PropertyItem[] | undefined): Segment[] { return (items ?? []).flatMap(item => [...item.cells.flatMap(cell => 'markdown' in cell ? [] : referencesInText(cell)), ...referencesInItems(item.items)]) } /** 属性表里(值、各层子行的格子)带引用的段。 */ export function referencedSegments(rows: readonly PropertyRow[]): Segment[] { return rows.flatMap(row => [...('markdown' in row.value ? [] : referencesInText(row.value)), ...referencesInItems(row.items)]) } /** * 查规则引用的定义:交给标它的那个规则的 resolve。规则已经注销、没给 resolve、查的时候出错,都记日志、当作没有定义, * 预览照常不动。 */ export async function resolveReference(reference: RuleReference, rules: readonly LibraryRule[], workspace: RuleWorkspace, report: (message: string) => void): Promise { const rule = rules.find(candidate => candidate.id === reference.rule) if (!rule?.resolve) { report(`没有能查引用「${reference.key}」的库规则 ${reference.rule}`) return [] } try { return await rule.resolve(reference.key, workspace) } catch (err) { report(`库规则 ${rule.id} 查「${reference.key}」的定义时出错:${err instanceof Error ? err.message : String(err)}`) return [] } }