评论

收藏

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

移动开发 移动开发 发布于:2021-11-01 11:55 | 阅读数:597 | 评论:0

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) 上的源代码:金蟒。

关注下面的标签,发现更多相似文章