Wednesday, August 8, 2007

语言是这样炼成的------
深入浅出解析IronPython实现


前言

总的来说,我是一个懒人,做事情经常是没有善终.写这个系列的目的,一是促进一下自己,而是了却一个心愿.

第0章 简介

首先,本系列主要关注的是用一门面向对象的类似C++的语言(C#)实现一门动态的(支持垃圾回收和源编程的)语言的方法.
行文方式大体采用TOP-DOWN的方式,从全局到细节,逐步细化.

阅读本系列,读者最好有Python(或者Lua,Php,Perl,Boo)的使用经验和C#(C++)的基础,如果阅读的过程中出现不明白的地方,推荐还是先Google,再问人.

第1章 启动

IronPython是微软实现的一个基于.Net平台的Python.相对于CPython(也就是通常所说的Python),IronPython在语法层面基本兼容,但是在库的层面还是不能很好的兼容.
(不过本系列的重点不是怎么使用IronPython,而是怎么实现IronPython.)IronPython主要实现语言是C#和IronPython.其中大部分代码是C#的.目前的版本是IronPython2.0A3,
本文以相对稳定的IronPython1.1为蓝本.如果,IronPython2.0正式发布了,可能会考虑同步更新.
在继续阅读前,我希望你已经下载了IronPython1.1的BIN和SRC.(下载的方法问Google)解压出所有的文件放到"C:IronPython-1.1目录"(会有覆盖的提示,直接覆盖就好),并且准备好Visual C# 2005 Express .如果习惯用Source Insight 也可以装一个,并建立工程.
这里提醒一下,SourceInsight用户,可以下载一个Python.CLF文件提升SI对Python的支持,在新建工程的时候也要注意把.py文件加入进来.
好,假设你已经做好了准备.
首先用VC#2005打开"C:IronPython-1.1Src"下的解决方案文件,可以看到有5个工程.选择重新编译解决方案,确认所有工程的都成功的生成.
设置IronPythonConsole为启动项,按F5.可以看到一个控制台界面的IronPython已经在等待我们的输入了.











如果,看不到,应该是没有启动项.
可以输入几个Python语句,运行一下,看看是否正常? ^_^

第2章 从启动开始

让我们继续,先回到,刚刚打开工程的状态,然后,按F10.光标应该停在如图所示的位置:









这里就是IPY的命令行界面的入口函数.
[STAThread]
static int Main(string[] rawArgs) {
...

这个[STAThread]是设置COMInterop的线程模式的,[STAThread]表示是单线程的COM模型.
如果,不明白COM和这个属性,我们后面遇到了再讨论.


该如果函数是位于:IronPythonConsole.PythonCommandLine的.
PythonCommandLine类实现了IPy命令行解析器的大部分封装.

让我们大概看看这个类:


可以看到,这个类的几个主要的方法.






































让我们,继续.按F10.

static int Main(string[] rawArgs) {
List<string> args = new List<string>(rawArgs);//这里是处理命令行参数
ConsoleOptions options = ParseOptions(args);//对参数进行处理
//
// case "-V":
// ConsoleOptions.PrintVersionAndExit = true;
//
if (ConsoleOptions.PrintVersionAndExit) {//-V参数的处理
Console.WriteLine(PythonEngine.VersionString);//对结构来说有点乱
return 0;
}

engine = new PythonEngine(options);//这里实例化一个PythonEngine
CreateMainModule();//创建Python的主模块--"__main__"

接下来,try-finally块用来初始化其他模块


try {
#if IRONPYTHON_WINDOW
MyConsole = new BasicConsole(engine.Sys, ColorfulConsole);//如果是Window模式的话使用基本的命令行类
#else
if (TabCompletion) {
UseSuperConsole(engine);//否则在case "-X:TabCompletion": TabCompletion = true; break;的情况下,启用SuperConsole替代BasicConsole,待会再看两者的区别
} else {
MyConsole = new BasicConsole(engine.Sys, ColorfulConsole);
}
#endif
if (Options.WarningFilters != null)
engine.Sys.warnoptions = IronPython.Runtime.List.Make(Options.WarningFilters);// 设置告警过滤器

engine.Sys.SetRecursionLimit(Options.MaximumRecursion);// 设置一下递归调用的限制,默认是不限制 private static int maximumRecursion = Int32.MaxValue;

// Publish the command line arguments
if (args == null)
engine.Sys.argv = new List();// engine.Sys实际是SystemState的一个实例
else
engine.Sys.argv = List.Make(args);// 保存参数


if (mta) {// 多线程套间模式 case "-X:MTA": mta = true; break;
MTAParameters p = new MTAParameters(engine, args);//
SystemThread thread = new SystemThread(MTAThread);//在独立的线程里面启动Python Engine
thread.SetApartmentState(ApartmentState.MTA);
thread.Start(p);
thread.Join();
return p.result;
} else {
return Run(engine, args == null ? null : args.Count > 0 ? args[0] : null);//在主线程里面启动Python Engine
}
} finally {
try {
engine.Shutdown();// 关闭主线程
} catch (Exception e) {
MyConsole.WriteLine("", Style.Error);
MyConsole.WriteLine("Error in sys.exitfunc:", Style.Error);
MyConsole.Write(engine.FormatException(e), Style.Error);
}

PythonEngine.DumpDebugInfo();// Dump调试信息
}
至此主函数结束,

接下来让我们看看 IronPython 的核心 engine 的实现.

第3章 Python Engine

Hosting目录的UML结构图:


好,我们可以看到整个Hosting模块的核心就是PythonEngine类.

public PythonEngine() {
Initialize(new EngineOptions());
}

public PythonEngine(EngineOptions engineOptions) {
if (engineOptions == null)
throw new ArgumentNullException("engineOptions", "No options specified for PythonEngine");
// Clone it first to prevent the client from unexpectedly mutating it
engineOptions = engineOptions.Clone();
Initialize(engineOptions);
}



这是该类的构造函数.

可见,初始化工作是交由Initialize()方法来实现的,引擎的选项则由EngineOptions类负责.

private void Initialize(EngineOptions engineOptions) {
// make sure cctor for OutputGenerator has run
System.Runtime.CompilerServices.RuntimeHelpers.RunClassConstructor(typeof(OutputGenerator).TypeHandle);

systemState = new SystemState(engineOptions);
defaultModule = new EngineModule("defaultModule", new Dictionary<string, object>(), systemState);
}



初始化函数其实就干了三件事:

1 通过.Net的运行时,运行OutputGenerator类的构造函数.这个是IronPython负责生成.Net IL用的.其生成的方式分成 Snippets 和 Modules

前者是一些小的代码片段,后者则是整块的Python模块.关于该类的细节,后面会有介绍.

2 初始化系统状态对象.该对象负责保存整个IronPython虚拟机全局的环境.

3 初始化defaultModule模块.也就是说,IronPython Engine 其实是作为整个虚拟机的缺省模块被创建的.



至此,我们看完了IronPython engine 的初始化,接下来让我们再回到PythonCommandLine类的Main函数的155行.

return Run(engine, args == null ? null : args.Count > 0 ? args[0] : null);

这里调用了函数 Run(),该函数将根据参数,决定是执行一个文件还是一条交互模式的命令.

RunInteractive(engine);// 运行一条交互模式的命令,是啊,RunInteractiveLoop()

RunFile(engine, fileName);// 运行一个文件


RunInteractiveLoop();是交互命令模式的主循环函数.

private static int RunInteractiveLoop() {
bool continueInteraction = true;
int result = 0;
while (continueInteraction) {
result = TryInteractiveAction(
delegate(out bool continueInteractionArgument) {// 这里,用了一个匿名的代理.
continueInteractionArgument = DoOneInteractive();
return 0;
},
out continueInteraction);
}

return result;
}

DoOneInteractive()是实际干活的方法,TryInteractiveAction()主要用于格式化异常信息.
因为这是命令行交互模式,用户的输入可能有错误,为了避免整个程序异常推出,需要处理所有的异常并给用户一定的提示信息.
public static bool DoOneInteractive() {
bool continueInteraction;
string s = ReadStatement(out continueInteraction);// 从命令行读入一行命令,其实也可以支持多行的定义语句

if (continueInteraction == false)// 是否继续
return false;

if (s == null) {
// Is it an empty line?
MyConsole.Write(String.Empty, Style.Out);
return true;
}

engine.ExecuteToConsole(s);//这里调用IronPython Engine来执行

return true;
}



public void ExecuteToConsole(string text) { ExecuteToConsole(text, defaultModule, null); }// 也就是说,上面的调用实际上是用缺省模块来执行命令行语句

public void ExecuteToConsole(string text, EngineModule engineModule, IDictionary<string, object> locals) {
ModuleScope moduleScope = GetModuleScope(engineModule, locals);// 获得模块的环境(上下文和全局变量)

CompilerContext context = DefaultCompilerContext("<stdin>");// 获取缺省的编译器上下文

Parser p = Parser.FromString(Sys, context, text);// 分析用户输入的命令文本
bool isEmptyStmt = false;
Statement s = p.ParseInteractiveInput(false, out isEmptyStmt);

// 's' is null when we parse a line composed only of a NEWLINE (interactive_input grammar);
// we don't generate anything when 's' is null
if (s != null) {// 这里要判断一下是不是空行
CompiledCode compiledCode = OutputGenerator.GenerateSnippet(context, s, true, false);// 这里是真正的编译
Exception ex = null;

if (consoleCommandDispatcher != null) {
CallTarget0 runCode = delegate() {
try { compiledCode.Run(moduleScope); } catch (Exception e) { ex = e; }// 如果没有什么异常,就执行编译出的代码
return null;
};

consoleCommandDispatcher(runCode);

// We catch and rethrow the exception since it could have been thrown on another thread
if (ex != null)
throw ex;
} else {
compiledCode.Run(moduleScope);// 如果没有什么异常,就执行编译出的代码
}
}
}

这段代码中有两个重点:

OutputGenerator.GenerateSnippet(context, s, true, false);

compiledCode.Run(moduleScope);



先看GenerateSnippet()


public static CompiledCode GenerateSnippet(CompilerContext context, Statement body) {
return GenerateSnippet(context, body, false, false);
}

public static CompiledCode GenerateSnippet(CompilerContext context, Statement body, bool printExprStmts, bool enableDebugging) {
return GenerateSnippet(context, body, context.SourceFile, printExprStmts, enableDebugging);
}


上面调用的是4个参数的版本,是这样调用的:

CompiledCode compiledCode = OutputGenerator.GenerateSnippet(context, s, true, false);


这两个省略参数的版本最终都委托到:

这个方法有点长,不要紧,我们一句句分析:)

public static CompiledCode GenerateSnippet(CompilerContext context/*这是传入的编译器上下文*/, Statement body/*经过分析的语句*/, string name, bool printExprStmts, bool enableDebugging) {
GlobalSuite gs = Ast.Binder.Bind(body, context);// 全局变量

if (name.Length == 0) name = "<empty>"; // The empty string isn't a legal method name
CodeGen cg;// 注意这个类 这是实际负责代码生成的类
List<object> staticData = null;
TypeGen tg = null;

if (enableDebugging) {//
tg = MakeDebuggableSnippetType(name, context.SourceFile);
cg = tg.DefineUserHiddenMethod(MethodAttributes.Public | MethodAttributes.Static,
"Initialize", typeof(object), new Type[] { typeof(ModuleScope) });
cg.typeGen = tg;
cg.ModuleSlot = tg.moduleSlot;
} else {
cg = snippetAssembly.DefineDynamicMethod(name, typeof(object), new Type[] { typeof(ModuleScope) });
staticData = new List<object>();
cg.staticData = staticData;
cg.doNotCacheConstants = true;
cg.ModuleSlot = cg.GetLocalTmp(typeof(PythonModule));
}

cg.ContextSlot = cg.GetArgumentSlot(0);
cg.Names = CodeGen.CreateFrameNamespace(cg.ContextSlot);
cg.Context = context;
cg.printExprStmts = printExprStmts;
if (printExprStmts) {
cg.Names.EnsureLocalSlot(SymbolTable.Underscore);
}

cg.ContextSlot.EmitGet(cg);
cg.EmitFieldGet(typeof(ModuleScope), "__module__");
cg.ModuleSlot.EmitSet(cg);

if (context.TrueDivision) {
cg.ContextSlot.EmitGet(cg);
cg.EmitInt(1);
cg.EmitCall(typeof(ICallerContext), "set_TrueDivision");
}

Slot dummySlot = null;
// Emit a try/catch block for TraceBack support, except for simple return statements
if (!(body is ReturnStatement)) {
// Try block may yield, but we are not interested in the isBlockYielded value
// hence push a dummySlot to pass the Assertion.
dummySlot = cg.GetLocalTmp(typeof(object));

cg.EmitTraceBackTryBlockStart(dummySlot);
cg.FuncOrClassName = "<module>";
cg.EmitSetTraceBackUpdateStatus(false);
}

gs.Emit(cg);

if (!(body is ReturnStatement)) {
// free up the dummySlot
cg.FreeLocalTmp(dummySlot);
cg.EmitTraceBackFaultBlock();
cg.EmitPosition(Location.None, Location.None);
cg.EmitReturn(null);
}
cg.Finish();

if (tg != null) tg.FinishType();

CompiledCode compiledCode = new CompiledCode(name,
(CompiledCodeDelegate)cg.CreateDelegate(typeof(CompiledCodeDelegate)),
staticData);
return compiledCode;
}













































Wednesday, July 4, 2007