96

我正在着手做一个附带项目,其目标是将代码从一种编程语言翻译成另一种编程语言。我开始使用的语言是 PHP 和 Python(Python 到 PHP 应该更容易开始),但理想情况下,我能够(相对)轻松地添加其他语言。计划是:

  • 这是面向Web开发的。原始代码和目标代码将位于框架之上(我也必须编写)。这些框架将采用 MVC 设计模式并遵循严格的编码约定。这应该会使翻译更容易一些。

  • 我还在研究 IOC 和依赖注入,因为它们可能使翻译过程更容易,更不容易出错。

  • 我将使用 Python 的解析器模块,它可以让我摆弄抽象语法树。显然我能用 PHP 得到的最接近的是token_get_all(),这是一个开始。

  • 从那时起,我可以构建 AST、符号表和控制流。

然后我相信我可以开始输出代码了。我不需要完美的翻译。我仍然需要检查生成的代码并修复问题。理想情况下,翻译人员应该标记有问题的翻译。

在你问“这到底是什么意思?”之前 答案是……这将是一次有趣的学习经历。如果您对如何使这不那么令人生畏有任何见解,请告诉我。


编辑:

我更感兴趣的是知道我可以在代码上实施什么样的模式以使其更容易翻译(即:IoC、SOA?)代码,而不是如何进行翻译。

4

6 回答 6

124

自 1995 年以来,我一直在构建工具(DMS Software Reengineering Toolkit)来进行通用程序操作(语言翻译是一个特例),得到了强大的计算机科学家团队的支持。DMS 提供通用解析、AST 构建、符号表、控制和数据流分析、翻译规则的应用、带注释的源文本的再生等,所有这些都由计算机语言的显式定义参数化。

你需要大量的机器来做好这件事特别是如果你希望能够以一种通用的方式为多种语言做到这一点),然后你需要可靠的解析器来处理定义不可靠的语言(PHP 就是一个很好的例子)。

您考虑构建语言到语言的翻译器或尝试它并没有错,但我认为您会发现对于真正的语言来说,这比您预期的要大得多。我们仅在 DMS 上投入了大约 100 人年,在每个“可靠”语言定义(包括我们为 PHP 痛苦地构建的那个)上又投入了 6-12 个月,对于 C++ 等讨厌的语言则更多。这将是一次“地狱般的学习经历”;它一直是给我们的。(您可能会发现上述网站上的技术论文部分很有趣,可以快速开始学习)。

人们经常尝试从他们熟悉的某些技术开始构建某种通用机器,这可以完成部分工作。(Python AST 就是很好的例子)。好消息是,部分工作已经完成。坏消息是,机器内置了无数的假设,其中大部分是你不会发现的,直到你试图让它去做别的事情。到那时,您会发现机器已经连接到执行它最初的工作,并且真的会真正抵制您让它做其他事情的尝试。(我怀疑尝试让 Python AST 来为 PHP 建模会很有趣)。

我最初开始构建 DMS 的原因是为了构建很少内置此类假设的基础。它有些让我们头疼。到目前为止,没有黑洞。(在过去的 15 年里,我工作中最困难的部分是试图阻止这种假设蔓延)。

许多人还错误地认为,如果他们可以解析(并且可能获得 AST),他们就可以很好地完成一些复杂的事情。艰难的教训之一是您需要符号表和流分析来进行良好的程序分析或转换。AST 是必要的,但还不够。这就是 Aho&Ullman 的编译器书没有停留在第 2 章的原因。(OP 有这个权利,因为他计划在 AST 之外构建额外的机器)。有关此主题的更多信息,请参阅解析后的生活

“我不需要完美的翻译”这句话很麻烦。弱翻译者所做的是转换“简单”的 80% 代码,而将难的 20% 手动完成。如果您打算转换的应用程序非常小,并且您只打算将其转换一次,那么 20% 就可以了。如果您想转换许多应用程序(甚至是同一个应用程序,但随着时间的推移会有细微的变化),这并不好。如果您尝试转换 100K SLOC,那么 20% 是 20,000 行原始代码,在另外 80,000 行您已经不理解的翻译程序的上下文中难以翻译、理解和修改。这需要付出巨大的努力。在百万行级别,这在实践中根本是不可能的。更难,他们通常会因长时间延误、高成本和经常彻底失败而痛苦地发现。)

