前两个周末写了《手写 PHP 转 Python 编译器》的词法,语法分析部分,上个周末卡文了。
访问器部分写了两次都不满意,没办法,只好停下来,参考一下 Python 的实现。我实现的部分正好和 Python 是一个思路,就是生成 CST(Concrete syntax tree)之后,再生成 AST。由于我想创(tou)新(lan),所以未没有详细实现 AST,而想绕过 AST 去生成代码。这下有点欲速不达了。
先看看 Python 执行代码的过程:
1. Tokenizer 进行词法分析,把源程序分解为 Token
2. Parser 根据 Token 创建 CST
3. 将 CST 转换为 AST
4. 将 AST 编译为字节码
5. 执行字节码
现在我们要实现第 3 步。参考一下 Python 源码:
- /* Transform the CST rooted at node * to the appropriate AST*/
- mod_ty
- PyAST_FromNode(const node *n, PyCompilerFlags *flags, const char *filename, PyArena *arena)
这是将 CST 转换为 AST 的入口,Node 就是 CST 的节点。而下一步处理的函数为 ast_for_stmt。
在 ast_for_stmt 里面,根据语法定义表,由各子函数生成 AST 的节点。例如函数调用:ast_for_call 最后生成了这样的结构
- Call(name_expr, NULL, NULL, NULL, NULL, LINENO(n),
- n->n_col_offset, c->c_arena);
而这个 C 语言的函数与 Python 的 AST 库接口一模一样。
我们来看一个 AST 库的例子
- import ast
- expr = """
- def hello(name='world'):
- print("hello " + name)
- hello()
- """expr_ast = ast.parse(expr)
- print(ast.dump(expr_ast))
- exec compile(expr_ast, '<string>', 'exec')
运行后的结果:
- Module(
- body=[
- FunctionDef(
- name='hello',
- args=arguments(args=[Name(id='name', ctx=Param())], vararg=None,
- kwarg=None,
- defaults=[Str(s='world')]),
- body=[
- Print(dest=None, values=[BinOp(left=Str(s='hello '), op=Add(), right=Name(id='name', ctx=Load()))], nl=True)], decorator_list=[]),
- Expr(value=Call(func=Name(id='hello', ctx=Load()), args=[], keywords=[], starargs=None, kwargs=None))])
- hello world
注意结果中的 Call(func=Name(id='hello', ctx=Load()), args=[], keywords=[], starargs=None, kwargs=None)
为了更好的说明问题,我们来为 AST 增加一条函数调用
- import ast
- expr = """
- def hello(name='world'):
- print("hello " + name)
- hello()
- """
- expr_ast = ast.parse(expr)
- n = ast.Name('hello', ast.Load(), lineno=5, col_offset=0)
- p = ast.Str('windfic', lineno=5, col_offset=0)
- c = ast.Call(n, [p], [], None, None, lineno=5, col_offset=0)
- e = ast.Expr(c, lineno=5, col_offset=0)
- expr_ast.body.append(e)
- exec compile(expr_ast, '<string>', 'exec')
运行后,结果为
- hello world
- hello windfic
就这样,我们已经可以直接生成一个 AST 结构,并且运行了。
以前,我一直是想先由 Php 生成 AST,再生成 Python 代码。但是突然发现,我们不必生成代码了,可以直接生成兼容 Python 的 AST 结构,并且可以在 Python 中直接运行。
而 Python 的语法定义也不算很大,在 Python 文档中已经完整列出了
- module Python version "$Revision$"
- {
- mod = Module(stmt* body)
- | Interactive(stmt* body)
- | Expression(expr body)
- -- not really an actual node but useful in Jython's typesystem.
- | Suite(stmt* body)
- stmt = FunctionDef(identifier name, arguments args,
- stmt* body, expr* decorator_list)
- | ClassDef(identifier name, expr* bases, stmt* body, expr* decorator_list)
- | Return(expr? value)
- | Delete(expr* targets)
- | Assign(expr* targets, expr value)
- | AugAssign(expr target, operator op, expr value)
- -- not sure if bool is allowed, can always use int
- | Print(expr? dest, expr* values, bool nl)
- -- use 'orelse' because else is a keyword in target languages
- | For(expr target, expr iter, stmt* body, stmt* orelse)
- | While(expr test, stmt* body, stmt* orelse)
- | If(expr test, stmt* body, stmt* orelse)
- | With(expr context_expr, expr? optional_vars, stmt* body)
想要完整实现 Php 的 CST 转 Python 的 AST 还需要一些时间,先来一个例子吧 demo.php
- <?php
- function hello($name='world'){
- echo 'hello ' . $name;
- }
- hello();
- if __name__ == '__main__':
- CompilerPhp().compile_file('src/demo.php').execute()
运行上面的例子,结果与 Python 版本的例子完全一样。
试着用 Unparse 生成代码,也一切正常,唯一要说的,就是编码问题,不能使用 Unicode。
以上
源码:converterV0.5.zip
这个项目到现在差不多有三个星期的狂热编码,做到现在也已经完全和最初的设想是两码事了。最核心及最难的部分,都已经完成。是时候休整一段时间了。最后再啰嗦几句。
很多人可能还不了解这个项目的价值所在,或者说我想输出的价值在哪里。这个项目实际上是一种廉价编译器的试验品。廉价编译器,意味着我们可以很轻易地扩充支持的语言,比如所有的主流语言。这个不难。做到了这一步,我们可以想像一下,这个工具的价值在哪里。至少有三种后续的可能:
1、还是 Transpiler,就是这个项目。
2、源码阅读器。
3、源码(片段)管理、搜索、自动生成工具。
来源: http://www.cnblogs.com/windfic/p/6594791.html