轉換器5:參考Python源碼,實現Php代碼轉Ast並直接運行


前兩個周末寫了《手寫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文檔中已經完整列出了

  1 module Python version "$Revision$"
  2 {
  3     mod = Module(stmt* body)
  4         | Interactive(stmt* body)
  5         | Expression(expr body)
  6 
  7         -- not really an actual node but useful in Jython's typesystem.
  8         | Suite(stmt* body)
  9 
 10     stmt = FunctionDef(identifier name, arguments args, 
 11                             stmt* body, expr* decorator_list)
 12           | ClassDef(identifier name, expr* bases, stmt* body, expr* decorator_list)
 13           | Return(expr? value)
 14 
 15           | Delete(expr* targets)
 16           | Assign(expr* targets, expr value)
 17           | AugAssign(expr target, operator op, expr value)
 18 
 19           -- not sure if bool is allowed, can always use int
 20            | Print(expr? dest, expr* values, bool nl)
 21 
 22           -- use 'orelse' because else is a keyword in target languages
 23           | For(expr target, expr iter, stmt* body, stmt* orelse)
 24           | While(expr test, stmt* body, stmt* orelse)
 25           | If(expr test, stmt* body, stmt* orelse)
 26           | With(expr context_expr, expr? optional_vars, stmt* body)
 27 
...

想要完整實現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、源碼(片段)管理、搜索、自動生成工具。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM