IronPython 源码剖析系列(1):IronPython 编译器

自 IronPython 正式发布以来,由于对 Python 语言的喜爱所驱使,同时我想藉此去了解一下编程语言的编译器,分析器等程序是什么原理,如何运作的,所以我开始了对 IronPython 源代码的学习过程。但代码也看了有一段时间了,之前是看一些实现细节,结果越看越糊涂。现在我发现需要改变一下策略了,因为我们了解一个系统总是从对它的使用方法去开始了解,如果直接去了解底层的运作原理,则可能会迷失在代码海洋里面。所以我也准备采取自顶而下的分析方法,捡软柿子捏,从简单的,宏观的入手。至于具体的实现细节,可以慢慢再深入研究。

OK,今天被我抓到的看起来像软柿子的是:

IronPython/Hosting/PythonCompiler.cs

首先我们抓住要点,看这个文件中的主要类: PythonCompiler (Python 编译器)

在这个类中,首先有一堆的属性不用去管,表示的无非是编译器将要输出的文件的类型啊(DLL 还是 EXE),输出路径,引用了哪些程序集啊等等东西。

直奔主题,我们看到 Compile() 方法,这是负责编译的主控制方法。
这个方法不难理解,我读了一遍,注释如下:

///   <summary>
///  编译
///   </summary>
public   void  Compile() {
    
string  fullPath  =  Path.GetFullPath(outputAssembly);
    
string  outDir  =  Path.GetDirectoryName(fullPath);
    
string  fileName  =  Path.GetFileName(outputAssembly);

    
//  Python 编译器的接受池
    PythonCompilerSink sink  =   new  PythonCompilerSink(compilerSink);

    
//  程序集产生器
    assemblyGen  =   new  AssemblyGen(
        Path.GetFileNameWithoutExtension(outputAssembly),
        outDir, fileName, includeDebugInformation, staticTypes, executable, machine
        );

    
//  是否以设定入口点(entry point)
     bool  entryPointSet  =   false ;

    
//  设定默认的主文件(对非 DLL 的输出文件类型而言)
     if  (mainFile  ==   null   &&  sourceFiles.Count  ==   1   &&  targetKind  !=  PEFileKinds.Dll) {
        mainFile 
=  sourceFiles[ 0 ];
    }

    
//  对每个源文件依次编译
     foreach  ( string  sourceFile  in  sourceFiles) {
        
//  是否产生 Main 方法
         bool  createMainMethod  =  sourceFile  ==  mainFile;
        
//  每个源代码文件编译为一个模块
        CompilePythonModule(sourceFile, sink, createMainMethod);

        
if  (sink.Errors  >   0 return ;

        
if  (createMainMethod) {
            entryPointSet 
=   true ;
        }
    }

    
//  依次将所有资源文件添加到程序集中
     if  (resourceFiles  !=   null ) {
        
foreach  (ResourceFile rf  in  resourceFiles) {
            assemblyGen.AddResourceFile(rf.Name, rf.File, rf.PublicResource 
?  ResourceAttributes.Public : ResourceAttributes.Private);
        }
    }

    
//  对非 DLL 的目标文件,必须要求有一个入口点
     if  (targetKind  !=  PEFileKinds.Dll  &&   ! entryPointSet) {
        sink.AddError(
"" string .Format( " Need an entry point for target kind {0} " , targetKind), String.Empty, CodeSpan.Empty,  - 1 , Severity.Error);
    }

    
//  最终产生输出的程序集
    assemblyGen.Dump();
}

这段代码中,调用到了 PythonCompiler 类自身的私有方法 CompilePythonModule() 来完成编译模块的功能。下面我们来看一下这个方法在做什么:

//  编译模块
private   void  CompilePythonModule( string  fileName, PythonCompilerSink sink,  bool  createMain) {
    
//  设定当前要编译的源文件
    assemblyGen.SetPythonSourceFile(fileName);
    
//  创建编译器环境对象
    CompilerContext context  =   new  CompilerContext(fileName, sink);
    
//  创建分析器
    Parser p  =  Parser.FromFile(state, context);
    
//  调用分析器的分析方法,得到一个语句对象(语句应该是利用了组合模式的一个嵌套的概念,这个语句代表整个文件里的一个大语句)
    Statement body  =  p.ParseFileInput();

    
if  (sink.Errors  >   0 return ;

    
//  创建一个全局套件??有可能是指 globals() 这个字典对象。有待分析。。。
    
//  这里面的 Binder 是干什么的也有待研究。
    GlobalSuite gs  =  Compiler.Ast.Binder.Bind(body, context);
    
string  moduleName  =  GetModuleFromFilename(fileName);
    
//  这里看到了 TypeGen,该类代表一个类型产生器
    
//  tg 指向了一个模块类型(IronPython 中,每一个模块产生为一个对应的类。)
    TypeGen tg  =  OutputGenerator.GenerateModuleType(moduleName, assemblyGen);
    
//  编译模块的 __init__ 方法??(猜测)
    CodeGen init  =  CompileModuleInit(context, gs, tg, moduleName);

    
//  如果需要创建 Main 方法则创建之
     if  (createMain) {
        
//  联想前面一个 CodeGen 的例子,观察调用语句可以想到,方法的产生器是
        
//  CodeGen,而类型的产生器是 TypeGen
        CodeGen main  =  OutputGenerator.GenerateModuleEntryPoint(tg, init, moduleName, referencedAssemblies);
        
//  这里注意到 CodeGen 代码产生器含有一个重要的属性就是代表这个方法
        
//  的反射信息的 MethodInfo, 可以通过这个来调用产生的方法。
        assemblyGen.SetEntryPoint(main.MethodInfo, targetKind);
    }

    
//  因为模块类不是普通的类,需要给他添加一个特殊的标签(Attribute)
    assemblyGen.AddPythonModuleAttribute(tg, moduleName);
    
//  产生类型的动作完毕
    tg.FinishType();
}

在上述两个方法中,我们看到,出现了几个重要的类,它们将是我们下面接着分析的重点线索:

Parser:       分析器
Statement:   语句
GlobalSuite: globals() ??
TypeGen:     类型产生器
CodeGen:     代码产生器(用于产生方法的代码)

在另一个私有方法 CompileModuleUnit 中,主要是进行了一些模块的导入工作,代码很容易懂,这里不详细分析。

现在回头看一下,在 IronPython/Hosting/PythonCompiler.cs 这个文件中,还剩下了两个类没有提到:
ResourceFile: 代表一个资源文件的相关属性。
PythonCompilerSink: 按照字面理解就是 PythonCompiler 的编译结果的接受池。至于它到底如何运作,留待后面再分析。

到这里为止,我们大致上看到了 IronPython 编译器的工作流程,从一系列源代码文件,资源文件,以及其他一些配置属性出发,经过 Parser, 各种 Generator 的运作,最终到达 AssemblyGenerator 的 Dump() 方法,输出编译结果程序集。

以上的代码分析难免有错误之处,尚有待继续挖掘和梳理。


下一篇:IronPython 源码剖析系列(2):IronPython 引擎的运作流程

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值