语言是这样炼成的------
深入浅出解析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;
}