MYC编译器源码分析

前文.NET框架源码解读之MYC编译器讲了MyC编译器的架构,整个编译器是用C#语言写的,上图列出了MyC编译器编译一个C源文件的过程,编译主路径如下:

  1. 首先是入口Main函数用来解析命令行参数,读取源文件,并开始编译过程。Main函数在MyC.cs文件,而IO.cs文件主要保存读取源码文件的相关操作。下表是Main函数的源码(批注用注释的方式显示),IO.cs文件用单独的一个小节说明:
public static void Main()
{
    try
{
 // 看源码注释,代码是99年写的,也就是说.NET当年正在开发中
    // 可能那个时候虚拟机都没有做好向Main函数传递命令行参数的开发
    // 用了下面这个奇葩方法获取程序的命令行参数
        String[] args = Environment.GetCommandLineArgs();
        // 初始化读取源文件的IO对象,该对象负责将源文件以字节流的方式
        // 输出给下一个对象 – 词法分析
        Io prog = new Io(args);
        // 词法分析对象,该对象的工作是过滤掉源码中不必要的字符,比如空格
        // 注释之类的,并且把源码中的字符归类 – Tokenize,以便语法分析器
        // 更方便的解析语法
        Tok tok = new Tok(prog);
        // 语法分析对象,解析完毕后即是代码生成阶段,但一般语法分析过程
        // 都只会生成语法树,这样的设计可以对接多种结果文件输出手段。比如
        // 说,本例中生成可执行文件的exe.cs和生成IL源码的asm.cs都是通过
        // 遍历语法树,使用不同的输出策略生成结果文件的
        Parse p = new Parse(prog, tok);
        // 采用自顶向下的方式进行语法解析
        p.program();
        // 编译工作已经完成,关闭打开的源文件句柄等资源
        prog.Finish();
    }
    catch (Exception e)
{
    // 编译过程中有任何错误,即中断处理,打印错误消息并退出程序
        Console.WriteLine("Compiler aborting: " + e.ToString());
    }
}

  

  1. MyC的语法很简单,因此编译过程是很干净的词法分析、语法分析、代码生成和结果输出的过程。其中词法分析代码在tok.cs,语法分析代码在parse.cs中,Emit.cs处理代码生成,而Asm.cs和Exe.cs分别根据命令行参数的设置,来生成最终的可执行文件并选择是否输出IL源码。 IO.cs - IO处理 将源文件读取进内存,并采用流式处理的代码都放在IO这个类里面,IO的构造函数解析命令行参数,并打开源文件,等待Tok.cs里面代码的指令将源文件的字符一个个读进内存并处理,下面是它的构造函数的源码:
public Io(String[] a)
  {
  int i;

  args = a;
  // 解析命令行参数,并根据参数打开内部的控制开关,详情请看下面对ParseArgs
  // 函数的源码解读
  ParseArgs();

  // 打开要编译的源文件
  ifile = new FileStream(ifilename, FileMode.Open,
             FileAccess.Read, FileShare.Read, 8192);
  // 如果源文件不存在,报错退出
  if (ifile == null)
    {
    Abort("Could not open file ‘"+ifilename+"‘\n");
}
  // 采用流式处理方式读取源文件
  rfile = new StreamReader(ifile); // open up a stream for reading

  // 根据源文件的名称设定结果输出文件的文件名
  i = ifilename.LastIndexOf(‘.‘);
  if (i < 0)
    Abort("Bad filename ‘"+ifilename+"‘");
  int j = ifilename.LastIndexOf(‘\\‘);
  if (j < 0)
    j = 0;
  else
    j++;

  classname = ifilename.Substring(j,i-j);

  // 根据命令行参数决定是生成.exe、.dll等可执行文件,还是输出包含
  // IL源码的.lst文件
  if (genexe)
    ofilename = classname+".exe";
  if (gendll)
    ofilename = classname+".dll";
  if (genlist)
{
// 如果是要输出IL源码,因为原来的可执行文件也要输出,需要创建一个新的文件
    lst_ofilename = classname+".lst";
    lst_ofile = new FileStream(lst_ofilename, FileMode.Create,
             FileAccess.Write, FileShare.Write, 8192);
    if (lst_ofile == null)
      Abort("Could not open file ‘"+ofilename+"‘\n");
    lst_wfile = new StreamWriter(lst_ofile);
    }
  }

  