要翻译大型系统,您必须争取达到 90 % 的高转换率,否则您可能无法完成翻译活动的手动部分。

另一个关键考虑因素是要翻译的代码大小。即使使用好的工具,也需要花费大量精力来构建一个有效的、强大的翻译器。虽然构建翻译器而不是简单地进行手动转换看起来很性感和酷,但对于小型代码库(例如,根据我们的经验,最高可达 100K SLOC),经济学根本无法证明它是合理的。没有人喜欢这个答案,但如果你真的只需要翻译 10K SLOC 的代码,你最好还是硬着头皮去做。是的,这很痛苦。

我认为我们的工具非常好(但是,我很偏颇)。而且要造一个好的翻译还是很困难的;我们大约需要 1.5-2 人年,并且我们知道如何使用我们的工具。不同之处在于,有了这么多机器,我们成功的次数比失败的次数要多得多。

于 2010-08-11T17:11:02.000 回答
14

我的回答将解决解析 Python 以将其翻译成另一种语言的具体任务,而不是 Ira 在他的回答中很好地解决的更高级别的方面。

简而言之:不要使用解析器模块,有一种更简单的方法。

ast模块自 Python 2.6 起可用,更适合您的需求,因为它为您提供了现成的 AST 来使用。我去年写过一篇关于这个parse的文章,但总之,使用 的方法ast将 Python 源代码解析为 AST。该parser模块将为您提供解析树,而不是 AST。小心区别

现在,由于 Python 的 AST 非常详细,给定一个 AST,前端工作并不难。我想您可以很快为功能的某些部分准备一个简单的原型。但是,获得完整的解决方案需要更多时间,主要是因为语言的语义不同。语言的一个简单子集(函数、基本类型等)可以很容易地翻译,但是一旦您进入更复杂的层,您将需要重型机器来在另一种语言中模拟一种语言的核心。例如,考虑 PHP 中不存在的 Python 生成器和列表推导(据我所知,当涉及到 PHP 时,这无疑是很差的)。

最后给你一个提示,考虑一下2to3Python 开发人员创建的用于将 Python 2 代码转换为 Python 3 代码的工具。前端方面,它具有将 Python 翻译成某种东西所需的大部分元素。但是,由于 Python 2 和 3 的核心相似,因此不需要仿真机制。

于 2010-08-14T14:07:20.553 回答
5

编写翻译器并非不可能,尤其是考虑到乔尔的实习生是在一个夏天完成的。

如果你想做一种语言,这很容易。如果你想做更多,它会更难一点,但不要太多。最困难的部分是,虽然任何图灵完备语言都可以做另一种图灵完备语言所做的事情,但内置数据类型可以显着改变一种语言的作用。

例如:

word = 'This is not a word'
print word[::-2]

需要大量的 C++ 代码来复制(好吧,你可以用一些循环结构来做相当短的事情,但仍然如此)。

我猜这有点偏题了。

你有没有写过基于语言语法的分词器/解析器?如果你还没有,你可能想学习如何做到这一点,因为这是这个项目的主要部分。我要做的是提出一个基本的图灵完整语法——与 Python 字节码非常相似。然后,您创建一个采用语言语法(可能使用BNF)的词法分析器/解析器,并根据语法将语言编译成您的中间语言。然后你要做的就是做相反的事情——根据语法创建一个从你的语言到目标语言的解析器。

我看到的最明显的问题是,一开始您可能会创建非常低效的代码,尤其是在 Python 等更强大*的语言中。

但是,如果您这样做,那么您可能会在进行过程中找到优化输出的方法。总结一下:

  • 阅读提供的语法
  • 将程序编译成中间(但也是图灵完备的)语法
  • 将中间程序编译成最终语言(基于提供的语法)
  • ...?
  • 利润!(?)

*强大的意思是这需要 4 行:

