“如果你不了解编译器是如何工作的,那么你就不了解计算机是如何工作的。如果你无法确保100%掌握编译器的工作原理,那么你就是不了解其工作原理” — Steve Yegge
好了,思考一下这个问题。无论你是菜鸟,还是经验丰富的软件开发人员,如果你不了解编译器和解释器的工作原理,那么你就不了解计算机的工作原理。就是这么简单。
因此,你知道编译器和解释器的工作原理吗?我的意思是,你必须确保100%掌握其工作原理,如果你无法确保这点。
或如果你不了解其工作原理,并为之焦虑不安。
其实完全不必担心。如果你能坚持下来,完成这一系列任务,并跟着我编写一个解释器和编译器,最后你会掌握它们的工作原理。同时你也会为此感到快乐且信心百倍。至少我希望实现这种效果。
为什么你要学习解释器和编译器?我给出了三个原因。
写一个解释器或一个编译器你必须要有与之匹配的一些技术能力。写一个解释器或一个编译器将会帮你提升那些技能,并且会成为一个更好的软件开发人员。同时,这些技能对于写任何软件都是有帮助的,不局限于解释器或编译器。
你真正想要知道计算机是如何工作的。通常解释器和编译器看起来就像魔法。并且你不会对这个魔法感到舒服。你想要阐述建立一个解释器和编译器的过程,理解他们是怎么工作的,让这些东西处在控制之中。
你希望创建你自己的编程语言或者支配某个特殊的语言。如果你创造了这样一门语言,你同时也会需要制作一个解释器或者编译器给这个语言。最近,你要是对新的编程语言兴趣再起。你会看到新的编程语言每天都在出现:Elixir,Go,Rust 这里仅举几例。
好的,那什么是解释器和编译器呢?
一个解释器或编译器的目标是将某些高级语言的源程序翻译成其他的形式。解释的相当含糊,不是吗?再忍我一下,之后的一系列章节你将能够确实地了解源程序被翻译成什么。
在这个时间点上,你可能会好奇到底解释器和编译器之间有什么差别呢。为了达成这个系列的目的,让我们同意以下解释:如果翻译器把源程序翻译成了机器语言,他就是一个编译器。如果源程序没先被翻译成机器语言,翻译器就处理并且执行了源程序,他就是一个解释器。形象地看起来就像这样:
我希望截至目前你已经确信你真的想要学习并构建一个解释器和编译器,那你可以期望从这个关于解释器的系列中获得什么呢?
直说了吧,你我打算为 Pascal 语言大子集实现一个简单的解释器。在该系列最后部分,你会得到一个可工作的 Pascal 解释器,和一个类似 python 中 pdb 的源码级别的调试器。
你或许会问,为什么选 pascal?首先,它不是一个虚假的语言,所以我选中它在这个系列中使用:这是一个真实的编程语言,具备许多重要的语法结构。它虽然有点古老了,但仍被使用。CS(注,计算机科学)书籍使用 pascal 编程语言实现书中示例 (我知道,选择为这个语言实现解释器的理由并不充分,但我想,这也是一个学习非主流语言的好机会:)
下面例子使用 pascal 写的阶乘函数实现。可以用你自己构建的解释器解析,还可以使用后面将实现的调试器做代码级别的交互式调试。
我们使用 python 作为 Pascal 解释器的实现语言,当然,你可以使用任何你熟悉的语言(来实现),因为实现原理并不依赖具体的语言特性。Okay, 该做正事了。 各就各位,预备,开始!
开始制作解释器和编译器的第一步尝试是写一个简单的算术表达式解释器,也叫计算器。.今天的目标很简单:能处理计算两个个位整数相加,如 3+5。下面是是计算器,啊不,解释器的源代码:
将上面代码保存为 calc1.py 文件,或者直接从 GitHub 中 下载。 在你打算深入研究代码之前,先在命令行中运行它,观察它的动作。自己动手试试! 下面是我笔记本电脑上的会话(注,交互式执行)示例 (如果你在 python3 上运行计算器, 你应该是用 input,而不是 raw_input):
为确保你的简单计算器能正确执行,不抛出异常,你的输入需满足以下指定的规则:
输入中只允许出现个位数整数
当前算术运算符解释器只支持加法运算
输入中的任何位置不能出现空白(注,空格,TAB等)
这些限制对保持计算器简单很有必要。别担心,很快,你的实现就会越来越复杂的。
Okay,现在我们研究下,你的解释器如何工作,如何计算算术表达式的。
你在命令行输入表达式 3+5 ,你的解释器会得到一个字符串 “3+5”。 为能准确理解并处理字符串,解释器第一步要将输入的“3+5” 分割成多个部件,这些部件我们称为标记符(token),标记符是指对象,它具有类型和值属性。例如,字符串“3”,标记符号的类型是整型(INTEGER),对应的值是整数3。
将输入字符串分割为标记符的过程称为文法分析。所以,解释器的第一步工作是读取输入的字符序列,并将其转换为标记符流。解释器中处理该部分工作的部分称为文法分析器,或叫词法分析器。简而言之,你或许听到过该部件的其他命名,如 scanner (扫描器)或 tokenizer (标记符生成器)。这指的都是一回事:解释器或编译器的该部件将输入的字符序列转变成标记符号流。
Interpreter 类中的函数 get_next_token 是文法分析器。每次调用它时,从输入给解释器的字符序列输入中,会得到相邻下一个标记符(的起始位置索引)。咱们仔细看一下这个函数本身,研究下,它是如何完成将字符序列转换成标记符工作的。输入保存到变量 text 中,text 变量存储输入字符串,而 pos 变量则是字符串的索引(将字符串看成一个字符数组)。pos 的初始值为 0,指向字符‘3’。 函数首先检查字符是否是一个整型数字,如果是,pos 变量加 1,返回 ‘3’的标记符实例,其类型是整型(INTEGER),值是3:
pos 索引现在指向 text 变量中的‘+’ 字符。你再次调用该函数时,它测试 pos 索引指向的 text 中的字符是否是整型数字,而测试的结果是一个加号。结果,函数使 pos 变量自增,返回新创建的标记符实例,类型是 PLUS,值是‘+’。
pos 变量指向字符‘5’。你又一次调用函数 get_next_token,来检查当前指向的字符是否整型数字, 这次是了,因此,pos 变量自增,返回新的标记符实例,其类型是整型( INTEGER),值是 5:
由于 pos 索引当前指向字符串“3+5” 的最后位置,你再调用函数 get_next_token,则返回 EOF标记符:
观察计算器的词法分析器部件(程序)是如何工作的:
好的,既然你的解释器能访问(获取)输入字符序列生成的标记符流,解释器需要用它进一步处理:它需要从词法分析器函数 get_next_token 生成的序化(线性)的标记符流中,找出数据的结构:INTEGER -> PLUS -> INTEGER。也就是说,它尝试找出标记符序列:一个整数,接着是加号,之后是一个整数。
负责发现和解释数据结构的函数是 expr。该函数校验(验证)标记符序列是否与预期序列一致,比如,INTEGER -> PLUS -> INTEGER。在结构确认无误后,它将加号左边标记符的值与其右边标记符值相加,生成(表达式的)结果,这样,就将你传给解释器的表达式,成功计算出算术表达式的结果。
expr 函数本身调用了助手函数 eat 校验输入标记符参数的类型与当前的标记符类型是否匹配。匹配输入标记符类型后,eat 函数获取下一个标记符,将其赋值给 current_token 变量,看起来像正在吞食当前匹配的标记符,并不断将虚拟指针指向标记符序列流的下一个位置。如果标记符序列流的结构与预期的 INTEGER PLUS INTEGER 序列并不一致的话,eat 函数则会抛出异常。
我们复述下,你的解释器验算(evaluate,原意是评价)算术表达式时,都做了哪些工作(或都发生了什么):
解释器获取了一个输入字符串,就是 “3+5”
解释器条用 expr 函数找出由文法分析器 get_next_token 返回的标记符序列流的结构。结构应该是<INTEGER PLUS INTEGER>形式。确认结构无误后,它解析输入为两个整型标记符的值之和,原因很明显,此时,解释器需要做的事就两个整数 3,5 求和运算。
自豪吧!你已经能构建属于你自己的解释器了!
现在,该动手练习了。
你不会认为读完这篇文章(掌握构建解释器)就够了吧? Okay,别让手闲着,完成下面的练习:
修改代码,允许多位数整数输入,如 “12+3”
增加函数能忽略空白字符,则计算器可以接受含空白的输入,如 ”12 + 3“
修改代码,将 ‘+’替换成‘-‘,实现 “7-5”表达式的减法运算。
测验你是否理解
什么是解释器?
什么是编译器?
解释器和编译器的区别是什么?
标记符是什么?
将输入分割为标记符的进程名叫什么?
解释器中,处理文法分析的部件叫什么?
解释器或编译器中的上题6的部件,其他通用名怎么称呼,请列举出来。
在本文结束之前,我真切希望你正在学习解释器和编译器。希望你马上就学,而不是扔到一边。别等了。如果你已经概略看过本位,再读一遍。如果你认真读过,但没有完成练习——那就现在开始(做练习)。如果你没有做完,那完成它。你读懂大意,理解怎么回事了吗?签署保证书,今天就开始学习解释器和编译器!
本人, ________, 思想健全,身体健康,在此郑重声明,今天开始学习解释器和编译器,每一处,我都要100%掌握它们是如何工作的!
签名:
日期:
签好,注明日期, 放到每天都能看到的显眼处,以确保你坚持你的承诺。牢记承诺的定义:
“Commitment is doing the thing you said you were going to do long after the mood you said it in has left you.” — Darren Hardy
Okay, 今天的事到此结束。该 mini 系列的后一篇,扩展你的计算器,处理更多的算术表达式。请继续关注。
如果你等不及第二篇文章,迫不及待地想深入学习解释器和编译器, 下面的推荐书目能帮到你:
语言实现模式: 构建专属领域和通用编程语言 (程序员实用手册)
实现编译器和解释器: 以软件工程师方式
现代 编译器和解释器,java实现
现代编译器设计
编译器:原理, 技术, 和工具 (2nd Edition)
原文出处: 银杏花下 欢迎分享原创到 伯乐头条
Notepad++是一个小巧精悍的编辑器,其使用方法我就不多说了,由于notepad++是使用c++封装的windows句柄以及api来实现的,因此对于其源码的研究有助于学习如何封装自己简单的库(当然不是MTL、MFC或者QT那样大型的库)。Notepad++源码: https://github.com/notepad-plus-plus/notepad-plus-plus/releases/tag/v6.7.9.2。
下面是Notepad++源码的目录:
其主目录如第一张图所示,包含了两个开源工程,第一个PowerEditor就是notepad++;第二scintilla是一个代码编辑器的开源库,十分强大,许多编辑器都是基于这个库封装起来的,对于scintilla来说可以实现代码折叠、标记、缩进等等很多功能,具体情况可以去scintilla的官网以及一些博客来了解如何使用这个开源库:
http://www.scintilla.org/
http://www.cnblogs.com/superanyi/archive/2011/04/07/2008636.html
将第一个工程PowerEditor打开之后将如上右图所示,这里最重要的是src源码文件以及installer中的配置文件。当我用vs2012打开visual.net打开工程文件notepadPlus.vs2005.vcproj后出现了几个问题,第一个问题,就是一大堆找不到预编译头文件,解决方法是不使用预编译头文件即可。第二个问题出现在Sorters.h头文件中,vs2012虽然实现了c++11的部分特性,但是却没有实现std::unique_ptr,由此引发出很多语法错误,解决办法可以有自己写一个类似于unique_ptr这样的智能指针或者有办法替换vs2012内置编译器也可以。我比较懒,恰好笔记本中安装有vs2013,是支持unique_ptr的,我就直接换环境了。
然后程序跑起来还是有一些问题,在WinMain中作者对一些左值进行了赋值,语法直接报错了,对于这个直接注释掉这一行即可,其次再删除一个预编译源文件和实现一个函数声明之后程序就跑了起来,之后的小问题就不多说了,因为本文主要是讲notePad++的运行机制是啥。我稍微统计了下,整个工程大概是21W行代码,加上注释也比较少,实在花了我好几天才摸清楚大概情况。
从界面开始说起,整个工程中的窗口都是继承自Window这个类的,这个类封装最重要的一个成员就是HWND _hSelf,这个就是用来存放CreateWindow函数返回的窗口句柄,其次就是父窗口句柄HWND _hParent,以及实例HINSTANCE _hInst。还提供了许多窗口都能够用到的方法,都是以虚函数的方法来提供的,比如display函数用来显示窗口,reSizeTo用来调整窗口,redraw用来重绘窗口等等许多函数。有了Window类,后面的就容易理解一些了,其中整个NotePad++的主窗口类Notepad_plus_Window继承自Window,notepad++中所有的对话框都是继承自StaticDialog的,而StaticDialog也是继承自Window这个父类的。下面是Window类的源码:
从直观上来说,因为像菜单栏、工具栏、编辑框等等这些窗口应该属于主窗口,不过作者在主窗口Notepad_plus_Window和这些子窗口中间添加了一层,将所有的子窗口对象都封装在了Notepad_plus这个类中,再由Notepad_plus_Window来封装Notepad_plus对象_notepad_plus_plus_core。这样一来让主窗口的代码和子窗口的一些实现分离了,让Notepad_plus_Window的功能变得很清晰,不过Notepad_plus这个类因为封装可大量的子窗口对象变得十分复杂,另一个问题就是这些子窗口的父窗口需要指定,但是这个父窗口句柄被封装在Notepad_plus_Window中,于是Notepad_plus类中又封装了Notepad_plus_Window对象指针,机智的通过编译又能够拿到父窗口句柄了。下面是Notepad_plus_Window源码:
接下来就从WinMain这个函数(源码在下面)入口来讲解程序是怎么跑起来的,首先NotePad++是能够接受命令行的,从最开始就使用GetCommandLine()来接受命令行,作者为了更好的支持命令行,写了两个类ParamVector和CmdLineParams来分别保存命令行以及根据这些命令决定了程序运行的一些属性(如是否允许插件、可读性等等)。作者将GetCommandLine()返回的LPTSTR类型变量使用算法parseCommandLine()保存到了ParamVector对象中,在利用CmdLineParams方法isInList()来判断是否命令行带有某些程序运行属性,并将其保存在CmdLineParams对象中,这里有些属性马上就用到,有些属性都是很久之后才用到的。
因为NotePad++将许多配置信息都保存在了本地文件中,比如哪国语言、整体风格、用户快捷键等等都是如此,因此在命令行之后就应该处理好这些参数,以让窗口和子窗口显示出来时候都是按照以前设置的配置来的。这里作者创建NppParameters类来控制配置信息,这个类的头文件和源文件也是多的够呛,分别是1700行和6500行。总的来说就是把本地的配置信息读取到内存中来,NppGUI用来保存界面配置,ScintillaViewParams用来保存ScintillaView的配置,LexerStylerArray和StyleArray用来保存颜色以及字体,还有许多vector类来保存各种快捷键。这些配置的载入时从NppParameters的load()函数运行开始的,这些配置文件都应该跟程序在同一个文件夹下,因为代码中默认在程序运行的同一路径之下去查找这些配置文件的,在经过读取config.xml、stylers.xml、userDefineLang.xml、nativeLang.xml、toolbarIcons.xml、shortcuts.xml、contextMenu.xml、session.xml、blacklist.xml这些配置文件读入之后,load()函数就返回了,有读当然有写,写函数也是定义在NppParameters类中。其实只要找到一个配置文件debug一趟就明白来龙去脉了。
之后回到WinMain中判断程序是否允许多实例,如果不允许多实例并且还不是第一个启动的实例的话,就直接用::FindWindow()找到已经存在在内存中的窗口就好,之后显示这个主窗口,如果有参数的话就把参数利用::SendMessage()以WM_COPYDATA的消息传递过去,之后返回。如果是允许多实例(用户可以在首选项设定)或者是第一次启动NotePad++的话直接跳过这段往后执行。
如果是一个新的实例的话,先创建主界面封装类Notepad_plus_Window对象notepad_plus_plus留着后面用。紧接着程序看看当前目录下的updater目录下有没有GUP.exe这个程序,这个程序是用来升级NotePad++的,如果当前的日期比较陈旧并且存在这个程序并且操作系统比XP新再并且是第一个NotePad++实例的话就运行这个NotePad++的程序,如果有一个不符合就过喽。
之后当我第一次看到了MSG msg;这句代码,我很高兴,说明消息循环要开始了,后面要透明了,但是在此之前首先执行了notepad_plus_plus.init(hInstance, NULL, quotFileName.c_str(), &cmdLineParams); notepad_plus_plus我之前说过是主界面对象,这个对象在初始化的时候基本没干什么事情,反而在这里要露肌肉了,因为init()函数太过庞大,容我先传上WinMain函数的代码再来解释这个函数:
现在进入主窗口对象notepad_plus_plus的init()函数进行探究,init()函数的第一件事情就是创建窗口类,这里指定了主窗口的整体风格以及其菜单名称,这里的菜单就是作者事先准备好的菜单资源,这个资源定义在Notepad_plus.rc资源脚本中。之后就是大家熟知的注册窗口类,用CreateWindowEx创建窗口,返回的句柄保存在从Window继承下来的_hSelf成员变量中。因为有CreateWindowEx这个函数,系统会发送WM_CREATE消息到消息队列中,因为这个消息比较特殊,在消息循环未建立好之前也会被回调函数捕捉处理。因此在CreateWindowEx函数之后就应该转到窗口句柄对应的回调函数去,这个函数的实现位于NppBigSwitch.cpp中,下面是这个函数,只有WM_CREATE是现场处理的,其他的消息都被转到了_notepad_plus_plus_core的process函数中去了:
在处理WM_CREATE消息中调用了_notepad_plus_plus_core的init()函数,按照程序执行的步骤来讲解,这里也转到init()函数内部来进行讲解,先理一下顺序:WinMain->notepad_plus_plus.init()->CreateWindwEx()->WM_CREATE->_note_plus_plus_core.init();当然只有WM_CREATE会被首先捕捉处理,其他的消息仍然是在构建好消息循环之后接受处理的。
程序首先判定语言菜单项是不是紧凑型的,如果是紧凑型的话就将现有的语言菜单栏给移走,因为这里需要改变语言栏的形状,因为_notepad_plus_plus_core中已经定义了两个ScitillaEditView(开源项目scintilla的封装)对象_mainEditView和_subEditView,这两个很容易理解,因为Notepad++是能够将编辑框分割成左边一般右边一般的,因此这两个一个是左边的编辑框,一个是右边的编辑框。然而还定义了两个DocTabView对象,_mainDocTab和_subDocTab,这个十分有必要说一下,DocTabView类是继承自TabBarPlus的,而TabBarPlus是集成自TabBar的,而TabBar是继承自Window的,说明DocTabView是一个窗口,如果看到程序后面的DocTabView对象的init()函数就知道,这个DocTabView对象不仅继承自TabBarPlus而且还将ScitillaEditView封装在自己的对象中,从字面意思能够看懂TabBarPlus是一个标签栏,一个标签栏和编辑框组合在一起,明显肯定是想要利用标签栏来控制编辑框的关闭、移动窗口等等工作。事实上,DocTabView的init()函数还接受了IconList对象,其实这个对象_docTabIconList中只是用来管理标签栏的图标的,一共只有三种,也就是未保存状态的图标,保存状态的图标和文件只可读的图标:
上面有个tabBarStatus用来保存标签栏的状态,决定了是缩小还是不缩小,这个功能在实际的软件的设置->首选项->常用->标签栏中可以勾选或者不选缩小。之后程序又为一个不可见的ScitillaEditView对象_invisibleEditView进行了初始化,这个_invisibleEditView是为了用户搜索之后将搜索结果放在这个_invisibleEditView中显示的,平时当让不可见了。再之后就是对三个作者封装的编辑框进行了初始化,因为scintilla本身十分之强大,而作者的ScitillaEditView主要是对这个功能进行了自己的封装,之后都是通过execute()函数来进行调用scintilla的功能的,这些针对于ScitillaEditView的设置暂时放在一边。之后初始化了两个对话框:
一个是语言格式设置,一个就是首选项了。在这之后就是就是调整标签栏的显示状况、标签栏上的关闭按钮、绘制顶层标签栏、决定标签栏是否能够拖动等等一系列事情,再加载了标签栏的风格:
之后又初始化了分割栏SplitterContainer对象_subSplitter,这个从字面上来理解为分割容器的类其实十分之强大和费解,我在读取有关这个类的代码的时候一度十分困扰,跟踪了许多消息才恍然大悟是如此使用。其实除了这个_subSplitter之外,还有一个_pMainSplitter,让我们先搞懂_subSplitter这个对象,这个类SplitterContainer也是继承自Window的,_subSplitter在创建开始的时候吸纳了两个带有标签栏的编辑框,并在对象内维护了一个真正的Splitter,也就是说事实上我们看到的NotePad++的运行界面上的两个编辑框事实上还有一个SplitterContainer在显示,只不过其Background是NULL,也就是说不可见的。至于这样做的好处,可谓十分机制,在后面会有讲述。之后又初始化了状态栏,设定了状态栏的大致情况:
之后判断主界面是否需要被最小化,如果需要最小化的话则先保存。之后又让插件管理器初始化了,因为后面需要加载插件信息。接着后面就比较简单了,就是为主菜单栏的菜单项添加子菜单,有宏菜单、运行菜单、语言、文件、插件、窗口菜单,一个比较特殊的就是“升级”这个菜单项是否应该出现在菜单中,如果不应该就删除嘛。另外语言菜单项还需要将用户排除的那些编程语言去除语言菜单项中,这些代码虽然长,但是比较简单:
后面就是程序另一大功能了,添加程序快捷键,作者将这些快捷键分开管理了,其实添加快捷键的代码还是比较麻烦的,主要的还是先将普通菜单栏的一些菜单项的快捷键设置好,其次是插件的快捷键,宏的快捷键,用户自定义的快捷键,右键菜单。分别是都是用vector来保存的:vector<CommandShortcut> vector<MacroShortcut> vector<UserCommand> vector<PluginCmdShortcut>。在这期间将英语设置成了用户之前自定义的语言:
之后就到了映射快捷键的时候的,经过层层调用,最终使用系统函数CreateAcceleratorTable来进行映射:
还有编辑框的快捷键的映射,因为走的不同的通道,所以这里本来就应该分开的:
之后就是工具栏了嘛,工具栏在win32中有比较方便的实现方法,这里作者也是直接用的,首先需要TBBUTTON这个结构体,这个东西可以直接和ImageList挂钩起来,设置好图片之后直接给toolbar发送一条消息就可以:TB_SETIMAGELIST。因为这个是现有的就不多做解释了,网上资料不多但是还能够找到点:
https://msdn.microsoft.com/en-us/library/bb787433(v=vs.85).aspx
http://www.gamedev.net/topic/451684-win32-non-mfc-rebar-and-toolbar-problems/
再下面就是初始化其他的对话框,比如查找代替啊,运行对话框啊等等…:
下面就是最麻烦的一个问题了,用户自定义语言格式是一个对话框,但是这个对话框的特殊之处在于带有了一个dock按钮,也就是浮动功能,但是这个浮动和其他窗口的浮动完全不一样,如果这个对话框在显示的状态下被按下了dock按钮,会干许多的事情:
注意最后一个case情况,这里的udd->doDialog()能够将对话框显示出来,因为没有浮动的情况下,这个对话框是没有父类的,所以能够看到,读者可以自行调试一下。但是如果是浮动的状态会通过消息的形式来模拟dock按钮被按下,那按下之后做了什么事情呢:
上面像显示透明度条和按钮啊都将被隐藏,因为在主窗口中透明肯定不好实现,之后向父窗口也就是整个主窗口发送了一条消息
WM_DOCK_USERDEFINE_DLG,因为主窗口多了一个用户自定义语言对话框,毫无疑问肯定需要重新设计整个界面的大小和排版了,于是如下:
再进入到这个函数:
这里终于重新见到了_pMainSplitter,这个意思就是如果用户自定义语言窗口浮动了,就将_pMainSplitter设定为_subSplitter和用户自定义对话框,而_subSplitter在上面已经讲过了是两个文本框放在一起的!这里的_pMainSplitter完美的反映出目前的状况,这个机制的实现比较复杂,使用了指针指针对象以及部分多态,重点是跨越的点实在太远了,很难联想到一起。在设置了这些窗口之后,用一个WM_SIZE消息调整一下大小,这个调整也很机智:
乍一看,感觉一点关系都没有,只不过就是改变了状态栏的高度神马的。这里引出了一个新的对象_dockingManager,浮动管理器,这个东西感觉就是用来管理用户自定义语言对话框的,但是感觉十分不靠谱,我在这里被坑了很久。其实这里的浮动管理器管理的额浮动根本就不是针对于两个编辑框以及用户自定义语言窗口的!这个是为了像插件窗口这样的可以浮动的窗口准备的,整个_dockingManager管理者四块区域,也就是上下左右,如果一点有哪个窗口dock在了上下左右中的一个就会引起整个主界面客户去的调整,首先调整的是dock窗口本身,其次!是ppMainWindow!也就是说只有dock窗口先调整,之后再轮到原来的两个编辑框和用户对话框的调整:
这最后一句让我醍醐灌顶,注意这里是多态,所以调用的不是父类的resizeto(),而是:
有时候传递的消息反而是我们容易忽略的,但是恰恰最重要,这里的WM_RESIZE_CONTAINER消息之后,所有的主界面都将被调整好。经过这些深入的探究也该回到_notepad_plus_plus_core的init()了,其实后面做的事情就是为编辑框加载文件了,之后正常返回:
返回之后就到了最开始notepad_plus_plus.init()这里,在CreateWindowEx代码之后继续执行,之后嘛该最小化就最小化,添加主题选择器和语言选择器等等,再之后一些细节加载完成就返回到了WinMain了,之后就构建了消息循环,开始处理之前在消息队列中存放的消息,之后程序就跑起来了:
本文转自:开源中国社区 [http://www.oschina.net]
本文标题:自己手动编写一个简单的解释器 Part 1
本文地址: http://www.oschina.net/translate/build-a-simple-interpreter-part-1
参与翻译: gx老苗 , 金柳颀 , 无若 , lostTemple , Mistoe
英文原文: Let's Build A Simple Interpreter. Part 1.