♻️ refactor(vscode): 代码阅读的翻译插件只经契约拿语言插件的词性和推导式结构
This commit is contained in:
1 parent
07eaf96a6b
commit
202fa954ba
12 files changed
+185
-76
No files matched your search
@@ -1,4 +1,4 @@
|
||||
import type { Access, AttributeNames, BlockKind, ClassFacts, CommonAttribute, Defines, EntryFacts, FactBlock, FunctionFacts, ImportFacts, ImportItem, Inline, IterationClause, IterationFacts, IterationResult, Markdown, Node, PreviewText, PropertyItem, PropertyRow, Segment, SegmentGroup, SourceSpan, TextBlock, TokenRole, Translator, VariableBlock } from '../contract'
|
||||
import type { Access, AttributeNames, BlockKind, ClassFacts, CommonAttribute, Defines, EntryFacts, FactBlock, FunctionFacts, ImportFacts, ImportItem, Inline, IterationClause, IterationFacts, IterationResult, LanguageAdapter, Markdown, Node, PreviewText, PropertyItem, PropertyRow, Segment, SegmentGroup, SourceDocument, SourceSpan, TextBlock, TokenRole, Translator, VariableBlock } from '../contract'
|
||||
import { commonIndent, splitLines } from '../editText'
|
||||
|
||||
/** 按所在语法节点区分译法的关键字,例如 Python 的 else:if 后「否则」,for / while 后「未中断时」。 */
|
||||
@@ -20,12 +20,14 @@ export interface ContextualWord {
|
||||
export type TemplatePart = string | { field: string } | { namedChild: number }
|
||||
|
||||
/** 一种语言的中文直译表。引擎替换表里的关键字、内置类型、运算符,按节点模板重排少数语法结构,其余原样照抄。 */
|
||||
/**
|
||||
* 一种语言的中文词表:只有叫法和语法节点类型,语言知道的结构(词性、遍历)翻译时从 SourceDocument.language 取——
|
||||
* 未翻译的词、关键字译文按它给的词性着色,它认出的遍历整个换成一个框,框里按读的顺序重排(见 iterationGroup)。
|
||||
*/
|
||||
export interface ZhTable {
|
||||
id: string
|
||||
label: string
|
||||
languageIds: string[]
|
||||
/** 未翻译的词按源码着色的词性上色;关键字译文的词性也取它。 */
|
||||
tokenRole: (leaf: Node) => TokenRole
|
||||
/** 关键字:按语法树里匿名节点的类型查(Python 的 `not in` 这类组合关键字本身也是一个匿名节点);随所在语法节点换译法的写成 ContextualWord。 */
|
||||
keywords: Record<string, string | ContextualWord>
|
||||
/** 字面量关键字:具名的叶子节点(true / None …),按节点类型查。 */
|
||||
@@ -49,11 +51,6 @@ export interface ZhTable {
|
||||
* 槽位里的子节点照常递归翻译。模板引用的子节点缺了,或节点没整个落在要翻的这一段里,就不重排、照常翻。
|
||||
*/
|
||||
nodeTemplates: Record<string, TemplatePart[]>
|
||||
/**
|
||||
* 写在表达式里的遍历(推导式、生成器表达式)的结构,语言插件给;没有这种写法的语言不填。
|
||||
* 认出来的节点整个换成一个框,框里按读的顺序重排,见 iterationGroup。
|
||||
*/
|
||||
iteration?: (node: Node) => IterationFacts | undefined
|
||||
}
|
||||
|
||||
interface Token {
|
||||
@@ -73,7 +70,7 @@ interface Token {
|
||||
|
||||
/** 按译文顺序收集词:cursor 是上一个词在源码里的结尾,gap 是下一个词前面要不要空一格。 */
|
||||
interface Collector {
|
||||
source: string
|
||||
document: SourceDocument
|
||||
tokens: Token[]
|
||||
cursor: number
|
||||
gap: boolean
|
||||
@@ -184,13 +181,13 @@ export function createZhTemplateTranslator(table: ZhTable): Translator {
|
||||
}
|
||||
|
||||
/** 照抄这个节点时带不带源码下标:名字(按名字着色的具名叶子)和整段字符串带,标点、数字、关键字不带。 */
|
||||
function offsetOf(node: Node): number | undefined {
|
||||
const linkable = stringNodes.has(node.type) || (node.childCount === 0 && node.isNamed && NAME_ROLES.has(table.tokenRole(node)))
|
||||
function offsetOf(node: Node, language: LanguageAdapter): number | undefined {
|
||||
const linkable = stringNodes.has(node.type) || (node.childCount === 0 && node.isNamed && NAME_ROLES.has(language.tokenRole(node)))
|
||||
return linkable ? node.startIndex : undefined
|
||||
}
|
||||
|
||||
/** 能整个换成中文的节点 → 译文和词性。 */
|
||||
function translateNode(node: Node): { text: string, role: TokenRole } | undefined {
|
||||
function translateNode(node: Node, language: LanguageAdapter): { text: string, role: TokenRole } | undefined {
|
||||
if (node.childCount === 0 && table.isTypePosition(node)) {
|
||||
const type = table.builtinTypes[node.text]
|
||||
if (type)
|
||||
@@ -202,11 +199,11 @@ export function createZhTemplateTranslator(table: ZhTable): Translator {
|
||||
return { text: table.operators[node.type], role: 'operator' }
|
||||
const word = table.keywords[node.type]
|
||||
if (word !== undefined)
|
||||
return { text: keywordText(word, node), role: table.tokenRole(firstLeaf(node)) }
|
||||
return { text: keywordText(word, node), role: language.tokenRole(firstLeaf(node)) }
|
||||
return undefined
|
||||
}
|
||||
if (node.childCount === 0 && table.literals[node.type])
|
||||
return { text: table.literals[node.type], role: table.tokenRole(node) }
|
||||
return { text: table.literals[node.type], role: language.tokenRole(node) }
|
||||
if (table.emptyLiterals[node.type] && node.namedChildCount === 0)
|
||||
return { text: table.emptyLiterals[node.type], role: 'literal' }
|
||||
return undefined
|
||||
@@ -223,7 +220,7 @@ export function createZhTemplateTranslator(table: ZhTable): Translator {
|
||||
|
||||
/** 源码里上一个词到 index 之间有空白,下一个词前面就空一格。 */
|
||||
function advance(out: Collector, index: number): void {
|
||||
out.gap ||= /\s/.test(out.source.slice(out.cursor, index))
|
||||
out.gap ||= /\s/.test(out.document.source.slice(out.cursor, index))
|
||||
out.cursor = index
|
||||
}
|
||||
|
||||
@@ -239,10 +236,10 @@ export function createZhTemplateTranslator(table: ZhTable): Translator {
|
||||
}
|
||||
|
||||
/** 语言插件认出的写在表达式里的遍历;没整个落在要翻的这一段里就不重排、照常翻。 */
|
||||
function iterationIn(node: Node, span: SourceSpan): IterationFacts | undefined {
|
||||
if (!table.iteration || node.startIndex < span.startIndex || node.endIndex > span.endIndex)
|
||||
function iterationIn(node: Node, span: SourceSpan, language: LanguageAdapter): IterationFacts | undefined {
|
||||
if (!language.iteration || node.startIndex < span.startIndex || node.endIndex > span.endIndex)
|
||||
return undefined
|
||||
return table.iteration(node)
|
||||
return language.iteration(node)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -250,11 +247,11 @@ export function createZhTemplateTranslator(table: ZhTable): Translator {
|
||||
* 同步、异步换了就另起一句),条件在它所在的位置写「,仅当 <条件>」,最后「,取 <值>」,字典是「<键>: <值>」。
|
||||
* 来源、变量、条件、值照常翻,里面的名字带源码下标,套着的遍历是框里的框。
|
||||
*/
|
||||
function iterationGroup(facts: IterationFacts, span: SourceSpan, source: string): SegmentGroup {
|
||||
function iterationGroup(facts: IterationFacts, span: SourceSpan, document: SourceDocument): SegmentGroup {
|
||||
const segments: Inline[] = []
|
||||
const plain = (text: string): void => pushSegment(segments, text, 'plain')
|
||||
const word = (text: string): void => pushSegment(segments, text, 'keyword')
|
||||
const say = (node: Node): void => appendInline(segments, joinTokens(tokensIn(node, span, source)))
|
||||
const say = (node: Node): void => appendInline(segments, joinTokens(tokensIn(node, span, document)))
|
||||
let previous: IterationClause | undefined
|
||||
for (const clause of facts.clauses) {
|
||||
if (clause.kind === 'if') {
|
||||
@@ -295,12 +292,12 @@ export function createZhTemplateTranslator(table: ZhTable): Translator {
|
||||
/** 遍历整个换成一个框;和外面的调用共用的那对括号照常写在框外,紧贴着框。 */
|
||||
function emitIteration(out: Collector, node: Node, facts: IterationFacts, span: SourceSpan): void {
|
||||
const shared = facts.sharedParentheses
|
||||
const bracket = (paren: Node): Omit<Token, 'gap' | 'raw'> => ({ text: paren.text, role: table.tokenRole(paren), translated: false })
|
||||
const bracket = (paren: Node): Omit<Token, 'gap' | 'raw'> => ({ text: paren.text, role: out.document.language.tokenRole(paren), translated: false })
|
||||
if (shared)
|
||||
emitSource(out, shared.open, bracket(shared.open))
|
||||
else
|
||||
advance(out, node.startIndex)
|
||||
emit(out, { text: '', role: 'plain', translated: true, raw: '', group: iterationGroup(facts, span, out.source) })
|
||||
emit(out, { text: '', role: 'plain', translated: true, raw: '', group: iterationGroup(facts, span, out.document) })
|
||||
if (shared) {
|
||||
out.cursor = shared.close.startIndex
|
||||
emitSource(out, shared.close, bracket(shared.close))
|
||||
@@ -315,7 +312,8 @@ export function createZhTemplateTranslator(table: ZhTable): Translator {
|
||||
emitSource(out, node, { text: '', role: 'comment', translated: false })
|
||||
return
|
||||
}
|
||||
const iteration = iterationIn(node, span)
|
||||
const { language } = out.document
|
||||
const iteration = iterationIn(node, span, language)
|
||||
if (iteration) {
|
||||
emitIteration(out, node, iteration, span)
|
||||
return
|
||||
@@ -335,14 +333,14 @@ export function createZhTemplateTranslator(table: ZhTable): Translator {
|
||||
out.cursor = node.endIndex
|
||||
return
|
||||
}
|
||||
const translated = translateNode(node)
|
||||
const translated = translateNode(node, language)
|
||||
if (translated) {
|
||||
emitSource(out, node, { ...translated, translated: true })
|
||||
return
|
||||
}
|
||||
if (node.childCount === 0 || stringNodes.has(node.type)) {
|
||||
const role = stringNodes.has(node.type) ? 'string' : table.tokenRole(node)
|
||||
emitSource(out, node, { text: oneLine(node.text), role, translated: false, offset: offsetOf(node) })
|
||||
const role = stringNodes.has(node.type) ? 'string' : language.tokenRole(node)
|
||||
emitSource(out, node, { text: oneLine(node.text), role, translated: false, offset: offsetOf(node, language) })
|
||||
return
|
||||
}
|
||||
for (const child of node.children) {
|
||||
@@ -352,14 +350,14 @@ export function createZhTemplateTranslator(table: ZhTable): Translator {
|
||||
}
|
||||
|
||||
/** 按译文顺序收集 node 落在 span 里的词。 */
|
||||
function tokensIn(node: Node, span: SourceSpan, source: string): Token[] {
|
||||
const out: Collector = { source, tokens: [], cursor: span.startIndex, gap: false }
|
||||
function tokensIn(node: Node, span: SourceSpan, document: SourceDocument): Token[] {
|
||||
const out: Collector = { document, tokens: [], cursor: span.startIndex, gap: false }
|
||||
collect(node, span, out)
|
||||
return out.tokens
|
||||
}
|
||||
|
||||
/** node 的源码原样照抄:叶子按源码着色(给了 role 就都按它),叶子之间的空白、标点照抄。 */
|
||||
function verbatim(node: Node, source: string, role?: TokenRole): Segment[] {
|
||||
function verbatim(node: Node, { source, language }: SourceDocument, role?: TokenRole): Segment[] {
|
||||
const segments: Segment[] = []
|
||||
let cursor = node.startIndex
|
||||
const visit = (current: Node): void => {
|
||||
@@ -373,7 +371,7 @@ export function createZhTemplateTranslator(table: ZhTable): Translator {
|
||||
return
|
||||
}
|
||||
pushSegment(segments, source.slice(cursor, current.startIndex), 'plain')
|
||||
pushSegment(segments, current.text, role ?? table.tokenRole(current), offsetOf(current))
|
||||
pushSegment(segments, current.text, role ?? language.tokenRole(current), offsetOf(current, language))
|
||||
cursor = current.endIndex
|
||||
}
|
||||
visit(node)
|
||||
@@ -388,10 +386,10 @@ export function createZhTemplateTranslator(table: ZhTable): Translator {
|
||||
return { markdown: docText(nodes.map(table.commentText)) }
|
||||
}
|
||||
|
||||
function translate(block: TextBlock, source: string): PreviewText {
|
||||
function translate(block: TextBlock, document: SourceDocument): PreviewText {
|
||||
if (block.kind === 'comment')
|
||||
return translateComment(block)
|
||||
const tokens = tokensIn(block.node, block.header, source)
|
||||
const tokens = tokensIn(block.node, block.header, document)
|
||||
const compound = block.children.length > 0 || block.header.endIndex < block.node.endIndex
|
||||
const last = tokens.at(-1)
|
||||
if (compound && last && !last.translated && HEADER_OPENERS.has(last.raw))
|
||||
@@ -399,12 +397,13 @@ export function createZhTemplateTranslator(table: ZhTable): Translator {
|
||||
return joinTokens(tokens)
|
||||
}
|
||||
|
||||
function describe(block: FactBlock, source: string): PropertyRow[] {
|
||||
function describe(block: FactBlock, document: SourceDocument): PropertyRow[] {
|
||||
const { header } = block
|
||||
const { language } = document
|
||||
// 值里带语句块主体的回调(useMemo(() => { … }))只翻到主体开始之前,和块头部一致
|
||||
const within = (node: Node, part: SourceSpan): Inline[] => {
|
||||
const span = part.endIndex > header.endIndex ? { ...part, endIndex: header.endIndex, endRow: header.endRow } : part
|
||||
return joinTokens(tokensIn(node, span, source))
|
||||
return joinTokens(tokensIn(node, span, document))
|
||||
}
|
||||
const phrases: Phrases = {
|
||||
translated: node => within(node, { startIndex: node.startIndex, endIndex: node.endIndex, startRow: node.startPosition.row, endRow: node.endPosition.row }),
|
||||
@@ -413,10 +412,10 @@ export function createZhTemplateTranslator(table: ZhTable): Translator {
|
||||
const name = table.mapLiterals[literal.type]
|
||||
return [hint(name ? `${name} ${size} 项` : `${size} 项`)]
|
||||
},
|
||||
verbatim: node => verbatim(node, source),
|
||||
verbatim: node => verbatim(node, document),
|
||||
// 点分模块名逐节照抄,每一节各自能跳到定义,和 VS Code 里一样
|
||||
module: node => stringNodes.has(node.type) ? [segment(node.text, 'string', offsetOf(node))] : verbatim(node, source, 'type'),
|
||||
name: (node, role) => [segment(node.text, role, offsetOf(node))],
|
||||
module: node => stringNodes.has(node.type) ? [segment(node.text, 'string', offsetOf(node, language))] : verbatim(node, document, 'type'),
|
||||
name: (node, role) => [segment(node.text, role, offsetOf(node, language))],
|
||||
doc: (node) => {
|
||||
const markdown = docText([table.commentText(node)])
|
||||
return markdown ? { markdown } : undefined
|
||||
|
||||
Reference in new issue
Block a user