myinput = raw_input("Enter something: ")
print myinput.replace('a', 'A')
print sum(ord(c) for c in myinput)
print myinput[::-1]

给我展示另一种可以在 4 行内完成类似操作的语言,我将向您展示一种与 Python 一样强大的语言。

于 2010-08-18T22:43:55.363 回答
3

有几个答案告诉你不要打扰。嗯,这有多大帮助?你想学吗?你可以学习。这是编译。碰巧您的目标语言不是机器代码,而是另一种高级语言。这是一直在做的。

有一种相对简单的入门方法。首先,去获取http://sourceforge.net/projects/lime-php/(如果你想在 PHP 中工作)或类似的,并通过示例代码。接下来,您可以使用一系列正则表达式编写词法分析器,并将标记提供给您生成的解析器。您的语义操作可以直接以另一种语言输出代码,也可以构建一些数据结构(想想对象,人),您可以对其进行按摩和遍历以生成输出代码。

你对 PHP 和 Python 很幸运,因为在许多方面它们是相同的语言,但语法不同。困难的部分是克服语法形式和数据结构之间的语义差异。例如,Python 有列表和字典,而 PHP 只有 assoc 数组。

“学习者”方法是为语言的受限子集(例如仅打印语句、简单数学和变量赋值)构建可以正常工作的东西,然后逐步消除限制。这基本上就是该领域的“大”家伙所做的。

哦,因为你在 Python 中没有静态类型,最好编写和依赖 PHP 函数,如“python_add”,它根据 Python 的方式添加数字、字符串或对象。

显然,如果您允许,这可能会变得更大。

于 2010-08-17T12:08:17.113 回答
2

关于使用 ast.parse 而不是解析器(我以前不知道),我将第二次 @EliBendersky 的观点。我也热烈推荐你查看他的博客。我用 ast.parse 做 Python->JavaScript 翻译器(@ https://bitbucket.org/amirouche/pythonium)。通过对其他实现进行一些审查并自己尝试它们,我提出了 Pythonium 设计。我从我也开始的https://github.com/PythonJS/PythonJS分叉了 Pythonium ,它实际上是一个完整的重写。整体设计灵感来自 PyPy 和http://www.hpl.hp.com/techreports/Compaq-DEC/WRL-89-1.pdf论文。

我尝试的一切,从开始到最好的解决方案,即使它看起来像 Pythonium 营销,但实际上并非如此(如果网络礼仪似乎不正确,请随时告诉我):

  • 使用原型继承在普通旧 JavaScript 中实现 Python 语义:AFAIK 使用 JS 原型对象系统实现 Python 多重继承是不可能的。后来我确实尝试使用其他技巧来做到这一点(参见getattribute)。据我所知,在 JavaScript 中没有 Python 多重继承的实现,存在的最好的是单继承 + mixins,我不确定它们是否处理菱形继承。有点类似于 Skulpt 但没有 google clojure。

  • 我尝试使用 Google clojure,就像 Skulpt(编译器)一样,而不是实际阅读 Skulpt 代码#fail。无论如何,因为基于 JS 原型的对象系统仍然是不可能的。创建绑定非常非常困难,您需要编写 JavaScript 和大量样板代码(参见https://github.com/skulpt/skulpt/issues/50,我是幽灵)。那时还没有明确的方法将绑定集成到构建系统中。我认为 Skulpt 是一个库,您只需将 .py 文件包含在要执行的 html 中,开发人员无需进行编译阶段。

  • 尝试过 pyjaco(编译器),但创建绑定(从 Python 代码调用 Javascript 代码)非常困难,每次都要创建太多样板代码。现在我认为 pyjaco 是更接近 Pythonium 的那个。pyjaco 是用 Python 编写的(ast.parse 也是),但很多都是用 JavaScript 编写的,并且它使用原型继承。

我从未真正成功地运行睡衣#fail,也从未尝试再次阅读代码#fail。但在我看来,睡衣是在做 API->API 翻译(或框架到框架),而不是 Python 到 JavaScript 的翻译。JavaScript 框架使用页面中已有的数据或来自服务器的数据。Python 代码只是“管道”。之后我发现睡衣其实是一个真正的python->js翻译器。

我仍然认为可以进行 API->API(或框架->框架)翻译,这基本上是我在 Pythonium 中所做的,但在较低级别。可能睡衣使用与Pythonium相同的算法......

然后我发现 brython 像 Skulpt 一样完全用 Javascript 编写,不需要编译和很多绒毛......但是用 JavaScript 编写的。

从这个项目过程中写的第一行开始,我就知道了 PyPy,甚至是 PyPy 的 JavaScript 后端。是的,如果你找到它,你可以直接从 PyPy 用 Ja​​vaScript 生成 Python 解释器。人们说,这是一场灾难。我没有读到为什么。但我认为原因是他们用来实现解释器的中间语言 RPython 是 Python 的一个子集,专门用于翻译成 C(可能还有 asm)。Ira Baxter 说,当你构建一些东西时,你总是会做出假设,并且可能你会对其进行微调,使其在 PyPy 的情况下做到最好:Python->C 翻译。这些假设在其他情况下可能不相关,更糟糕的是,它们可以推断出开销,否则直接翻译很可能总是更好。

用 Python 编写解释器听起来是一个(非常)好主意。但是出于性能原因,我对编译器更感兴趣,而且实际上将 Python 编译为 JavaScript 比解释它更容易。

我开始 PythonJS 的想法是把 Python 的一个子集放在一起,我可以很容易地把它翻译成 JavaScript。起初,由于过去的经验,我什至没有费心去实现 OO 系统。我实现转换为 JavaScript 的 Python 子集是:

  • 在定义和调用中具有完整参数语义的函数。这是我最自豪的部分。
  • 而/如果/elif/else
  • Python 类型被转换为 JavaScript 类型(没有任何类型的 Python 类型)
  • for 只能遍历 Javascript 数组(对于 in 数组)
  • 对 JavaScript 的透明访问:如果您在 Python 代码中编写 Array,它将被转换为 javascript 中的 Array。这是在可用性方面超过其竞争对手的最大成就。
  • 您可以将 Python 源代码中定义的函数传递给 javascript 函数。将考虑默认参数。
  • 它添加了一个名为 new 的特殊函数,该函数被翻译为 JavaScript new 例如:new(Python)(1, 2, spam, "egg") 被翻译为 "new Python(1, 2, spam, "egg")。
  • “var”由翻译器自动处理。(来自 Brett(PythonJS 贡献者)的非常好的发现。
  • 全局关键字
  • 关闭
  • 拉姆达斯
  • 列表推导
  • 通过 requirejs 支持导入
  • 通过classyjs的单类继承+ mixin

与 Python 的完整语义相比,这看起来很多,但实际上非常狭窄。它实际上是带有 Python 语法的 JavaScript。

生成的 JS 是完美的,即。没有开销,无法通过进一步编辑来提高性能。如果您可以改进生成的代码,您也可以从 Python 源文件中进行改进。此外,编译器不依赖于您可以在http://superherojs.com/编写的 .js 中找到的任何 JS 技巧,因此它非常具有可读性。

这部分 PythonJS 的直接后代是 Pythonium Veloce 模式。完整的实现可以在@https://bitbucket.org/amirouche/pythonium/src/33898da731ee2d768ced3​​92f1c369afd746c25d7/pythonium/veloce/veloce.py?at = master 793 SLOC + 大约 100 SLOC 与其他翻译器共享代码中找到。

pystones.py 的改编版本可以在 Veloce 模式下翻译,参见。https://bitbucket.org/amirouche/pythonium/src/33898da731ee2d768ced3​​92f1c369afd746c25d7/pystone/?at=master

在设置了基本的 Python->JavaScript 翻译后,我选择了另一条将完整的 Python 翻译成 JavaScript 的路径。除了目标语言之外,glib 执行基于类的面向对象代码的方式是 JS,因此您可以访问数组、类似映射的对象和许多其他技巧,所有这些部分都是用 Python 编写的。IIRC 没有用 Pythonium 翻译器编写的 javascript 代码。获得单一继承并不难,这里是使 Pythonium 完全兼容 Python 的困难部分:

  • spam.egg在 Python 中总是被翻译成getattribute(spam, "egg")我没有特别分析这个,但我认为它浪费了很多时间,我不确定我可以用 asm.js 或其他任何东西来改进它。
  • 方法解析顺序:即使使用 Python 编写的算法,将其转换为 Python Veloce 兼容代码也是一项艰巨的工作。
  • getattribute:实际的 getattribute 解析算法有点棘手,它仍然不支持数据描述符
  • 基于元类类:我知道在哪里插入代码,但仍然......
  • 最后一点很重要:some_callable(...) 总是转换为“call(some_callable)”。AFAIK 翻译器根本不使用推理,所以每次你打电话时,你都需要检查它是用哪种对象来调用它的,因为它们是按照它应该被调用的方式来调用的。

这部分包含在https://bitbucket.org/amirouche/pythonium/src/33898da731ee2d768ced3 ​​92f1c369afd746c25d7/pythonium/compliant/runtime.py?at= master 它是用与 Python Veloce 兼容的 Python 编写的。

实际的兼容翻译器https://bitbucket.org/amirouche/pythonium/src/33898da731ee2d768ced3 ​​92f1c369afd746c25d7/pythonium/compliant/compliant.py?at=master 不会直接生成 JavaScript 代码,最重要的是不会进行 ast->ast 转换. 我尝试了 ast->ast 的东西,即使使用 ast.NodeTransformer 比 cst 更好,ast 也不好使用,更重要的是我不需要做 ast->ast。

在我的情况下,至少对 python ast 执行 python ast 可能会提高性能,因为我有时会在生成与其关联的代码之前检查块的内容,例如:

  • var/global:为了能够对某些东西进行 var,我必须知道我需要什么而不是 var。我没有生成一个块来跟踪在给定块中创建了哪些变量并将其插入到生成的功能块的顶部,我只是在实际访问子节点以生成相关代码之前进入块时寻找相关的变量分配。
  • yield,到目前为止,生成器在 JS 中有一种特殊的语法,所以当我想编写“var my_generator = function”时,我需要知道哪个 Python 函数是生成器

因此,对于翻译的每个阶段,我并没有真正访问每个节点一次。

整个过程可以描述为:

Python source code -> Python ast -> Python source code compatible with Veloce mode -> Python ast -> JavaScript source code

Python 内置函数是用 Python 代码(!)编写的,IIRC 有一些与引导类型相关的限制,但您可以访问所有可以在兼容模式下转换 Pythonium 的东西。看看https://bitbucket.org/amirouche/pythonium/src/33898da731ee2d768ced3​​92f1c369afd746c25d7/pythonium/compliant/builtins/? at=master

阅读从 pythonium 兼容生成的 JS 代码可以理解,但 source maps 将有很大帮助。

根据这次经验,我可以给你的宝贵建议是老屁:

  • 广泛审查文献和现有项目中的主题,封闭源代码或免费。当我回顾不同的现有项目时,我应该给它更多的时间和动力。
  • 问问题!如果我事先知道 PyPy 后端由于 C/Javascript 语义不匹配导致的开销而无用。我可能会在 6 个月前或 3 年前有 Pythonium 的想法。
  • 知道自己想做什么,有目标。对于这个项目,我有不同的目标:练习一点 javascript,了解更多 Python 知识,并能够编写可以在浏览器中运行的 Python 代码(更多内容如下)。
  • 失败是经验
  • 一小步就是一步
  • 从小处着手
  • 远大的梦想
  • 做演示
  • 迭代

仅使用 Python Veloce 模式,我很高兴!但在此过程中,我发现我真正想要的是将我和其他人从 Javascript 中解放出来,但更重要的是能够以一种舒适的方式进行创作。这将我引向了 Scheme、DSL、模型以及最终的领域特定模型(参见http://dsmforum.org/)。

关于 Ira Baxter 的回应:

这些估计根本没有帮助。我花了大约 6 个月的空闲时间来学习 PythonJS 和 Pythonium。所以我可以期待更多的全职 6 个月。我想我们都知道企业环境中的 100 人年可能意味着什么,根本不意味着什么......

当有人说某事很难或更经常是不可能的时,我会回答“只需要时间来解决一个不可能的问题”,否则就说没有什么是不可能的,除非在这种情况下证明是不可能的数学证明......

如果它没有被证明是不可能的,那么它留下了想象的空间:

  • 找到证明这是不可能的证据

  • 如果不可能,则可能存在可以解决的“劣质”问题。

或者

  • 如果不是不可能,请找到解决方案

这不仅仅是乐观的想法。当我开始使用 Python->Javascript 时,每个人都说这是不可能的。PyPy 不可能。元类太难了。等等...我认为,将 PyPy 引入 Scheme->C 论文(已有 25 年历史)的唯一革命是一些自动 JIT 生成(我认为基于 RPython 解释器编写的提示)。

大多数说某件事“困难”或“不可能”的人并没有提供原因。C++很难解析?我知道,它们仍然是(免费的)C++ 解析器。邪恶在细节中?我知道。一个人说不可能是没有帮助的,比“没有帮助”更令人沮丧,而且有些人的意思是要劝阻别人。我通过https://stackoverflow.com/questions/22621164/how-to-automatically-generate-a-parser-code-to-code-translator-from-a-corpus听说了这个问题。

什么对你来说是完美的?这就是您定义下一个目标并可能达到总体目标的方式。

我更感兴趣的是知道我可以对代码实施什么样的模式以使其更容易翻译(即:IoC、SOA?)代码,而不是如何进行翻译。

我没有看到不能至少以不完美的方式从一种语言翻译成另一种语言的模式。由于可以进行语言到语言的翻译,因此您最好首先针对这一点。因为,我认为根据http://en.wikipedia.org/wiki/Graph_isomorphism_problem,两种计算机语言之间的翻译是树或 DAG 同构。即使我们已经知道它们都是图灵完备的,所以...

框架-> 框架,我更好地将其可视化为 API-> API 翻译,您可能仍然需要牢记这一点,以改进生成的代码。例如:Prolog 作为非常具体的语法,但您仍然可以通过在 Python 中描述相同的图形来进行类似 Prolog 的计算...如果我要实现 Prolog 到 Python 的翻译器,我不会在 Python 中实现统一,而是在 C 库中实现加上对 Pythonist 来说非常易读的“Python 语法”。最后,语法只是我们赋予意义的“绘画”(这就是我开始方案的原因)。邪恶在于语言的细节,我不是在谈论语法。语言中使用的概念getattribute钩子(你可以没有它)但是像尾递归优化这样的虚拟机特性可能很难处理。您不在乎初始程序是否不使用尾递归,即使目标语言中没有尾递归,您也可以使用 greenlets/event loop 来模拟它。

对于目标语言和源语言,请查找:

  • 大而具体的想法
  • 微小而共同的共同想法

由此会出现:

  • 容易翻译的东西
  • 难以翻译的东西

您可能还能够知道什么将被翻译成快速和慢速代码。

还有 stdlib 或任何库的问题,但没有明确的答案,这取决于您的目标。

惯用代码或可读生成代码也有解决方案......

定位像 PHP 这样的平台比定位浏览器容易得多,因为您可以提供慢速和/或关键路径的 C 实现。

鉴于您的第一个项目是将 Python 转换为 PHP,至少对于我所知道的 PHP3 子集而言,自定义 veloce.py 是您最好的选择。如果您可以为 PHP 实现 veloce.py,那么您可能可以运行兼容模式...此外,如果您可以将 PHP 转换为 PHP 的子集,您可以使用 php_veloce.py 生成,这意味着您可以将 PHP 转换为veloce.py 可以使用的 Python 子集,这意味着您可以将 PHP 转换为 Javascript。只是说...

您还可以查看这些库:

您也可能对这篇博文(和评论)感兴趣:https ://www.rfk.id.au/blog/entry/pypy-js-poc-jit/

于 2014-04-03T22:25:27.597 回答
0

您可以查看Vala 编译器,它将 Vala(一种类似 C# 的语言)翻译成 C。

于 2010-08-18T09:35:22.420 回答