编译器是在IO类里处理命令行参数的,参数解析实际上是一些字符串处理的活,本文解释下关键代码:

void ParseArgs()
  {
  int i = 1;

  // 程序至少需要两个参数,否则就输出帮助文字并退出
  if (args.Length < 2)
    {
    Abort("myc [/debug] [/nodebug] [/list] [/dll] [/exe] [/outdir:path] filename.myc\n");
    }

  // 逐个遍历命令行参数
  while (true)
    {
    if (args[i][0] != ‘/‘)
      break;
    // 处理 /? 这个参数,即输出帮助文本
    if (args[i].Equals("/?"))
      {
      Console.WriteLine("Compiler options:\n  myc [/debug] [/nodebug] [/list] [/dll] [/exe] [/outdir:path] filename.myc\n");
      Environment.Exit(1);
      }
// 如果有 /debug 参数,则打开内部的 gendebug 开关,这个开关在代码生成的过程
// 中会用到
    if (args[i].Equals("/debug"))
      {
      gendebug = true;
      i++;
      continue;
      }
// ... ... 跳过类似的代码
// 如果有 /outdir 参数,则获取命令行中指定的目录路径
    if (args[i].Length > 8 && args[i].Substring(0,8).Equals("/outdir:"))
      {
      genpath = args[i].Substring(8);
      i++;
      continue;
      }
// 前面那么多的if相当于switch … case … default 块里面的 case 处理路径
// 下面这段代码即是 default 处理路径 – 如果命令行参数符合前面的if条件
// 都会执行里面的 continue 子句跳出循环,能执行到这里,说明参数
// 是无法识别的参数,因此报告错误并退出执行
    Abort("Unmatched switch = ‘"+args[i]+"‘\nArguments are:\nmyc [/debug] [/nodebug] [/list] [/dll] [/exe] [/outdir:path] filename.myc\n");
    }

  // 如果前面的循环执行完毕,还有参数列表未处理,说明输入了不支持的参数
  if (args.Length-i != 1)
    {
    Abort("myc [/debug] [/nodebug] [/list] [/dll] [/exe] [/outdir:path] filename.myc\n");
}

  // 最后一个参数是要编译的源文件路径
  ifilename = args[args.Length-1]; // filename is last
  }

  

IO类中大部分函数都是为Tok.cs服务的,因此其它函数在解释词法分析的时候说明

时间: 2024-10-01 23:39:19

MYC编译器源码分析的相关文章

MYC编译器源码之词法分析

