当前位置: 首页 > 工具软件 > Pyright > 使用案例 >

利用 pyright 实现 MicroPython/Python 中文编程和中英互译

姚昊焱
2023-12-01

利用 pyright 实现 MicroPython/Python 中文编程和中英互译

pyright 是微软开源的一个为 python 提供类型检查、自动补全、文档信息提示等语言服务的工具,用 typescript 写成,微软自家的 VS Code python 扩展 Pylance 就是基于 pyright 开发。

笔者在对 python 解释器进行中文化,实现草蟒中文编程语言之后,便打算对其小弟 micropython 进行中文化。但是,mpy 是针对单片机等小内存设备而实现的精简解释器,对 uft8 的支持天生有限。因此,笔者之前用 python 中文化思路尝试的 mpy 中文化并不成功。

近期,笔者利用 typescript——可以看作是 ts/js 的语言服务工具——实现了 ts/js 中文化(极速Web开发语言初具气象)。在此过程中,笔者发现,此类语言服务工具除了提供常见的语言服务之外,其实还有一个重要用途,那就是能够很方便地用来实现编程语言的中文化和中英互译。

本着这个想法,笔者开始了解 python 的类型检查和语言服务工具,刚好发现微软就有这么一款开源工具,而且是后起之秀。

不过,不像 typescript,pyright 未提供 API 文档和 d.ts 声明文件,代码注释也很少,github 及其他网站上关于它的使用寥寥无几,其最大的应用 pylance 还是闭源的。

幸好,笔者之前开发极速 Web 语言的经验派上了用场,在潜心阅读源代码之后,克服了上述困难。

下面就把笔者的探索成果分享出来,希望有助于推动中文编程语言的发展和崛起。

本文主要说明如何将中文代码翻译成英文代码,英译中大同小异,不再赘述。

中文化步骤

一个中文代码文件其实就是一个字符串,我们需要做的是对这个字符串进行处理,将其中需要翻译的中文(包括保留字、库中的类名、函数名、参数名等)翻译成英文,然后生成英文代码文件,之后的处理和执行就是大家所熟悉的常规操作。

产生 AST

pyright 包含 tokenizer(用于将上述字符串中的一个个词元分门别类,形成一个带有丰富信息的词元或 token 流)和 parser(用于根据 token 流形成抽象语法树或 AST),略作修改使其能够识别并解析中文保留字。

// tokenizerTypes.ts
export const enum KeywordType {
    And,
    ...
    With,
    Yield,
    不是,
    不在,
}

// tokenizer.ts

const _keywords: Map<string, KeywordType> = new Map([
    ['and', KeywordType.And],
    ['且', KeywordType.And],
	...
    ['True', KeywordType.True],
    ['真', KeywordType.True],
    ['不是', KeywordType.不是],
    ['不在', KeywordType.不在],
]);

// parser.ts

    // comparison: expr (comp_op expr)*
    // comp_op: '<'|'>'|'=='|'>='|'<='|'<>'|'!='|'in'|'not' 'in'|'is'|'is' 'not'
    private _parseComparison(): ExpressionNode {
        ...
        while (true) {
            ...
            if (...
            } else if (this._consumeTokenIfKeyword(KeywordType.In)) {
                comparisonOperator = OperatorType.In;
            } else if (this._consumeTokenIfKeyword(KeywordType.不是)) {
                comparisonOperator = OperatorType.IsNot;
            } else if (this._consumeTokenIfKeyword(KeywordType.不在)) {
                comparisonOperator = OperatorType.NotIn;
            } else if (...
            }
            ...
        return leftExpr;
    }

然后,我们就可以使用 parser 处理中文代码,获得 AST。

	const parser = new Parser();
    let result = parser.parseSourceFile(code, new ParseOptions(), new DiagnosticSink())

遍历 AST 并翻译

pyright 提供了遍历 AST 的类 ParseTreeWalker,实现其一个子类并重写不同节点的处理函数,便可修改节点信息,达到我们的翻译目的。

class treeWalker extends ParseTreeWalker {
    constructor(srcFile, program) {
        super();
        this._srcFile = srcFile;
        this._program = program;
    }

    visitName(node/* : NameNode */) {
        // NameNode 包括保留字、函数名等,是翻译的重点
        let pos = {line: 0, character: node.start+1};
        // 查找文档信息
        let hoverResult = this._program.getHoverForPosition(this._srcFile, pos, 'plaintext', CancellationToken.None);
        let sigHelp = this._program.getSignatureHelpForPosition(this._srcFile, pos, 'plaintext', CancellationToken.None);

        if (hoverResult) {
            // 替换中文保留字、函数名等
        }

        if (sigHelp) {
            // 替换函数的中文参数名称等
        }

        return true;
    }
}

上面的代码中,program 这个对象至关重要,它包含了所有相关文件(标准库、项目文件、各种 pyi 等)的信息,通过它可以获得特定位置的标识符(不包括保留字)的文档信息,由此我们才知道应该将其翻译成什么。创建 program 对象需要两个参数,如下例所示。

	const configOptions = new ConfigOptions(dir, 'off'); 
	// 详细配置信息在 configOptions.ts 中
    configOptions.pythonPath = '';
    configOptions.typeshedPath = '';
    configOptions.stubPath = '';
    configOptions.verboseOutput = true;
    // configOptions.useLibraryCodeForTypes = true;

    const fs = createFromRealFileSystem();
    const importResolver = new ImportResolver(fs, configOptions, new FullAccessHost(fs));

    const program = new Program(importResolver, configOptions);
    program.setTrackedFiles([sourceFile]);

为了正确实现替换,文档信息须符合一定的规范,欢迎大家就此提出建议。笔者目前简单规定如下:

  • 对于库名、类名、函数名等,须在单独的一行注释中写上:@英文 xxxx
  • 对于参数名,须在单独的一行注释中写上:@@ [中文参数名] @英文 xxxx

生成英文代码文件

最后一步就是将修改后的 token 流再组装成代码字符串,保留字的翻译也是在此进行。这一步不难,但比较繁琐,感兴趣的话可以看我在码云 (gitee) 上的源代码:金蟒

 类似资料: