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() 方法,输出编译结果程序集。

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

出处:http://www.cnblogs.com/RChen/archive/2006/10/09/ipysrcstudy1.html

时间: 2024-10-10 17:17:43

IronPython 源码剖析系列(1):IronPython 编译器的相关文章

WorldWind源码剖析系列:星球类World

星球类World代表通用的星球类,因为可能需要绘制除地球之外的其它星球,如月球.火星等.该类的类图如下. 需要说明的是,在WorldWind中星球球体的渲染和经纬网格的渲染时分别绘制的.经纬网格的渲染过程请参见文章<WorldWind源码剖析系列:星球经纬度格网的绘制>,是通过Form.OnPaint()函数激活.刷新和绘制的.星球球体的渲染过程请参见文章<WorldWind源码剖析系列:星球球体的加载与渲染>.而星球类World是绘制过程中从XML配置文件中读取参数构造的用来代表

WorldWind源码剖析系列:星球球体的加载与渲染

WorldWind源码剖析系列:星球球体的加载与渲染 WorldWind中主函数Main()的分析 在文件WorldWind.cs中主函数Main()依次作以下几个事情: 1.  使用System.Version在内部,读取软件版本信息,并格式化输出.我们在外面配置软件版本,“关于”部分中版本自动更改. 获取格式化版本号 // Establish the version number string used for user display, // such as the Splash and 

【java集合框架源码剖析系列】java源码剖析之TreeMap

注:博主java集合框架源码剖析系列的源码全部基于JDK1.8.0版本.本博客将从源码角度带领大家学习关于ArrayList的知识. 一TreeMap的定义: public class TreeMap<K,V> extends AbstractMap<K,V> implements NavigableMap<K,V>, Cloneable, java.io.Serializable 可以看到TreeMap是继承自AbstractMap同时实现了NavigableMap,

React 源码剖析系列 - 生命周期的管理艺术

目前,前端领域中 React 势头正盛,很少能够深入剖析内部实现机制和原理. 本系列文章 希望通过剖析 React 源码,理解其内部的实现原理,知其然更要知其所以然. 对于 React,其组件生命周期(Component Lifecycle)是它的核心概念,本文从源码入手,来剖析 React 生命周期的管理艺术. 阅读本文需要对 React 有一定的了解,如果你不知何为组件的生命周期,请详读 React 生命周期的文档. 如果你对 React 组件的生命周期存在些许疑惑,如生命周期如何顺序管理:

thinkphp5源码剖析系列1-类的自动加载机制

前言 tp5想必大家都不陌生,但是大部分人都停留在应用的层面,我将开启系列随笔,深入剖析tp5源码,以供大家顺利进阶.本章将从类的自动加载讲起,自动加载是tp框架的灵魂所在,也是成熟php框架的必备功能 入口 // [ 应用入口文件 ] namespace think; // 加载基础文件 require __DIR__ . '/../thinkphp/base.php'; base.php <?php // +------------------------------------------

WorldWind源码剖析系列:挂件类Widgets

WorldWindow用户定制控件类中所包含的的挂件类Widgets控件主要有如下图所示的派生类.它们的类图如下所示. 鉴于挂件类Widgets及其派生类,相对简单,基本上都是些利用DirectX3D进行绘图和处理图标纹理影像等的操作,此处不再对各个类的主要的字段.属性和方法进行描述了.感兴趣的读者可以直接阅读源码.建议阅读源码之前读者应具备一定的DirectX3D开发基础. 挂件PictureBox类被WavingFlags.TimeController等插件引擎子类所引用. 挂件Button

WorldWind源码剖析系列:表面瓦片类SurfaceTile

表面瓦片类SurfaceTile描述星球类(如地球)表面纹理影像的瓦片模型.其类图如下. 表面瓦片类SurfaceTile包含的主要的字段.属性和方法如下: int m_Level;//该瓦片所属金字塔影像的层级 double m_North;//该瓦片北边界 double m_South; //该瓦片南边界 double m_West; //该瓦片西边界 double m_East; //该瓦片东边界 bool m_Initialized = false; //该瓦片是否已被初始化 Devic

WorldWind源码剖析系列:表面影像类SurfaceImage

表面影像类SurfaceImage描述星球类(如地球)表面纹理影像.该类的类图如下. 表面影像类SurfaceImage包含的主要的字段.属性和方法如下: string m_ImageFilePath;//影像文件的路径 double m_North;//影像文件的北部边界 double m_South;//影像文件的南部边界 double m_West;//影像文件的西部边界 double m_East; //影像文件的东部边界 Texture m_Texture = null;//用影像文件

WorldWind源码剖析系列:星球表面渲染类WorldSurfaceRenderer

星球表面渲染类WorldSurfaceRenderer描述如何渲染星球类(如地球)表面影像纹理.该类的类图如下. 星球类World包含的主要的字段.属性和方法如下: public const int RenderSurfaceSize = 256;//定义渲染表面尺寸的常量 RenderToSurface m_Rts = null;//D3D定义的类型 const int m_NumberRootTilesHigh = 5;//根瓦片高度数,即纬度方向上划分的瓦片数.经度方向上划分的瓦片数为该值