前文  .NET框架源码解读之MYC编译器 和 MYC编译器源码分析之程序入口 分别讲解了 SSCLI 里示例编译器的架构和程序入口,本文接着分析它的词法分析部分的代码. 词法解析的工作都由Tok类处理,其构造函数接受一个Io对象做文件处理,下面是Tok构造函数的源码: public Tok(Io ihandle) { io = ihandle; // 初始化Token(字符归类)字典 InitHash(); // initialize the tokens hashtable // 读入文件的

MYC编译器源码之语法分析

MyC编译器采用自顶向下的方法进行语法解析,这种语法解析方式,一般是从最左边的Token开始,然后自顶向下看哪一条语法规则可能包含这个Token,如果包含这个Token,则自左向右根据这条语法规则逐一匹配后面的Token.自顶向下的语法解析我会在其他文章中说明,在前文我们已经列出了MyC的语法规则: program ::= ( outer_decl | func_decl ); outer_decl ::= [ class ] type ident { "," ident } &quo

MYC编译器源码之代码生成

前面讲过语法的解析之后,代码生成方面就简单很多了.虽然myc是一个简单的示例编译器,但是它还是在解析的过程中生成了一个小的语法树,这个语法树将会用在生成exe可执行文件和il源码的过程中. 编译器在解析时,使用emit类来产生中间的语法树,语法树的数据结构和操作方法在iasm这个类型里完成,源程序的语法解析完毕后,Exe和Asm两个类分别遍历生成的语法树产生最终的代码. 我们来看几个代码的例子,下表的函数 Parser.program 里,在函数开始和结束的地方分别调用了 prolog 和 ep

Linux内核源码分析--内核启动之(3)Image内核启动(C语言部分)(Linux-3.0 ARMv7) 【转】

原文地址:Linux内核源码分析--内核启动之(3)Image内核启动(C语言部分)(Linux-3.0 ARMv7) 作者:tekkamanninja 转自:http://blog.chinaunix.net/uid-25909619-id-4938390.html 在构架相关的汇编代码运行完之后,程序跳入了构架无关的内核C语言代码:init/main.c中的start_kernel函数,在这个函数中Linux内核开始真正进入初始化阶段, 下面我就顺这代码逐个函数的解释,但是这里并不会过于深入

Spark SQL Catalyst源码分析之TreeNode Library

前几篇文章介绍了Spark SQL的Catalyst的SqlParser,和Analyzer,本来打算直接写Optimizer的,但是发现忘记介绍TreeNode这个Catalyst的核心概念,介绍这个可以更好的理解Optimizer是如何对Analyzed Logical Plan进行优化的生成Optimized Logical Plan,本文就将TreeNode基本架构进行解释. 一.TreeNode类型 TreeNode Library是Catalyst的核心类库,语法树的构建都是由一个个

Linux内核源码分析方法

  一.内核源码之我见 Linux内核代码的庞大令不少人“望而生畏”,也正因为如此,使得人们对Linux的了解仅处于泛泛的层次.如果想透析Linux,深入操作系统的本质,阅读内核源码是最有效的途径.我们都知道,想成为优秀的程序员,需要大量的实践和代码的编写.编程固然重要,但是往往只编程的人很容易把自己局限在自己的知识领域内.如果要扩展自己知识的广度,我们需要多接触其他人编写的代码,尤其是水平比我们更高的人编写的代码.通过这种途径,我们可以跳出自己知识圈的束缚,进入他人的知识圈,了解更多甚至我们一

tomcat源码分析前的准备工作

Tomcat源码学习前的准备工作 注:由于网上的帖子大部分没有配套的图片和错误的分析,所有费了半天劲整理了此篇博客,希望大家少走弯路吧 下面我们就开始我们的Tomcat源码学习之旅. 1. 下载Tomcat6.0的源代码 首先,我们得下载Tomcat6.0的源代码.Tomcat源代码的版本控制工具不是CVS,而是Subversion,如果您的机器上没有安装Subversion,请从http://subversion.tigris.org/servlets/ProjectDocumentList?

Opencv2.4.9源码分析——Cascade Classification(三)

前两篇文章分别介绍了级联分类器的原理和源码解析,下面我们给出一个具体的应用实例. 下面我们以车牌识别为例,具体讲解OpenCV的级联分类器的用法.在这里我们只对蓝底白字的普通车牌进行识别判断,对于其他车牌不在考虑范围内.而且车牌是正面照,略微倾斜可以,倾斜程度太大也是不在识别范围内的. 我们通过不同渠道共收集了1545幅符合要求的带有车牌图像的照片(很遗憾,我只能得到这么多车牌照片,如果能再多一些就更好了!),通过ACDSee软件手工把车牌图像从照片中剪切出来,并统一保存为jpg格式.为便于后续

C++:浅谈c++资源管理以及对[STL]智能指针auto_ptr源码分析,左值与右值

C++:浅谈c++资源管理以及对[STL]智能指针auto_ptr源码分析 by 小威威 1. 知识引入 在C++编程中,动态分配的内存在使用完毕之后一般都要delete(释放),否则就会造成内存泄漏,导致不必要的后果.虽然大多数初学者都会有这样的意识,但是有些却不以为意.我曾问我的同学关于动态内存的分配与释放,他的回答是:"只要保证new和delete成对出现就行了.如果在构造函数中new(动态分配内存),那么在析构函数中delete(释放)就可以避免内存泄漏了!" 事实果真如此么?