﻿<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/"><channel><title>C++博客-λ-calculus（惊愕到手了欧耶，GetBlogPostIds.aspx）-随笔分类-C++</title><link>http://www.cppblog.com/vczh/category/6885.html</link><description>【QQ：343056143】【Email：vczh@163.com】【新浪微博：http://weibo.com/vczh】</description><language>zh-cn</language><lastBuildDate>Thu, 21 Mar 2013 05:22:56 GMT</lastBuildDate><pubDate>Thu, 21 Mar 2013 05:22:56 GMT</pubDate><ttl>60</ttl><item><title>C++实用技巧之配置Visual C++的调试器显示数据结构的格式（附Vczh Library++配置文件）</title><link>http://www.cppblog.com/vczh/archive/2013/03/21/198665.html</link><dc:creator>陈梓瀚(vczh)</dc:creator><author>陈梓瀚(vczh)</author><pubDate>Thu, 21 Mar 2013 03:55:00 GMT</pubDate><guid>http://www.cppblog.com/vczh/archive/2013/03/21/198665.html</guid><wfw:comment>http://www.cppblog.com/vczh/comments/198665.html</wfw:comment><comments>http://www.cppblog.com/vczh/archive/2013/03/21/198665.html#Feedback</comments><slash:comments>1</slash:comments><wfw:commentRss>http://www.cppblog.com/vczh/comments/commentRss/198665.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/vczh/services/trackbacks/198665.html</trackback:ping><description><![CDATA[<p>今天我写了一个给Visual C++用的配置，用来让VC++在显示自己写的字符串和容器等设施变得跟显示STL一样漂亮。VC++的可配置型还是很高的，我们只要写一个xml，就可以改变调试器对自己的数据结构的显示了.</p>
<p>在这里我简单地介绍一下用法。假设大家觉得vlpp（Vczh Library++，也就是GacUI用的那个库）的WString啊List这些东西在调试器里面显示出来的东西太丑，就可以用以下三步来修改它。</p>
<p>1：去<a title="http://gac.codeplex.com/SourceControl/changeset/view/99419#2395529" href="http://gac.codeplex.com/SourceControl/changeset/view/99419#2395529">http://gac.codeplex.com/SourceControl/changeset/view/99419#2395529</a>下载我写的那个natvis文件。这个文件在整个zip包里面的位置是Common\vlpp.natvis<br />2：把这个文件复制到C:\Program Files (x86)\Microsoft Visual Studio 11.0\Common7\Packages\Debugger\Visualizers（如果使用默认安装路径的话）<br />3：重启你最喜爱的Visual Studio 2012，就可以看到下面的东西了：</p>
<p><a href="http://www.cppblog.com/images/cppblog_com/vczh/Windows-Live-Writer/51783bf28f3a_A413/image_2.png"><img style="background-image: none; border-bottom: 0px; border-left: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top: 0px; border-right: 0px; padding-top: 0px" title="image" border="0" alt="image" src="http://www.cppblog.com/images/cppblog_com/vczh/Windows-Live-Writer/51783bf28f3a_A413/image_thumb.png" width="275" height="95" /></a><br />空的智能指针</p>
<p><a href="http://www.cppblog.com/images/cppblog_com/vczh/Windows-Live-Writer/51783bf28f3a_A413/image_4.png"><img style="background-image: none; border-bottom: 0px; border-left: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top: 0px; border-right: 0px; padding-top: 0px" title="image" border="0" alt="image" src="http://www.cppblog.com/images/cppblog_com/vczh/Windows-Live-Writer/51783bf28f3a_A413/image_thumb_1.png" width="433" height="115" /></a><br />有东西的智能指针</p>
<p><a href="http://www.cppblog.com/images/cppblog_com/vczh/Windows-Live-Writer/51783bf28f3a_A413/image_6.png"><img style="background-image: none; border-bottom: 0px; border-left: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top: 0px; border-right: 0px; padding-top: 0px" title="image" border="0" alt="image" src="http://www.cppblog.com/images/cppblog_com/vczh/Windows-Live-Writer/51783bf28f3a_A413/image_thumb_2.png" width="224" height="64" /></a><br />有内容的&#8220;惰性计算&#8221;类</p>
<p><a href="http://www.cppblog.com/images/cppblog_com/vczh/Windows-Live-Writer/51783bf28f3a_A413/image_8.png"><img style="background-image: none; border-bottom: 0px; border-left: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top: 0px; border-right: 0px; padding-top: 0px" title="image" border="0" alt="image" src="http://www.cppblog.com/images/cppblog_com/vczh/Windows-Live-Writer/51783bf28f3a_A413/image_thumb_3.png" width="344" height="88" /></a><br />有内容但是还没计算的&#8220;惰性计算&#8221;类</p>
<p><a href="http://www.cppblog.com/images/cppblog_com/vczh/Windows-Live-Writer/51783bf28f3a_A413/image_10.png"><img style="background-image: none; border-bottom: 0px; border-left: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top: 0px; border-right: 0px; padding-top: 0px" title="image" border="0" alt="image" src="http://www.cppblog.com/images/cppblog_com/vczh/Windows-Live-Writer/51783bf28f3a_A413/image_thumb_4.png" width="353" height="83" /></a><br />没内容的&#8220;惰性计算&#8221;类</p>
<p><a href="http://www.cppblog.com/images/cppblog_com/vczh/Windows-Live-Writer/51783bf28f3a_A413/image_12.png"><img style="background-image: none; border-bottom: 0px; border-left: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top: 0px; border-right: 0px; padding-top: 0px" title="image" border="0" alt="image" src="http://www.cppblog.com/images/cppblog_com/vczh/Windows-Live-Writer/51783bf28f3a_A413/image_thumb_5.png" width="445" height="259" /></a><br />新鲜热辣的容器</p>
<p><a href="http://www.cppblog.com/images/cppblog_com/vczh/Windows-Live-Writer/51783bf28f3a_A413/image_14.png"><img style="background-image: none; border-bottom: 0px; border-left: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top: 0px; border-right: 0px; padding-top: 0px" title="image" border="0" alt="image" src="http://www.cppblog.com/images/cppblog_com/vczh/Windows-Live-Writer/51783bf28f3a_A413/image_thumb_6.png" width="667" height="226" /></a><br />新鲜热辣的映射</p>
<p><a href="http://www.cppblog.com/images/cppblog_com/vczh/Windows-Live-Writer/51783bf28f3a_A413/image_16.png"><img style="background-image: none; border-bottom: 0px; border-left: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top: 0px; border-right: 0px; padding-top: 0px" title="image" border="0" alt="image" src="http://www.cppblog.com/images/cppblog_com/vczh/Windows-Live-Writer/51783bf28f3a_A413/image_thumb_7.png" width="596" height="295" /></a><br />就连一对多的映射也是如此的新鲜热辣</p>
<p><a href="http://www.cppblog.com/images/cppblog_com/vczh/Windows-Live-Writer/51783bf28f3a_A413/image_18.png"><img style="background-image: none; border-bottom: 0px; border-left: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top: 0px; border-right: 0px; padding-top: 0px" title="image" border="0" alt="image" src="http://www.cppblog.com/images/cppblog_com/vczh/Windows-Live-Writer/51783bf28f3a_A413/image_thumb_8.png" width="318" height="161" /></a><br />List&lt;Nullable&lt;vint&gt;&gt;的互相嵌套也是如此的完美</p>
<p>如果大家想写自己的Customized Visualizer的话，可以去参考微软良心提供的文档<a title="http://msdn.microsoft.com/en-us/library/vstudio/jj620914.aspx" href="http://msdn.microsoft.com/en-us/library/vstudio/jj620914.aspx">http://msdn.microsoft.com/en-us/library/vstudio/jj620914.aspx</a>，然后按照上面的步骤写自己的natvis文件。在这里我把我的natvis贴上来，以供参考：</p>
<div style="border-bottom: #cccccc 1px solid; border-left: #cccccc 1px solid; padding-bottom: 5px; background-color: #f5f5f5; padding-left: 5px; padding-right: 5px; border-top: #cccccc 1px solid; border-right: #cccccc 1px solid; padding-top: 5px" class="cnblogs_code"><pre><span style="color: #0000ff">&lt;?</span><span style="color: #ff00ff">xml version="1.0" encoding="utf-8"</span><span style="color: #0000ff">?&gt;</span>
<span style="color: #0000ff">&lt;</span><span style="color: #800000">AutoVisualizer </span><span style="color: #ff0000">xmlns</span><span style="color: #0000ff">="http://schemas.microsoft.com/vstudio/debugger/natvis/2010"</span><span style="color: #0000ff">&gt;</span>

  <span style="color: #0000ff">&lt;</span><span style="color: #800000">Type </span><span style="color: #ff0000">Name</span><span style="color: #0000ff">="vl::ObjectString&amp;lt;wchar_t&amp;gt;"</span><span style="color: #0000ff">&gt;</span>
    <span style="color: #0000ff">&lt;</span><span style="color: #800000">DisplayString</span><span style="color: #0000ff">&gt;</span>{{ size={length}, buffer={buffer+start,su} }}<span style="color: #0000ff">&lt;/</span><span style="color: #800000">DisplayString</span><span style="color: #0000ff">&gt;</span>
    <span style="color: #0000ff">&lt;</span><span style="color: #800000">StringView</span><span style="color: #0000ff">&gt;</span>buffer+start,su<span style="color: #0000ff">&lt;/</span><span style="color: #800000">StringView</span><span style="color: #0000ff">&gt;</span>
    <span style="color: #0000ff">&lt;</span><span style="color: #800000">Expand</span><span style="color: #0000ff">&gt;</span>
      <span style="color: #0000ff">&lt;</span><span style="color: #800000">Item </span><span style="color: #ff0000">Name</span><span style="color: #0000ff">="[size]"</span><span style="color: #0000ff">&gt;</span>length<span style="color: #0000ff">&lt;/</span><span style="color: #800000">Item</span><span style="color: #0000ff">&gt;</span>
      <span style="color: #0000ff">&lt;</span><span style="color: #800000">ArrayItems</span><span style="color: #0000ff">&gt;</span>
        <span style="color: #0000ff">&lt;</span><span style="color: #800000">Size</span><span style="color: #0000ff">&gt;</span>length<span style="color: #0000ff">&lt;/</span><span style="color: #800000">Size</span><span style="color: #0000ff">&gt;</span>
        <span style="color: #0000ff">&lt;</span><span style="color: #800000">ValuePointer</span><span style="color: #0000ff">&gt;</span>buffer+start<span style="color: #0000ff">&lt;/</span><span style="color: #800000">ValuePointer</span><span style="color: #0000ff">&gt;</span>
      <span style="color: #0000ff">&lt;/</span><span style="color: #800000">ArrayItems</span><span style="color: #0000ff">&gt;</span>
    <span style="color: #0000ff">&lt;/</span><span style="color: #800000">Expand</span><span style="color: #0000ff">&gt;</span>
  <span style="color: #0000ff">&lt;/</span><span style="color: #800000">Type</span><span style="color: #0000ff">&gt;</span>

  <span style="color: #0000ff">&lt;</span><span style="color: #800000">Type </span><span style="color: #ff0000">Name</span><span style="color: #0000ff">="vl::ObjectString&amp;lt;char&amp;gt;"</span><span style="color: #0000ff">&gt;</span>
    <span style="color: #0000ff">&lt;</span><span style="color: #800000">DisplayString</span><span style="color: #0000ff">&gt;</span>{{ size={length}, buffer={buffer+start,s} }}<span style="color: #0000ff">&lt;/</span><span style="color: #800000">DisplayString</span><span style="color: #0000ff">&gt;</span>
    <span style="color: #0000ff">&lt;</span><span style="color: #800000">StringView</span><span style="color: #0000ff">&gt;</span>buffer+start,s<span style="color: #0000ff">&lt;/</span><span style="color: #800000">StringView</span><span style="color: #0000ff">&gt;</span>
    <span style="color: #0000ff">&lt;</span><span style="color: #800000">Expand</span><span style="color: #0000ff">&gt;</span>
      <span style="color: #0000ff">&lt;</span><span style="color: #800000">Item </span><span style="color: #ff0000">Name</span><span style="color: #0000ff">="[size]"</span><span style="color: #0000ff">&gt;</span>length<span style="color: #0000ff">&lt;/</span><span style="color: #800000">Item</span><span style="color: #0000ff">&gt;</span>
      <span style="color: #0000ff">&lt;</span><span style="color: #800000">ArrayItems</span><span style="color: #0000ff">&gt;</span>
        <span style="color: #0000ff">&lt;</span><span style="color: #800000">Size</span><span style="color: #0000ff">&gt;</span>length<span style="color: #0000ff">&lt;/</span><span style="color: #800000">Size</span><span style="color: #0000ff">&gt;</span>
        <span style="color: #0000ff">&lt;</span><span style="color: #800000">ValuePointer</span><span style="color: #0000ff">&gt;</span>buffer+start<span style="color: #0000ff">&lt;/</span><span style="color: #800000">ValuePointer</span><span style="color: #0000ff">&gt;</span>
      <span style="color: #0000ff">&lt;/</span><span style="color: #800000">ArrayItems</span><span style="color: #0000ff">&gt;</span>
    <span style="color: #0000ff">&lt;/</span><span style="color: #800000">Expand</span><span style="color: #0000ff">&gt;</span>
  <span style="color: #0000ff">&lt;/</span><span style="color: #800000">Type</span><span style="color: #0000ff">&gt;</span>

  <span style="color: #0000ff">&lt;</span><span style="color: #800000">Type </span><span style="color: #ff0000">Name</span><span style="color: #0000ff">="vl::collections::List&amp;lt;*,*&amp;gt;"</span><span style="color: #0000ff">&gt;</span>
    <span style="color: #0000ff">&lt;</span><span style="color: #800000">AlternativeType </span><span style="color: #ff0000">Name</span><span style="color: #0000ff">="vl::collections::SortedList&amp;lt;*,*&amp;gt;"</span><span style="color: #0000ff">/&gt;</span>
    <span style="color: #0000ff">&lt;</span><span style="color: #800000">AlternativeType </span><span style="color: #ff0000">Name</span><span style="color: #0000ff">="vl::collections::Array&amp;lt;*,*&amp;gt;"</span><span style="color: #0000ff">/&gt;</span>
    <span style="color: #0000ff">&lt;</span><span style="color: #800000">DisplayString</span><span style="color: #0000ff">&gt;</span>{{ size={count} }}<span style="color: #0000ff">&lt;/</span><span style="color: #800000">DisplayString</span><span style="color: #0000ff">&gt;</span>
    <span style="color: #0000ff">&lt;</span><span style="color: #800000">Expand</span><span style="color: #0000ff">&gt;</span>
      <span style="color: #0000ff">&lt;</span><span style="color: #800000">Item </span><span style="color: #ff0000">Name</span><span style="color: #0000ff">="[size]"</span><span style="color: #0000ff">&gt;</span>count<span style="color: #0000ff">&lt;/</span><span style="color: #800000">Item</span><span style="color: #0000ff">&gt;</span>
      <span style="color: #0000ff">&lt;</span><span style="color: #800000">ArrayItems</span><span style="color: #0000ff">&gt;</span>
        <span style="color: #0000ff">&lt;</span><span style="color: #800000">Size</span><span style="color: #0000ff">&gt;</span>count<span style="color: #0000ff">&lt;/</span><span style="color: #800000">Size</span><span style="color: #0000ff">&gt;</span>
        <span style="color: #0000ff">&lt;</span><span style="color: #800000">ValuePointer</span><span style="color: #0000ff">&gt;</span>buffer<span style="color: #0000ff">&lt;/</span><span style="color: #800000">ValuePointer</span><span style="color: #0000ff">&gt;</span>
      <span style="color: #0000ff">&lt;/</span><span style="color: #800000">ArrayItems</span><span style="color: #0000ff">&gt;</span>
    <span style="color: #0000ff">&lt;/</span><span style="color: #800000">Expand</span><span style="color: #0000ff">&gt;</span>
  <span style="color: #0000ff">&lt;/</span><span style="color: #800000">Type</span><span style="color: #0000ff">&gt;</span>

  <span style="color: #0000ff">&lt;</span><span style="color: #800000">Type </span><span style="color: #ff0000">Name</span><span style="color: #0000ff">="vl::collections::Dictionary&amp;lt;*,*,*,*&amp;gt;"</span><span style="color: #0000ff">&gt;</span>
    <span style="color: #0000ff">&lt;</span><span style="color: #800000">AlternativeType </span><span style="color: #ff0000">Name</span><span style="color: #0000ff">="vl::collections::Group&amp;lt;*,*,*,*&amp;gt;"</span><span style="color: #0000ff">/&gt;</span>
    <span style="color: #0000ff">&lt;</span><span style="color: #800000">DisplayString</span><span style="color: #0000ff">&gt;</span>{{ size={keys.count} }}<span style="color: #0000ff">&lt;/</span><span style="color: #800000">DisplayString</span><span style="color: #0000ff">&gt;</span>
    <span style="color: #0000ff">&lt;</span><span style="color: #800000">Expand</span><span style="color: #0000ff">&gt;</span>
      <span style="color: #0000ff">&lt;</span><span style="color: #800000">Item </span><span style="color: #ff0000">Name</span><span style="color: #0000ff">="[size]"</span><span style="color: #0000ff">&gt;</span>keys.count<span style="color: #0000ff">&lt;/</span><span style="color: #800000">Item</span><span style="color: #0000ff">&gt;</span>
      <span style="color: #0000ff">&lt;</span><span style="color: #800000">Item </span><span style="color: #ff0000">Name</span><span style="color: #0000ff">="Keys"</span><span style="color: #0000ff">&gt;</span>keys<span style="color: #0000ff">&lt;/</span><span style="color: #800000">Item</span><span style="color: #0000ff">&gt;</span>
      <span style="color: #0000ff">&lt;</span><span style="color: #800000">Item </span><span style="color: #ff0000">Name</span><span style="color: #0000ff">="Values"</span><span style="color: #0000ff">&gt;</span>values<span style="color: #0000ff">&lt;/</span><span style="color: #800000">Item</span><span style="color: #0000ff">&gt;</span>
    <span style="color: #0000ff">&lt;/</span><span style="color: #800000">Expand</span><span style="color: #0000ff">&gt;</span>
  <span style="color: #0000ff">&lt;/</span><span style="color: #800000">Type</span><span style="color: #0000ff">&gt;</span>

  <span style="color: #0000ff">&lt;</span><span style="color: #800000">Type </span><span style="color: #ff0000">Name</span><span style="color: #0000ff">="vl::Ptr&amp;lt;*&amp;gt;"</span><span style="color: #0000ff">&gt;</span>
    <span style="color: #0000ff">&lt;</span><span style="color: #800000">AlternativeType </span><span style="color: #ff0000">Name</span><span style="color: #0000ff">="vl::ComPtr&amp;lt;*&amp;gt;"</span><span style="color: #0000ff">/&gt;</span>
    <span style="color: #0000ff">&lt;</span><span style="color: #800000">DisplayString </span><span style="color: #ff0000">Condition</span><span style="color: #0000ff">="reference == 0"</span><span style="color: #0000ff">&gt;</span>[empty]<span style="color: #0000ff">&lt;/</span><span style="color: #800000">DisplayString</span><span style="color: #0000ff">&gt;</span>
    <span style="color: #0000ff">&lt;</span><span style="color: #800000">DisplayString </span><span style="color: #ff0000">Condition</span><span style="color: #0000ff">="reference != 0"</span><span style="color: #0000ff">&gt;</span>{*reference}<span style="color: #0000ff">&lt;/</span><span style="color: #800000">DisplayString</span><span style="color: #0000ff">&gt;</span>
    <span style="color: #0000ff">&lt;</span><span style="color: #800000">Expand</span><span style="color: #0000ff">&gt;</span>
      <span style="color: #0000ff">&lt;</span><span style="color: #800000">Item </span><span style="color: #ff0000">Condition</span><span style="color: #0000ff">="reference != 0"</span><span style="color: #ff0000"> Name</span><span style="color: #0000ff">="[ptr]"</span><span style="color: #0000ff">&gt;</span>reference<span style="color: #0000ff">&lt;/</span><span style="color: #800000">Item</span><span style="color: #0000ff">&gt;</span>
    <span style="color: #0000ff">&lt;/</span><span style="color: #800000">Expand</span><span style="color: #0000ff">&gt;</span>
  <span style="color: #0000ff">&lt;/</span><span style="color: #800000">Type</span><span style="color: #0000ff">&gt;</span>

  <span style="color: #0000ff">&lt;</span><span style="color: #800000">Type </span><span style="color: #ff0000">Name</span><span style="color: #0000ff">="vl::Lazy&amp;lt;*&amp;gt;"</span><span style="color: #0000ff">&gt;</span>
    <span style="color: #0000ff">&lt;</span><span style="color: #800000">DisplayString </span><span style="color: #ff0000">Condition</span><span style="color: #0000ff">="internalValue.reference == 0"</span><span style="color: #0000ff">&gt;</span>[empty]<span style="color: #0000ff">&lt;/</span><span style="color: #800000">DisplayString</span><span style="color: #0000ff">&gt;</span>
    <span style="color: #0000ff">&lt;</span><span style="color: #800000">DisplayString </span><span style="color: #ff0000">Condition</span><span style="color: #0000ff">="internalValue.reference != 0 &amp;amp;&amp;amp; internalValue.reference-&gt;evaluated == false"</span><span style="color: #0000ff">&gt;</span>[not evaluated]<span style="color: #0000ff">&lt;/</span><span style="color: #800000">DisplayString</span><span style="color: #0000ff">&gt;</span>
    <span style="color: #0000ff">&lt;</span><span style="color: #800000">DisplayString </span><span style="color: #ff0000">Condition</span><span style="color: #0000ff">="internalValue.reference != 0 &amp;amp;&amp;amp; internalValue.reference-&gt;evaluated == true"</span><span style="color: #0000ff">&gt;</span>{internalValue.reference-&gt;value}<span style="color: #0000ff">&lt;/</span><span style="color: #800000">DisplayString</span><span style="color: #0000ff">&gt;</span>
    <span style="color: #0000ff">&lt;</span><span style="color: #800000">Expand</span><span style="color: #0000ff">&gt;</span>
      <span style="color: #0000ff">&lt;</span><span style="color: #800000">Item </span><span style="color: #ff0000">Condition</span><span style="color: #0000ff">="internalValue.reference != 0 &amp;amp;&amp;amp; internalValue.reference-&gt;evaluated == true"</span><span style="color: #ff0000"> Name</span><span style="color: #0000ff">="[value]"</span><span style="color: #0000ff">&gt;</span>internalValue.reference-&gt;value<span style="color: #0000ff">&lt;/</span><span style="color: #800000">Item</span><span style="color: #0000ff">&gt;</span>
    <span style="color: #0000ff">&lt;/</span><span style="color: #800000">Expand</span><span style="color: #0000ff">&gt;</span>
  <span style="color: #0000ff">&lt;/</span><span style="color: #800000">Type</span><span style="color: #0000ff">&gt;</span>

  <span style="color: #0000ff">&lt;</span><span style="color: #800000">Type </span><span style="color: #ff0000">Name</span><span style="color: #0000ff">="vl::ObjectBox&amp;lt;*&amp;gt;"</span><span style="color: #0000ff">&gt;</span>
    <span style="color: #0000ff">&lt;</span><span style="color: #800000">DisplayString</span><span style="color: #0000ff">&gt;</span>{object}<span style="color: #0000ff">&lt;/</span><span style="color: #800000">DisplayString</span><span style="color: #0000ff">&gt;</span>
    <span style="color: #0000ff">&lt;</span><span style="color: #800000">Expand</span><span style="color: #0000ff">&gt;</span>
      <span style="color: #0000ff">&lt;</span><span style="color: #800000">ExpandedItem</span><span style="color: #0000ff">&gt;</span>object<span style="color: #0000ff">&lt;/</span><span style="color: #800000">ExpandedItem</span><span style="color: #0000ff">&gt;</span>
    <span style="color: #0000ff">&lt;/</span><span style="color: #800000">Expand</span><span style="color: #0000ff">&gt;</span>
  <span style="color: #0000ff">&lt;/</span><span style="color: #800000">Type</span><span style="color: #0000ff">&gt;</span>

  <span style="color: #0000ff">&lt;</span><span style="color: #800000">Type </span><span style="color: #ff0000">Name</span><span style="color: #0000ff">="vl::Nullable&amp;lt;*&amp;gt;"</span><span style="color: #0000ff">&gt;</span>
    <span style="color: #0000ff">&lt;</span><span style="color: #800000">DisplayString </span><span style="color: #ff0000">Condition</span><span style="color: #0000ff">="object == 0"</span><span style="color: #0000ff">&gt;</span>[empty]<span style="color: #0000ff">&lt;/</span><span style="color: #800000">DisplayString</span><span style="color: #0000ff">&gt;</span>
    <span style="color: #0000ff">&lt;</span><span style="color: #800000">DisplayString </span><span style="color: #ff0000">Condition</span><span style="color: #0000ff">="object != 0"</span><span style="color: #0000ff">&gt;</span>{*object}<span style="color: #0000ff">&lt;/</span><span style="color: #800000">DisplayString</span><span style="color: #0000ff">&gt;</span>
    <span style="color: #0000ff">&lt;</span><span style="color: #800000">Expand</span><span style="color: #0000ff">&gt;</span>
      <span style="color: #0000ff">&lt;</span><span style="color: #800000">ExpandedItem </span><span style="color: #ff0000">Condition</span><span style="color: #0000ff">="object != 0"</span><span style="color: #0000ff">&gt;</span>*object<span style="color: #0000ff">&lt;/</span><span style="color: #800000">ExpandedItem</span><span style="color: #0000ff">&gt;</span>
    <span style="color: #0000ff">&lt;/</span><span style="color: #800000">Expand</span><span style="color: #0000ff">&gt;</span>
  <span style="color: #0000ff">&lt;/</span><span style="color: #800000">Type</span><span style="color: #0000ff">&gt;</span>

<span style="color: #0000ff">&lt;/</span><span style="color: #800000">AutoVisualizer</span><span style="color: #0000ff">&gt;</span></pre></div><img src ="http://www.cppblog.com/vczh/aggbug/198665.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/vczh/" target="_blank">陈梓瀚(vczh)</a> 2013-03-21 11:55 <a href="http://www.cppblog.com/vczh/archive/2013/03/21/198665.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>可配置语法分析器开发纪事（五）&amp;mdash;&amp;mdash;构造一个真正能用的状态机（中）</title><link>http://www.cppblog.com/vczh/archive/2013/01/01/196893.html</link><dc:creator>陈梓瀚(vczh)</dc:creator><author>陈梓瀚(vczh)</author><pubDate>Tue, 01 Jan 2013 07:52:00 GMT</pubDate><guid>http://www.cppblog.com/vczh/archive/2013/01/01/196893.html</guid><wfw:comment>http://www.cppblog.com/vczh/comments/196893.html</wfw:comment><comments>http://www.cppblog.com/vczh/archive/2013/01/01/196893.html#Feedback</comments><slash:comments>10</slash:comments><wfw:commentRss>http://www.cppblog.com/vczh/comments/commentRss/196893.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/vczh/services/trackbacks/196893.html</trackback:ping><description><![CDATA[<p><a href="http://www.cppblog.com/vczh/archive/2012/12/23/196524.html" target="_blank">上一篇博客</a>写到了如何给一个非终结符的文法规则构造出一个压缩过的下推状态机，那么今天说的就是如何把所有的文法都连接起来。其实主要的idea在<a href="http://www.cppblog.com/vczh/archive/2012/12/07/196079.html" target="_blank">（三）</a>和他的勘误<a href="http://www.cppblog.com/vczh/archive/2012/12/07/196085.html" target="_blank">（三点五）</a>里面已经说得差不多了。但是今天我们要处理的是带信息的transition，所以还有一些地方要注意一下。</p>
<p>所以在这里我们先把几条文法的最后的状态机都列出来（大图）：</p>
<p><a href="http://www.cppblog.com/images/cppblog_com/vczh/Windows-Live-Writer/5a2a95565d03_AB00/image_2.png"><img style="background-image: none; border-bottom: 0px; border-left: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top: 0px; border-right: 0px; padding-top: 0px" title="image" border="0" alt="image" src="http://www.cppblog.com/images/cppblog_com/vczh/Windows-Live-Writer/5a2a95565d03_AB00/image_thumb.png" width="893" height="1438" /></a></p>
<p>接下来的这一步，就是要对所有靠非终结符（Exp啊Term这些）进行跳转的transition都执行上一篇文章所说的传说中的交叉链接。在产生链接的时候，我们给shift和reduce的边分别加上shift和reduce。而shift和reduce是有参数的&#8212;&#8212;就是被shift走的状态的id。这样可以在parse的时候匹配和处理状态堆栈。在这里我门对e3-&gt;e1这一步做一下操作做为例子。红色的边是被删掉的，而粗壮的绿色边是被新加进去的：</p>
<p><a href="http://www.cppblog.com/images/cppblog_com/vczh/Windows-Live-Writer/5a2a95565d03_AB00/image_12.png"><img style="background-image: none; border-bottom: 0px; border-left: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top: 0px; border-right: 0px; padding-top: 0px" title="image" border="0" alt="image" src="http://www.cppblog.com/images/cppblog_com/vczh/Windows-Live-Writer/5a2a95565d03_AB00/image_thumb_5.png" width="885" height="597" /></a></p>
<p>红色的边变成了两条绿色的边，红色的边附带的信息则被复制到了绿色的reduce边上。当我们使用这个状态机的时候，shift(s3)就表示往堆栈里面压入s3，reduce(s3)就表示从堆栈里面弹出(s3)。当然弹出不一定会成功，所以如果不成功的话，这条边就不能在当时使用。因此这也就是为什么在e3跳转到t0之后，t1知道往回跳的是e1而不是别的什么地方&#8212;&#8212;就如同为什么C++的函数执行完之后总是知道如何跳转回调用它的地方一样&#8212;&#8212;因为把信息推入了堆栈。</p>
<p>那现在我们就来看一下，当所有的非终结符跳转都处理掉之后，会变成什么样子吧（这个图真是复杂和乱到我不想画啊），为了让图变得不那么糟糕，我把shift都变成紫色，reduce都变成绿色：</p>
<p><a href="http://www.cppblog.com/images/cppblog_com/vczh/Windows-Live-Writer/5a2a95565d03_AB00/image_6.png"><img style="background-image: none; border-bottom: 0px; border-left: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top: 0px; border-right: 0px; padding-top: 0px" title="image" border="0" alt="image" src="http://www.cppblog.com/images/cppblog_com/vczh/Windows-Live-Writer/5a2a95565d03_AB00/image_thumb_1.png" width="1191" height="1528" /></a></p>
<p>在添加shift和reduce边之前，每一条边都是有输入token的。但是我们刚刚添加上去的shift和reduce边却是不输入token的，所以他们是epsilon边，下一步就是要消除他们。上面这个图消除了epsilon边之后，会变成一个状态很少，但是每一条边附带的信息都会非常多，而且像n1这种经常到达的状态（因为四则运算里面有很多数字）将恢复射出无数条边。到了这里这个状态机已经再也画不出来了。所以我下面就只拿两个例子来画。下面要展示的是用Exp来parse单独的一个数字会走的边，当然就是Exp &#8211;&gt; Term &#8211;&gt; Factor &#8211;&gt; Number了：</p>
<p><a href="http://www.cppblog.com/images/cppblog_com/vczh/Windows-Live-Writer/5a2a95565d03_AB00/image_8.png"><img style="background-image: none; border-bottom: 0px; border-left: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top: 0px; border-right: 0px; padding-top: 0px" title="image" border="0" alt="image" src="http://www.cppblog.com/images/cppblog_com/vczh/Windows-Live-Writer/5a2a95565d03_AB00/image_thumb_2.png" width="557" height="504" /></a></p>
<p>就会被处理成：</p>
<p><a href="http://www.cppblog.com/images/cppblog_com/vczh/Windows-Live-Writer/5a2a95565d03_AB00/image_14.png"><img style="background-image: none; border-bottom: 0px; border-left: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top: 0px; border-right: 0px; padding-top: 0px" title="image" border="0" alt="image" src="http://www.cppblog.com/images/cppblog_com/vczh/Windows-Live-Writer/5a2a95565d03_AB00/image_thumb_3.png" width="676" height="457" /></a></p>
<p>注意边上面的信息是要按顺序重新叠加在一起的。当所有的epsilon边都去掉了之后，我们就得到了最终的一个状态机。最重要的一件事情出现了。我们知道，发明LR和LALR这种东西就基本上是为了处理左递归的，所以这种图就可以在去除epsilon边的过程中自动发现左递归。这是怎么做到的呢？只要在去除epsilon边的时候，发现了一条完全由shift这种epsilon边组成的环，那么左递归就发现了。为了方便，我们可以只处理直接左递归&#8212;&#8212;就是这种环的长度是1的。不包含间接左递归的问法是很容易写出来的。当然这种环并不一定是首尾相接的，譬如说我们在处理e0的时候就会发现e0-&gt;t0-&gt;t0这种环（当然严格来说环只有最后一截的这个部分）。我们的程序要很好地应对这种情况。因为我们只接受直接左递归，所以类似这种结构的epsilon路径可以直接的抛弃他，因为t0-&gt;t0会被t0状态单独处理掉。因此这样做并不会漏掉什么。</p>
<p>细心的朋友可能会发现，这个结构的图是不能直接处理右递归的（总之左递归和右递归总要有一个会让你的状态机傻逼就是了！）。关于如何处理有递归（其实内容也不复杂）地方法会在&#8220;下篇&#8221;描述出来。那处理左递归有什么用呢？举个例子，我们的e0-&gt;e2就是一个左递归，而他会在之前的步骤被处理成shift(e0-&gt;e0)和reduce(e1-&gt;e2)。我们要记下shift和reduce的对应关系，那么当我们找到一个左递归的shift之后，我们就可以把对应的reduce给标记成&#8220;left-recursive-reduce&#8221;。这是一个在构造语法树的时候，非常关键的一种构造指令。</p>
<p>处理完这些之后，我们可以把左递归的shift边全部删掉，最后把token和state都统统处理成连续的数字，做成一张[state, token] &#8211;&gt; [transitions]的二维表，每一个表的元素是transition的列表。为什么是这样呢？因为我们对一个state输入一个token之后，由于保存着state的堆栈（你还记得吗？shift==push，reduce==pop）的栈顶若干个元素的不同，可能会走不通的路线。于是最后我们就得到了这么一张图。</p>
<p>下面这张图可以通过运行gac.codeplex.com上面的Common\UnitTest\UnitTest.sln（VS2012限定）之后，在Common\UnitTest\TestFiles\Parsing.Calculator.Table.txt里面找到。这一组文件都是我在测试状态机的时候log下来的。</p>
<p><a href="http://www.cppblog.com/images/cppblog_com/vczh/Windows-Live-Writer/5a2a95565d03_AB00/image_16.png"><img style="background-image: none; border-bottom: 0px; border-left: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top: 0px; border-right: 0px; padding-top: 0px" title="image" border="0" alt="image" src="http://www.cppblog.com/images/cppblog_com/vczh/Windows-Live-Writer/5a2a95565d03_AB00/image_thumb_6.png" width="1934" height="1812" /></a></p>
<p>如果大家有VS2012的话，通过运行我准备的几个输入，譬如说&#8220;1*2+3*4&#8221;，就可以在Parsing.Calculator.[2].txt里面找到所有状态跳转的轨迹。因为我们总是需要parse一个Exp，所以我们从22: Exp.RootStart开始。我们假设token stream的第一个和最后一个分别是$TokenBegin和$TokenFinish。上图的$TryReduce是为了应对右递归而设计出来的一种特殊输入。由于四则运算里面并没有右递归，所以这一列就是空的：</p>
<p>StartState: 22[Exp.RootStart]<br />$TokenBegin =&gt; 23[Exp.Start]<br />&nbsp;&nbsp;&nbsp; State Stack: <br />NUMBER[1] =&gt; 2[Number.1]<br />&nbsp;&nbsp;&nbsp; State Stack: 23[Exp.Start], 21[Term.Start], 19[Factor.Start]<br />&nbsp;&nbsp;&nbsp; Shift 23[Exp]<br />&nbsp;&nbsp;&nbsp; Shift 21[Term]<br />&nbsp;&nbsp;&nbsp; Shift 19[Factor]<br />&nbsp;&nbsp;&nbsp; Assign value<br />&nbsp;&nbsp;&nbsp; Create NumberExpression<br />MUL[*] =&gt; 5[Term.3]<br />&nbsp;&nbsp;&nbsp; State Stack: 23[Exp.Start]<br />&nbsp;&nbsp;&nbsp; Reduce 19[Factor]<br />&nbsp;&nbsp;&nbsp; Using<br />&nbsp;&nbsp;&nbsp; Reduce 21[Term]<br />&nbsp;&nbsp;&nbsp; Using<br />&nbsp;&nbsp;&nbsp; LR-Reduce 21[Term]<br />&nbsp;&nbsp;&nbsp; Assign firstOperand<br />&nbsp;&nbsp;&nbsp; Setter binaryOperator = Mul<br />&nbsp;&nbsp;&nbsp; Create BinaryExpression<br />NUMBER[2] =&gt; 2[Number.1]<br />&nbsp;&nbsp;&nbsp; State Stack: 23[Exp.Start], 5[Term.3], 19[Factor.Start]<br />&nbsp;&nbsp;&nbsp; Shift 5[Term]<br />&nbsp;&nbsp;&nbsp; Shift 19[Factor]<br />&nbsp;&nbsp;&nbsp; Assign value<br />&nbsp;&nbsp;&nbsp; Create NumberExpression<br />ADD[+] =&gt; 10[Exp.3]<br />&nbsp;&nbsp;&nbsp; State Stack: <br />&nbsp;&nbsp;&nbsp; Reduce 19[Factor]<br />&nbsp;&nbsp;&nbsp; Using<br />&nbsp;&nbsp;&nbsp; Reduce 5[Term]<br />&nbsp;&nbsp;&nbsp; Assign secondOperand<br />&nbsp;&nbsp;&nbsp; Reduce 23[Exp]<br />&nbsp;&nbsp;&nbsp; Using<br />&nbsp;&nbsp;&nbsp; LR-Reduce 23[Exp]<br />&nbsp;&nbsp;&nbsp; Assign firstOperand<br />&nbsp;&nbsp;&nbsp; Setter binaryOperator = Add<br />&nbsp;&nbsp;&nbsp; Create BinaryExpression<br />NUMBER[3] =&gt; 2[Number.1]<br />&nbsp;&nbsp;&nbsp; State Stack: 10[Exp.3], 21[Term.Start], 19[Factor.Start]<br />&nbsp;&nbsp;&nbsp; Shift 10[Exp]<br />&nbsp;&nbsp;&nbsp; Shift 21[Term]<br />&nbsp;&nbsp;&nbsp; Shift 19[Factor]<br />&nbsp;&nbsp;&nbsp; Assign value<br />&nbsp;&nbsp;&nbsp; Create NumberExpression<br />MUL[*] =&gt; 5[Term.3]<br />&nbsp;&nbsp;&nbsp; State Stack: 10[Exp.3]<br />&nbsp;&nbsp;&nbsp; Reduce 19[Factor]<br />&nbsp;&nbsp;&nbsp; Using<br />&nbsp;&nbsp;&nbsp; Reduce 21[Term]<br />&nbsp;&nbsp;&nbsp; Using<br />&nbsp;&nbsp;&nbsp; LR-Reduce 21[Term]<br />&nbsp;&nbsp;&nbsp; Assign firstOperand<br />&nbsp;&nbsp;&nbsp; Setter binaryOperator = Mul<br />&nbsp;&nbsp;&nbsp; Create BinaryExpression<br />NUMBER[4] =&gt; 2[Number.1]<br />&nbsp;&nbsp;&nbsp; State Stack: 10[Exp.3], 5[Term.3], 19[Factor.Start]<br />&nbsp;&nbsp;&nbsp; Shift 5[Term]<br />&nbsp;&nbsp;&nbsp; Shift 19[Factor]<br />&nbsp;&nbsp;&nbsp; Assign value<br />&nbsp;&nbsp;&nbsp; Create NumberExpression<br />$TokenFinish =&gt; 11[Exp.RootEnd]<br />&nbsp;&nbsp;&nbsp; State Stack: <br />&nbsp;&nbsp;&nbsp; Reduce 19[Factor]<br />&nbsp;&nbsp;&nbsp; Using<br />&nbsp;&nbsp;&nbsp; Reduce 5[Term]<br />&nbsp;&nbsp;&nbsp; Assign secondOperand<br />&nbsp;&nbsp;&nbsp; Reduce 10[Exp]<br />&nbsp;&nbsp;&nbsp; Assign secondOperand</p>
<p>我们把所有跳转过的transition的信息都记录下来，就可以构造语法苏了。我们想象一下，在执行这些指令的时候，遇到NUMBER[4]就等于获得了一个内容为4的token，shift的话就是往堆栈里面push进一个状态的名字，而reduce则是弹出。</p>
<p>相对应的，因为每一个文法都会创建一个对象，所以我们在初始化的时候，要先放一个空对象在堆栈上。shift一次就再创建一个空的对象push进去，reduce的时候就把栈顶的对象弹出来作为&#8220;待处理对象&#8221;，using了就把待处理对象和栈顶对象合并，left-reduce就是把栈顶对象弹出来作为待处理对象的同时，push一个空对象进去。assign fieldName就是把&#8220;待处理对象&#8221;保存到栈顶对象的叫做fieldName的成员变量里面去。如果栈顶对象为空，那么被保存的对象就是刚刚输入的那个token了。因此我们从头到尾执行一遍之后，就可以得到下面的一颗语法树：</p>
<p>BinaryExpression {<br />&nbsp;&nbsp;&nbsp; binaryOperator = [Add]<br />&nbsp;&nbsp;&nbsp; firstOperand = BinaryExpression {<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; binaryOperator = [Mul]<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; firstOperand = NumberExpression {<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; value = [1]<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; secondOperand = NumberExpression {<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; value = [2]<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />&nbsp;&nbsp;&nbsp; }<br />&nbsp;&nbsp;&nbsp; secondOperand = BinaryExpression {<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; binaryOperator = [Mul]<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; firstOperand = NumberExpression {<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; value = [3]<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; secondOperand = NumberExpression {<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; value = [4]<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />&nbsp;&nbsp;&nbsp; }<br />}</p>
<p>基本上parsing的过程就结束了。在&#8220;下篇&#8221;&#8212;&#8212;也就是（六）&#8212;&#8212;里面，我会讲述如何处理右递归，然后这个系列基本上就要完结了。</p>  <img src ="http://www.cppblog.com/vczh/aggbug/196893.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/vczh/" target="_blank">陈梓瀚(vczh)</a> 2013-01-01 15:52 <a href="http://www.cppblog.com/vczh/archive/2013/01/01/196893.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>可配置语法分析器开发纪事（四）&amp;mdash;&amp;mdash;构造一个真正能用的状态机（上）</title><link>http://www.cppblog.com/vczh/archive/2012/12/23/196524.html</link><dc:creator>陈梓瀚(vczh)</dc:creator><author>陈梓瀚(vczh)</author><pubDate>Sat, 22 Dec 2012 16:28:00 GMT</pubDate><guid>http://www.cppblog.com/vczh/archive/2012/12/23/196524.html</guid><wfw:comment>http://www.cppblog.com/vczh/comments/196524.html</wfw:comment><comments>http://www.cppblog.com/vczh/archive/2012/12/23/196524.html#Feedback</comments><slash:comments>1</slash:comments><wfw:commentRss>http://www.cppblog.com/vczh/comments/commentRss/196524.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/vczh/services/trackbacks/196524.html</trackback:ping><description><![CDATA[<p>本来说这一篇文章要把构造确定性状态机和look ahead讲完的，当我真正要写的时候发现东西太多，只好分成两篇了。<a href="http://www.cppblog.com/vczh/archive/2012/12/07/196085.html" target="_blank">上一篇文章</a>说道一个基本的状态机是如何构造出来的，但是根据<a href="http://www.cppblog.com/vczh/archive/2012/11/21/195503.html" target="_blank">第一篇文章</a>的说法，这一次设计的文法是为了直接构造出语法树服务的，所以必然在执行状态机的时候就要获得构造语法树的一切信息。如果自己开发过类似的东西就会知道，类似LALR这种东西，你可以很容易的把整个字符串分析完判断他是不是属于这个LALR状态机描述的这个集合，但是你却不能拿到语法分析所走的路径，也就是说你很难直接拿到那颗分析树。没有分析树肯定是做不出语法树的。因此我们得把一些信息插入到状态机里面，才能最终把分析树（并不一定真的要表达成树，像上一篇文章的&#8220;分析路径&#8221;（其实就是分析树的一种可能的表达形式）所确定的语法树构造出来。</p>
<p>就像《<a href="http://www.cppblog.com/vczh/archive/2008/05/22/50763.html" target="_blank">构造正则表达式引擎</a>》一般给状态机添加信息的方法，就是把一些附加的数据加到状态与状态之间的跳转箭头里面去。为了形象的表达这个事情，我就拿第一篇文章的四则运算式子来举例。在这里我为了大家方便，重复一下这个文法的内容（除去了语树书声明）：</p>
<div style="border-bottom: #cccccc 1px solid; border-left: #cccccc 1px solid; padding-bottom: 5px; background-color: #f5f5f5; padding-left: 5px; padding-right: 5px; border-top: #cccccc 1px solid; border-right: #cccccc 1px solid; padding-top: 5px" class="cnblogs_code"><pre>token NAME = <span style="color: #800000">"</span><span style="color: #800000">[a-zA-Z_]/w*</span><span style="color: #800000">"</span><span style="color: #000000">;
token NUMBER </span>= <span style="color: #800000">"</span><span style="color: #800000">/d+(./d+)</span><span style="color: #800000">"</span><span style="color: #000000">;
token ADD </span>= <span style="color: #800000">"</span><span style="color: #800000">/+</span><span style="color: #800000">"</span><span style="color: #000000">;
token SUB </span>= <span style="color: #800000">"</span><span style="color: #800000">-</span><span style="color: #800000">"</span><span style="color: #000000">;
token MUL </span>= <span style="color: #800000">"</span><span style="color: #800000">/*</span><span style="color: #800000">"</span><span style="color: #000000">;
token DIV </span>= <span style="color: #800000">"</span><span style="color: #800000">//</span><span style="color: #800000">"</span><span style="color: #000000">;
token LEFT </span>= <span style="color: #800000">"</span><span style="color: #800000">/(</span><span style="color: #800000">"</span><span style="color: #000000">;
token RIGHT </span>= <span style="color: #800000">"</span><span style="color: #800000">/)</span><span style="color: #800000">"</span><span style="color: #000000">;
token COMMA </span>= <span style="color: #800000">"</span><span style="color: #800000">,</span><span style="color: #800000">"</span><span style="color: #000000">;

rule NumberExpression Number
        </span>=<span style="color: #000000"> NUMBER : value;

rule FunctionExpression Call
        </span>= NAME : functionName <span style="color: #800000">"</span><span style="color: #800000">(</span><span style="color: #800000">"</span> [ Exp : arguments { <span style="color: #800000">"</span><span style="color: #800000">,</span><span style="color: #800000">"</span> Exp : arguments } ] <span style="color: #800000">"</span><span style="color: #800000">)</span><span style="color: #800000">"</span><span style="color: #000000">;

rule Expression Factor
        </span>= !Number | !<span style="color: #000000">Call;

rule Expression Term
        </span>= !<span style="color: #000000">Factor;
        </span>= Term : firstOperand <span style="color: #800000">"</span><span style="color: #800000">*</span><span style="color: #800000">"</span> Factor : secondOperand <span style="color: #0000ff">as</span> BinaryExpression with { binaryOperator = <span style="color: #800000">"</span><span style="color: #800000">Mul</span><span style="color: #800000">"</span><span style="color: #000000"> };
        </span>= Term : firstOperand <span style="color: #800000">"</span><span style="color: #800000">/</span><span style="color: #800000">"</span> Factor : secondOperand <span style="color: #0000ff">as</span> BinaryExpression with { binaryOperator = <span style="color: #800000">"</span><span style="color: #800000">Div</span><span style="color: #800000">"</span><span style="color: #000000"> };

rule Expression Exp
        </span>= !<span style="color: #000000">Term;
        </span>= Exp : firstOperand <span style="color: #800000">"</span><span style="color: #800000">+</span><span style="color: #800000">"</span> Term : secondOperand <span style="color: #0000ff">as</span> BinaryExpression with { binaryOperator = <span style="color: #800000">"</span><span style="color: #800000">Add</span><span style="color: #800000">"</span><span style="color: #000000"> };
        </span>= Exp : firstOperand <span style="color: #800000">"</span><span style="color: #800000">-</span><span style="color: #800000">"</span> Term : secondOperand <span style="color: #0000ff">as</span> BinaryExpression with { binaryOperator = <span style="color: #800000">"</span><span style="color: #800000">Sub</span><span style="color: #800000">"</span> };</pre></div>
<p>那么我们把这个文发转成状态机之后，要给跳转加上什么呢？从直觉上来说，跳转的时候我们会有六种要干的事情：<br />1、Create：这个文法创建的语法树节点是某个类型的（区别于在这一刻给这个问法创建一个返回什么类型的语法树节点）<br />2、Set：给创建的语法树节点的某个成员变量设置一个指定的值<br />3、Assign：给创建的语法树节点的某个成员变量设置这一次跳转的符号产生的语法树节点（譬如说Exp = Exp: firstOperand &#8220;+&#8221; Term: secondOperand，走Term的时候，一个语法树节点就会被assign给那个叫做secondOperand的成员变量）<br />4、Using：使用这一次跳转的符号产生的语法树节点来做这次文法的返回值（譬如说Factor = !Number | !Caller这一条）<br />5、Shift：略<br />6、Reduce：略</p>
<p>在这里我们并没有标记整个文法从哪一个非终结符开始，因为在实际过程中，其实分析师可以从任何一个文法开始的。譬如说写IDE的时候，我们可能在某些情况下仅仅只需要分析一个表达式。所以考虑到每一个非终结符都有可能被用到，因此我们的&#8220;Token流开始&#8221;和&#8220;Token流结束&#8221;就会在每一个非终结符的状态机中都出现。因此在第一步创建Epsilon PDA（下推自动机）的时候，就可以先直接生成。在这里我们拿Exp做例子：</p>
<p><a href="http://www.cppblog.com/images/cppblog_com/vczh/Windows-Live-Writer/e061a408f76c_139DA/image_18.png"><img style="background-image: none; border-bottom: 0px; border-left: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top: 0px; border-right: 0px; padding-top: 0px" title="image" border="0" alt="image" src="http://www.cppblog.com/images/cppblog_com/vczh/Windows-Live-Writer/e061a408f76c_139DA/image_thumb_8.png" width="644" height="265" /></a></p>
<p>双虚线代表的是Token流和Token流结束，这并不是我们现在关心的事情。在剩下的转换中，实现是具有输入的转换，而虚线则是没有输入的转换（一般称为epsilon边）。</p>
<p>在这里我们要明确一个概念&#8212;&#8212;分析路径。分析路径代表的是token在&#8220;流&#8221;过状态机的时候，状态是如何跳转的。因此对于实际的分析过程，分析路径其实就是分析树的一种表达形式。而在状态机里面，分析路径则代表一条从开始到结尾的可能的路径。譬如说在这里，分析路径可以有三条：<br />$e &#8211;&gt; e1 &#8211;&gt; e2 &#8211;&gt; e$<br />$e &#8211;&gt; e3 &#8211;&gt; e8 &#8211;&gt; e7 &#8211;&gt; e6 &#8211;&gt; e5 &#8211;&gt; e4 &#8211;&gt; e$<br />$e &#8211;&gt; e9 &#8211;&gt; e14 &#8211;&gt; e13 &#8211;&gt; e12 &#8211;&gt; e11 &#8211;&gt; e10 &#8211;&gt; e$</p>
<p>因此我们可以清楚，一条路径上是不能出现多个create的，否则你就不知道应该创建的是什么了。当然create和using都不能同时出现，using也不能有多个。而且由于create和set都是在描述这个非终结符（在这里是Exp）所创建的语法树节点的类型和属性，跟执行他们的时机无关，所以其实在同一条分析路径里面，create和set放在哪里都没关系。就譬如说在上面的第二条分析路径里面，create是在e6-&gt;e5里面标记出来的。就算他移动到了e3-&gt;e8，做的事情也一样。反正只要一条路径上标记了create，那么他在这条路径被确定之后，就一定会create所指定的具体类型的语法树节点。这是相当重要的，因为在后面的分析中，我们很可能需要移动create和set的具体位置。</p>
<p>跟上一篇文章说的一样，接下来的一步就是去除epsilon边了。结果如下：</p>
<p><a href="http://www.cppblog.com/images/cppblog_com/vczh/Windows-Live-Writer/e061a408f76c_139DA/image_20.png"><img style="background-image: none; border-bottom: 0px; border-left: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top: 0px; border-right: 0px; padding-top: 0px" title="image" border="0" alt="image" src="http://www.cppblog.com/images/cppblog_com/vczh/Windows-Live-Writer/e061a408f76c_139DA/image_thumb_9.png" width="644" height="332" /></a></p>
<p>面对这种状态机，去除epsilon边就不能跟处理正则表达式一样简单的去除了。首先，所有的终结状态&#8212;&#8212;也就是所有经过或者不经过epsilon边之后，通过&#8220;Token流结束&#8221;符号连接到最后一个状态的状态，在这里分别是e2、e6和e12&#8212;&#8212;都是不能删掉的。而且所有的&#8220;Token流开始&#8221;和&#8220;Token流结束&#8221;&#8212;&#8212;也就是图里面的$转换&#8212;&#8212;是不能带有信息的。所以我们就会看到e6后面的信息全部被移动到了e7-&gt;e6这条边上面。由于create和set的流动性，我们这么做对于状态机的定义完全没有影响。</p>
<p>到了这里还没完，因为这个状态机还是有很多冗余的状态的。譬如说e8和e14、e7和e13、e2和e6和e12实际上是可以合并的。合并的策略其实十分简单：</p>
<p>1、如果我们有跳转e0-&gt;e1和e0-&gt;e2，并且两个跳转所携带的token输入和信息完全一致的话，那么e1和e2就可以合并。<br />2、如果我们有跳转e1-&gt;e0和e2-&gt;e0，并且两个跳转所携带的token输入和信息完全一致的话，那么e1和e2就可以合并。</p>
<p>所以对于e8和e14我们是完全可以合并的。那么e7和e13怎么办呢？根据create和set的流动性，我们只要把这两个东西挪到他的前面一个或者若干个跳转去，那这两个状态就可以合并了。为了让算法更加的简单，我们遇到两个跳转类似的时候，总是先挪动create和set，然后再看看是不是真的可以合并。所以这一步处理完之后就会变成下面这个样子：</p>
<p><a href="http://www.cppblog.com/images/cppblog_com/vczh/Windows-Live-Writer/e061a408f76c_139DA/image_22.png"><img style="background-image: none; border-bottom: 0px; border-left: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top: 0px; border-right: 0px; padding-top: 0px" title="image" border="0" alt="image" src="http://www.cppblog.com/images/cppblog_com/vczh/Windows-Live-Writer/e061a408f76c_139DA/image_thumb_10.png" width="644" height="372" /></a></p>
<p>我们在不改变状态机语义的情况下，把Exp的三个状态机最终压缩成了这个样子。看过上一篇文章的同学们都知道，下一步就是要把所有的状态机统统都连接起来了。关于在连接的时候如何具体操作转换附带的信息、以及做出一个确定性的下推状态机的所有事情将在下一篇文章详细解释。大家敬请期待。</p><img src ="http://www.cppblog.com/vczh/aggbug/196524.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/vczh/" target="_blank">陈梓瀚(vczh)</a> 2012-12-23 00:28 <a href="http://www.cppblog.com/vczh/archive/2012/12/23/196524.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>可配置语法分析器开发纪事（三点五）&amp;mdash;&amp;mdash;生成下推自动机的具体步骤</title><link>http://www.cppblog.com/vczh/archive/2012/12/07/196085.html</link><dc:creator>陈梓瀚(vczh)</dc:creator><author>陈梓瀚(vczh)</author><pubDate>Fri, 07 Dec 2012 10:49:00 GMT</pubDate><guid>http://www.cppblog.com/vczh/archive/2012/12/07/196085.html</guid><wfw:comment>http://www.cppblog.com/vczh/comments/196085.html</wfw:comment><comments>http://www.cppblog.com/vczh/archive/2012/12/07/196085.html#Feedback</comments><slash:comments>2</slash:comments><wfw:commentRss>http://www.cppblog.com/vczh/comments/commentRss/196085.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/vczh/services/trackbacks/196085.html</trackback:ping><description><![CDATA[<p>刚刚发了<a href="http://www.cppblog.com/vczh/archive/2012/12/07/196079.html" target="_blank">上一篇文章</a>之后就发现状态机画错了。虽然LiveWriter有打开博客并修改文章的功能，不过为了让我留下一个教训，我还是决定发一篇勘误。这个教训就是，作分析的时候不要随便&#8220;跳步&#8221;，该一步一步来就一步一步来。其实人呢，就是很容易忘掉以前的教训的了。第一个告诉我不能这么干的人其实是小学三年级的数学老师。当时我因为懒得写字，所以计算应用题的时候省了几步，被批评了。</p> <p>故事就从状态机开始。文法我就不重复了，见上一篇文章。现在我们从状态机开始。第一个状态机是直接从文法变过来的：</p> <p><a href="http://www.cppblog.com/images/cppblog_com/vczh/Windows-Live-Writer/9a8c729f1e27_F803/image_6.png"><img style="background-image: none; border-bottom: 0px; border-left: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top: 0px; border-right: 0px; padding-top: 0px" title="image" border="0" alt="image" src="http://www.cppblog.com/images/cppblog_com/vczh/Windows-Live-Writer/9a8c729f1e27_F803/image_thumb_2.png" width="644" height="439"></a></p> <p>然后我们把所有的非终结符跳转都通过Shift和Reduce连接到该非终结符所代表的状态机的状态上面，就会变成下面的图。具体的做法是，对于每一条非终结符的跳转，譬如说S0 &#8211;&gt; Symbol &#8211;&gt; S1。首先抹掉这条跳转。然后增加两条边，分别是S0到Symbol的起始节点，操作是Shift&lt;S0&gt;。还有从Symbol的终结节点到S0，操作是Pop&lt;S0&gt; Reduce。Shift&lt;S&gt;等于把状态S给push到堆栈里，然后Pop&lt;S&gt;等于在状态里面弹出内容是S的栈顶元素。如果失败了怎么办呢？那就不能用这条跳转。跟上图一样，所有输入$跳转到Finish的边，操作都是要Pop&lt;Null&gt;的。在刚开始分析的时候，堆栈有一个Null值，用来代表&#8220;语法分析从这里开始&#8221;。</p> <p><a href="http://www.cppblog.com/images/cppblog_com/vczh/Windows-Live-Writer/9a8c729f1e27_F803/image_10.png"><img style="background-image: none; border-bottom: 0px; border-left: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top: 0px; border-right: 0px; padding-top: 0px" title="image" border="0" alt="image" src="http://www.cppblog.com/images/cppblog_com/vczh/Windows-Live-Writer/9a8c729f1e27_F803/image_thumb_4.png" width="644" height="439"></a></p>    <p>这个图的粗虚边代表所有跟左递归有关的跳转。这些边是成对的，分别是左递归跳转的Shift和Reduce。如果不是为了实现高性能的语法分析的话，其实这个状态机已经足够了。这个图跟语法分析的&#8220;状态跳转轨迹&#8221;有很大的关系。虽然IDList0你不知道第一步要跳转到IDList0还是ID0，不过没关系，现在我们先假设我们可以通过某种神秘的方法来预测到。那么，当输入是A,B,C$的时候，状态跳转轨迹就会是如下的样子：</p> <p><a href="http://www.cppblog.com/images/cppblog_com/vczh/Windows-Live-Writer/9a8c729f1e27_F803/image_12.png"><img style="background-image: none; border-bottom: 0px; border-left: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top: 0px; border-right: 0px; padding-top: 0px" title="image" border="0" alt="image" src="http://www.cppblog.com/images/cppblog_com/vczh/Windows-Live-Writer/9a8c729f1e27_F803/image_thumb_5.png" width="466" height="397"></a></p> <p>为什么要这么做呢？我们把这幅图想象成为<br>1：想做的箭头表示push一个状态<br>2：向下的箭头表示修改当前状态<br>3：向右的状态表示pop一个状态并修改当前状态</p> <p>因此当输入到B的时候，到达ID1，并跳转到IDList1。这个时候IDList1【左边】的所有【还留在堆栈里】的状态时Null和IDList0，当前状态IDList1，输入剩下,C$。这个图特别的有用。当我们分析完并且把构造语法树的指令附着在这些箭头上面之后，按顺序执行这些指令就可以构造出一颗完整的语法树了。</p> <p>但是在实际操作里面，我们并没有办法预测&#8220;这里要左递归两次&#8221;，也没办法在多次reduce的时候选择究竟要从哪里跳到哪里。所以实际上我们要学习从EpsilonNFA到DFA的那个计算过程，把Shift和Reduce当成Epsilon，把吃掉一个token当成非Epsilon边，然后执行我之前写的《<a href="http://www.cppblog.com/vczh/archive/2008/05/22/50763.html" target="_blank">构造可配置词法分析器</a>》一文中的那个去Epsilon边算法（如何从Nondeterministic到Deterministic，以及相关的Look Ahead，是下一篇文章的内容），然后就可以把状态机变成这样：</p> <p><a href="http://www.cppblog.com/images/cppblog_com/vczh/Windows-Live-Writer/9a8c729f1e27_F803/image_18.png"><img style="background-image: none; border-bottom: 0px; border-left: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top: 0px; border-right: 0px; padding-top: 0px" title="image" border="0" alt="image" src="http://www.cppblog.com/images/cppblog_com/vczh/Windows-Live-Writer/9a8c729f1e27_F803/image_thumb_8.png" width="644" height="453"></a></p> <p>上面粗体的Pop&lt;IDList0&gt;表示，这一个Pop是对应于那个左递归Shifting操作的。实际上这是做了一个怎样的变化呢？从&#8220;物理解释&#8221;上来讲，其实是把&#8220;状态跳转轨迹&#8221;里面那些除了左递归shifting之外的所有不吃掉token的边都去掉了：</p> <p><a href="http://www.cppblog.com/images/cppblog_com/vczh/Windows-Live-Writer/9a8c729f1e27_F803/image_22.png"><img style="background-image: none; border-bottom: 0px; border-left: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top: 0px; border-right: 0px; padding-top: 0px" title="image" border="0" alt="image" src="http://www.cppblog.com/images/cppblog_com/vczh/Windows-Live-Writer/9a8c729f1e27_F803/image_thumb_10.png" width="466" height="397"></a></p>  <p>在这里我们可以看到，为什么当堆栈是IDList0, IDList0和IDList0, IDList3的时候，从ID0都可以通过吃掉一个&#8221;,&#8221;从而跳转到IDList3。在上面这张&#8220;状态跳转轨迹&#8221;里面，这两个事情都发生了，分别是第一条向左的箭头和第二条向左的方向。而且这两条边刚好对应于上图带有蓝色粗体文字的跳转，属于左递归Reducing操作。</p> <p>所以，其实在这个时候，我们同时解决了&#8220;应该在什么时候进行左递归Shifting&#8221;的问题。只要当左递归Reducing已发生，我们立刻在轨迹上面补上一条左递归Shifting就好了。因此，我们在一开始做parsing的时候，根本不需要预先做左递归Shifting。所以当刚刚输入A的时候，&#8220;状态跳转轨迹&#8221;是这样子的：</p> <p><a href="http://www.cppblog.com/images/cppblog_com/vczh/Windows-Live-Writer/9a8c729f1e27_F803/image_24.png"><img style="background-image: none; border-bottom: 0px; border-left: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top: 0px; border-right: 0px; padding-top: 0px" title="image" border="0" alt="image" src="http://www.cppblog.com/images/cppblog_com/vczh/Windows-Live-Writer/9a8c729f1e27_F803/image_thumb_11.png" width="270" height="107"></a></p> <p>然后遇到一个&#8221;,&#8221;，发现之前&#8220;做漏&#8221;了一个左递归Shifting，因此就变成下面这个样子：</p> <p><a href="http://www.cppblog.com/images/cppblog_com/vczh/Windows-Live-Writer/9a8c729f1e27_F803/image_26.png"><img style="background-image: none; border-bottom: 0px; border-left: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top: 0px; border-right: 0px; padding-top: 0px" title="image" border="0" alt="image" src="http://www.cppblog.com/images/cppblog_com/vczh/Windows-Live-Writer/9a8c729f1e27_F803/image_thumb_12.png" width="370" height="165"></a></p> <p>这也就是上一篇文章那个Fake-Shift所做的事情了。</p> <img src ="http://www.cppblog.com/vczh/aggbug/196085.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/vczh/" target="_blank">陈梓瀚(vczh)</a> 2012-12-07 18:49 <a href="http://www.cppblog.com/vczh/archive/2012/12/07/196085.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>可配置语法分析器开发纪事（三）&amp;mdash;&amp;mdash;生成下推自动机</title><link>http://www.cppblog.com/vczh/archive/2012/12/07/196079.html</link><dc:creator>陈梓瀚(vczh)</dc:creator><author>陈梓瀚(vczh)</author><pubDate>Fri, 07 Dec 2012 08:43:00 GMT</pubDate><guid>http://www.cppblog.com/vczh/archive/2012/12/07/196079.html</guid><wfw:comment>http://www.cppblog.com/vczh/comments/196079.html</wfw:comment><comments>http://www.cppblog.com/vczh/archive/2012/12/07/196079.html#Feedback</comments><slash:comments>2</slash:comments><wfw:commentRss>http://www.cppblog.com/vczh/comments/commentRss/196079.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/vczh/services/trackbacks/196079.html</trackback:ping><description><![CDATA[<p>上一篇博客讲到了构造符号表的事情。构造完符号表之后，就要进入语义分析的后一个阶段了：构造状态机。跟我以前写<a href="http://www.cppblog.com/vczh/archive/2008/05/22/50763.html" target="_blank">的如何实现正则表达式引擎的两篇文章</a>讲的一样，自动机先从Epsilon Nondeterministic Automaton开始，然后一步一步构造成Deterministic Automaton。但是语法分析和正则表达式有很大不同，那么这个自动机是什么样子的呢？</p> <p>（对学术感兴趣的人可以去wiki一下&#8220;下推自动机&#8221;）</p> <p>下推自动机和有限自动机的区别是，下推自动机扩展成普通的自动机的时候，他的状态的数目是无限的（废话）。但是无限的东西是没办法用编程来表达的，那怎么办呢？那就加入一个不定长度的&#8220;状态描述&#8221;。下面我举一个简单的文法：</p> <p>ID = NAME<br>IDList = ID | IDList &#8220;,&#8221; ID </p> <p>这样就构成了一个简单的文法，用来分析带逗号分割的名字列表的。那么写成状态机就是如下的形式：</p> <p>ID0 = ● NAME<br>ID1 = NAME ●<br>IDList0 = ● (ID | IDList &#8220;," ID)<br>IDList1 = (ID | IDList &#8220;,&#8221; ID) ●<br>IDList2 = (ID | IDList ● &#8220;,&#8221; ID)<br>IDList3 = (ID | IDList &#8220;,&#8221; ● ID)</p> <p>ID0 &#8211;&gt; NAME &#8211;&gt; ID1<br>IDList0 &#8211;&gt; ID &#8211;&gt; IDList1<br>IDList0 &#8211;&gt; IDList &#8211;&gt; IDList2<br>IDList2 &#8211;&gt; &#8220;,&#8221; &#8211;&gt; IDList3<br>IDList3 &#8211;&gt; ID &#8211;&gt; IDList1</p> <p>可以很容易的看出，ID0和IDList0是文法的起始状态，而ID1和IDList1是文法的终结状态，画成图如下：</p> <p><a href="http://www.cppblog.com/images/cppblog_com/vczh/Windows-Live-Writer/c1f82c9f0c3b_C745/image_4.png"><img style="background-image: none; border-bottom: 0px; border-left: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top: 0px; border-right: 0px; padding-top: 0px" title="image" border="0" alt="image" src="http://www.cppblog.com/images/cppblog_com/vczh/Windows-Live-Writer/c1f82c9f0c3b_C745/image_thumb_1.png" width="467" height="372"></a></p> <p>（PowerPoint画图复制到LiveWriter里面是一幅图面简直太方便了）</p> <p>但是这样还没完。IDList0跳到IDList2的时候的输入&#8220;IDList&#8221;其实还不够，因为用作输入的token其实只有NAME和","两种。下一步即将演示如何从这个状态机编程名副其实的下推状态机。</p> <p>在这里我先介绍几个概念。第一个是移进，第二个是规约。为什么要用这两个名字呢？因为大部分人看的傻逼清华大学出版社的低能编译原理课本都是这么讲的，黑化分别叫Shift和Reduce。好了，什么是Shift呢？IDList0跳到IDList2的时候，要移进IDList。IDList3跳到IDList1，要移进到ID。IDList0跳到IDList1也要移进到ID。这也就是说，<strong>状态转移经过一条非终结符的边的时候会移进到另一条文法的状态机里</strong>。ID1和IDList1作为ID和IDList的终结节点，要根据&#8220;从那里移进来的&#8221;分别规约然后跳转到&#8220;IDList2或者IDList1&#8221;。这也就是说，<strong>一旦你到达了一条闻法的状态机的终结状态，就要开始规约然后跳转到上一级的状态了</strong>。</p> <p>有人要问，那我怎么知道规约结束的时候要跳转去哪里呢？这个问题问得非常好。让我们回想一下我以前写的<a href="http://www.cppblog.com/vczh/archive/2008/06/15/53373.html" target="_blank">如何手写语法分析器</a>这一篇文章。里面怎么说的？当你手写递归下降的语法分析器的时候，每一条文法其实都是一个函数。那调用函数的时候程序怎么就知道函数结束的时候下一条指令是什么呢？那当然是因为编译器帮我们把&#8220;调用函数的时候的下一条指令的地址&#8221;给push进了调用堆栈。但是我们现在不手写语法分析器了，而用下推状态机来做，道理也是一样的。在&#8220;移进&#8221;的时候，先把当前的状态push进堆栈，规约的时候，就可以看一下&#8220;栈顶那几个状态都是什么&#8221;，配合一次向前查看（这就是Look Ahead。LALR的那个LA，LALR(1)就是在LA的时候偷看一个token），来决定规约到哪里去。至于LA在这里的深刻内涵我将下一篇文章再说。因为现在我还没有做到Nondeterministic到Deterministic的一步，里面有很多黑科技，我想集中讨论。</p> <p>那现在让我们把上面那幅图的两个状态机连起来，产生一个下推自动机。但是在这里我先做第一步。因为IDList0到IDList1的跳转是一个左递归的过程，先暂时不管。</p> <p><a href="http://www.cppblog.com/images/cppblog_com/vczh/Windows-Live-Writer/c1f82c9f0c3b_C745/image_8.png"><img style="background-image: none; border-bottom: 0px; border-left: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top: 0px; border-right: 0px; padding-top: 0px" title="image" border="0" alt="image" src="http://www.cppblog.com/images/cppblog_com/vczh/Windows-Live-Writer/c1f82c9f0c3b_C745/image_thumb_3.png" width="671" height="372"></a></p>  <p>橙色的边都是一个输入非终结符的跳转，所以实际上在下推状态机里面是不存在的。在这张图里面我们处理了两条ID的边。IDList0会shift（就是在堆栈里面push）自己然后跳转到ID0，因此ID1在查看到栈顶是IDList0的时候，他就知道走的是IDList0 &#8211;&gt; ID &#8211;&gt; IDList1这条路，因此就reduce并跳转到了IDList1。IDList3同理。</p> <p>但是Shift的时候并没有产生输入，所以实际上应该改成下面这个样子。</p> <p><a href="http://www.cppblog.com/images/cppblog_com/vczh/Windows-Live-Writer/c1f82c9f0c3b_C745/image_10.png"><img style="background-image: none; border-bottom: 0px; border-left: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top: 0px; border-right: 0px; padding-top: 0px" title="image" border="0" alt="image" src="http://www.cppblog.com/images/cppblog_com/vczh/Windows-Live-Writer/c1f82c9f0c3b_C745/image_thumb_4.png" width="618" height="372"></a></p> <p>这样Shift边也就有输入了。而且ID0到ID1也废掉了。实际上ID0自己也应该废掉。现在还有一个问题没解决，就是左递归和Reduce不产生输入的问题。这两个问题实际上是一起的。我们先来考虑一下为什么这里没办法用相同的办法来把Reduce处理成产生输入的。实际上是因为，你在这一个阶段还不知道究竟Reduce要输入什么才能跳转，特别是token已经结束并且parse出了一个完整的IDList的时候。以前你们是不是在看《Parsing Techniques》和《龙书》都对为什么一个字符串结尾要产生一个$字符感到很困惑呢？实际上他是特别有用的。现在我们来给他加上大家就明白了。在这里，这个文法的目标是产生一个IDList结构，所以$当然也要加在IDList的终结状态——IDList1上：</p> <p><a href="http://www.cppblog.com/images/cppblog_com/vczh/Windows-Live-Writer/c1f82c9f0c3b_C745/image_12.png"><img style="background-image: none; border-bottom: 0px; border-left: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top: 0px; border-right: 0px; padding-top: 0px" title="image" border="0" alt="image" src="http://www.cppblog.com/images/cppblog_com/vczh/Windows-Live-Writer/c1f82c9f0c3b_C745/image_thumb_5.png" width="674" height="372"></a></p> <p>然后就轮到Reduce。ID1应该是Reduce到哪里了？第一步自然是Reduce到IDList1。那么IDList1又要Reduce到哪里呢？我们可以看到，在IDList结束的时候，要么就是跳到IDList2，要么就是跳到FINISH。但是IDList2是通过左递归产生的，我们先不管他。跳到FINISH需要什么条件呢？第一个是输入$，第二个是Pop完状态之后堆栈会为空。所以这个时候我们可以先修改一下ID1到IDList1的Reduce边：</p> <p><a href="http://www.cppblog.com/images/cppblog_com/vczh/Windows-Live-Writer/c1f82c9f0c3b_C745/image_14.png"><img style="background-image: none; border-bottom: 0px; border-left: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top: 0px; border-right: 0px; padding-top: 0px" title="image" border="0" alt="image" src="http://www.cppblog.com/images/cppblog_com/vczh/Windows-Live-Writer/c1f82c9f0c3b_C745/image_thumb_6.png" width="674" height="372"></a></p> <p>最后就是左递归了。左递归的处理有点像hack，因为实际上你不能预先判断你要不要左递归（也就是看一下token stream有多少个逗号），然后先shift几个IDList0进去，再慢慢来。所以我们只有在满足跳转关系的时候临时插入一些IDList0。那么这个关系是什么呢？左递归的IDList结束——也就是从IDList0跳到IDList2——之后只有一种可能，就是输入","。而且所有指向IDList1的边都是输入ID，所以这条左递归的线应该从ID1（ID的终结状态）连到IDList2，并且在链接的时候补充&#8220;假shift IDList0&#8221;：</p> <p><a href="http://www.cppblog.com/images/cppblog_com/vczh/Windows-Live-Writer/c1f82c9f0c3b_C745/image_31.png"><img style="background-image: none; border-bottom: 0px; border-left: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top: 0px; border-right: 0px; padding-top: 0px" title="image" border="0" alt="image" src="http://www.cppblog.com/images/cppblog_com/vczh/Windows-Live-Writer/c1f82c9f0c3b_C745/image_thumb_14.png" width="674" height="424"></a></p> <p>橙色的两个状态分别是整个parsing过程的起始状态和终结状态。这个时候我们把所有没用的边和状态都干掉，就变成了：</p> <p><a href="http://www.cppblog.com/images/cppblog_com/vczh/Windows-Live-Writer/c1f82c9f0c3b_C745/image_33.png"><img style="background-image: none; border-bottom: 0px; border-left: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top: 0px; border-right: 0px; padding-top: 0px" title="image" border="0" alt="image" src="http://www.cppblog.com/images/cppblog_com/vczh/Windows-Live-Writer/c1f82c9f0c3b_C745/image_thumb_15.png" width="674" height="362"></a></p> <p>是不是觉得特别亲切呢，这不就是正则表达式NAME ( &#8220;,&#8221; NAME)*的状态机吗？这也是因为这个文法刚好可以表达为一个正则文法才有这样的结果，如果我们给他加点儿括号改变点优先级什么的，那就会变成一个复杂得多的状态机了。好了。现在我们来模拟一下下推状态机的状态转换和堆栈操作过程，来分析一下A,B,C$这个输入吧。</p> <p>在下面的标示图里面，我们用s|abc|def来分别表达当前状态s、当前堆栈里的状态abc（栈顶在右边）和正在等待的输入def。那么初始状态肯定就是<br>IDList0 | null | A,B,C$</p> <p>然后就开始了！（用文字表达实在是太难看了，所以贴成图）</p>    <p><a href="http://www.cppblog.com/images/cppblog_com/vczh/Windows-Live-Writer/c1f82c9f0c3b_C745/image_35.png"><img style="background-image: none; border-bottom: 0px; border-left: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top: 0px; border-right: 0px; padding-top: 0px" title="image" border="0" alt="image" src="http://www.cppblog.com/images/cppblog_com/vczh/Windows-Live-Writer/c1f82c9f0c3b_C745/image_thumb_16.png" width="675" height="500"></a></p> <p>如果成功到达FINISH并且堆栈和输入都全部没有了的话，那就证明，parsing过程完美结束，没有任何错误发生。</p> <p>如何从文法生成下推自动机并完成parsing工作的大概过程就写到这里了。目前开发进度是到&#8220;生成非确定性下推自动机&#8221;这里。当我完成了生成&#8220;确定性下推自动机&#8221;——也就是上面的最后一个状态机图的时候——就会开始写下一篇文章，讲面对复杂的文法的时候，下推自动机将要如何调整。同时将重点描述Look Ahead部分，以及为什么LALR(1)要设计成那个样子。</p> <img src ="http://www.cppblog.com/vczh/aggbug/196079.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/vczh/" target="_blank">陈梓瀚(vczh)</a> 2012-12-07 16:43 <a href="http://www.cppblog.com/vczh/archive/2012/12/07/196079.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>可配置语法分析器开发纪事（二）&amp;mdash;&amp;mdash;构造符号表</title><link>http://www.cppblog.com/vczh/archive/2012/11/29/195779.html</link><dc:creator>陈梓瀚(vczh)</dc:creator><author>陈梓瀚(vczh)</author><pubDate>Wed, 28 Nov 2012 16:50:00 GMT</pubDate><guid>http://www.cppblog.com/vczh/archive/2012/11/29/195779.html</guid><wfw:comment>http://www.cppblog.com/vczh/comments/195779.html</wfw:comment><comments>http://www.cppblog.com/vczh/archive/2012/11/29/195779.html#Feedback</comments><slash:comments>6</slash:comments><wfw:commentRss>http://www.cppblog.com/vczh/comments/commentRss/195779.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/vczh/services/trackbacks/195779.html</trackback:ping><description><![CDATA[<p>上一篇博客讲到了构造语法树的问题。有朋友在留言问我，为什么一定要让语法分析器产生语法树，而不是让用户自己决定要怎么办呢？在这里我先解答这个问题。</p> <p>1、大部分情况下都是真的需要有语法树<br>2、如果要直接返回计算结果之类的事情的话，只需要写一个visitor运行一下语法树就好了，除去自动生成的代码以外（反正这不用人写，不计入代价），代码量基本上没什么区别<br>3、加入语法树可以让文法本身描述起来更简单，如果要让程序员把文法单独放在一边，然后自己写完整的语义函数来让他生成语法树的话，会让大部分情况（需要语法树）变得特别复杂，而少数情况（不需要语法树）又没有获得什么好处。</p> <p>尽管类似yacc这样的东西的确是不包含语法树的内容而要你自己写的，但是用起来难道不是很难受吗？</p> <p>现在转入正题。这一篇文章讲的主要是构造符号表的问题。想要把符号表构造的好是一件很麻烦的问题。我曾经尝试过很多种方法，包括强类型的符号表，弱类型的符号表，基于map的符号表等等，最后还是挑选了跟Visual Studio自带的用来读pdb文件的DIA类其中的IDIASymbol（<a title="http://msdn.microsoft.com/en-us/library/w0edf0x4.aspx" href="http://msdn.microsoft.com/en-us/library/w0edf0x4.aspx" target="_blank">http://msdn.microsoft.com/en-us/library/w0edf0x4.aspx</a>）基本上一样的结构：所有的符号都只有这么一个symbol类，然后包罗万象，什么都有。为什么最后选择这么做呢？因为在做语义分析的时候，其实做的最多的事情不是构造符号表，而是查询符号表。如果符号表是强类型的画，譬如说类型要一个类，变量要一个类，函数要一个类之类的，总是需要到处cast来cast去，也找不到什么好方法来在完成相同事情的情况下，保留强类型而不在代码里面出现cast。为什么语法树就要用visitor来解决这个问题，而符号表就不行呢？因为通常我们在处理语法树的时候都是递归的形式，而符号表并不是。在一个上下文里面，实际上我们是知道那个symbol对象究竟是什么东西的（譬如说我们查询了一个变量的type，那这返回值肯定只能是type了）。这个时候我们要cast才能用，本身也只是浪费表情而已。这个时候，visitor模式就不是和面对这种情况了。如果硬要用visitor模式来写，会导致语义分析的代码分散得过于离谱导致可读性几乎就丧失了。这是一个辩证的问题，大家可以好好体会体会。</p> <p>说了这么一大段，实际上就是怎么样呢？让我们来看&#8220;文法规则&#8221;本身的符号表吧。既然这个新的可配置语法分析器也是通过parse一个文本形式的文法规则来生成parser，那实际上就跟编译器一样要经历那么多阶段，其中肯定有符号表：</p> <div style="border-bottom: #cccccc 1px solid; border-left: #cccccc 1px solid; padding-bottom: 5px; background-color: #f5f5f5; padding-left: 5px; padding-right: 5px; border-top: #cccccc 1px solid; border-right: #cccccc 1px solid; padding-top: 5px" class="cnblogs_code"><pre><span style="color: #0000ff">class</span> ParsingSymbol : <span style="color: #0000ff">public</span><span style="color: #000000"> Object
{
</span><span style="color: #0000ff">public</span><span style="color: #000000">:
    </span><span style="color: #0000ff">enum</span><span style="color: #000000"> SymbolType
    {
        Global,
        EnumType,
        ClassType,        </span><span style="color: #008000">//</span><span style="color: #008000"> descriptor == base type</span>
        ArrayType,        <span style="color: #008000">//</span><span style="color: #008000"> descriptor == element type</span>
<span style="color: #000000">        TokenType,
        EnumItem,        </span><span style="color: #008000">//</span><span style="color: #008000"> descriptor == parent</span>
        ClassField,        <span style="color: #008000">//</span><span style="color: #008000"> descriptor == field type</span>
        TokenDef,        <span style="color: #008000">//</span><span style="color: #008000"> descriptor == token type</span>
        RuleDef,        <span style="color: #008000">//</span><span style="color: #008000"> descriptor == rule type</span>
<span style="color: #000000">    };
</span><span style="color: #0000ff">public</span><span style="color: #000000">:
    </span>~<span style="color: #000000">ParsingSymbol();

    ParsingSymbolManager</span>*<span style="color: #000000">            GetManager();
    SymbolType                        GetType();
    </span><span style="color: #0000ff">const</span> WString&amp;<span style="color: #000000">                    GetName();
    vint                            GetSubSymbolCount();
    ParsingSymbol</span>*<span style="color: #000000">                    GetSubSymbol(vint index);
    ParsingSymbol</span>*                    GetSubSymbolByName(<span style="color: #0000ff">const</span> WString&amp;<span style="color: #000000"> name);
    ParsingSymbol</span>*<span style="color: #000000">                    GetDescriptorSymbol();
    ParsingSymbol</span>*<span style="color: #000000">                    GetParentSymbol();
    </span><span style="color: #0000ff">bool</span><span style="color: #000000">                            IsType();
    ParsingSymbol</span>*                    SearchClassSubSymbol(<span style="color: #0000ff">const</span> WString&amp;<span style="color: #000000"> name);
    ParsingSymbol</span>*                    SearchCommonBaseClass(ParsingSymbol*<span style="color: #000000"> classType);
};</span></pre></div>
<p>因为文法规则本身的东西也不多，所以这里的symbol只能是上面记载的9种对象。这些对象其实特别的相似，所以我们可以看出唯一的区别就是当GetType返回值不一样的时候，GetDescriptorSymbol返回的对象的意思也不一样。而这个区别记载在了enum SymbolType的注释里面。实际上这种做法在面对超级复杂的符号表（考虑一下C++编译器）的时候并不太好。那好的做法究竟是什么呢？看上面IDIASymbol的链接，那就是答案。</p>
<p>不可否认，微软设计出来的API大部分还是很有道理的，除了Win32的原生GUI部分。</p>
<p>我们还可以看到，这个ParsingSymbol类的几乎所有成员函数都是用来查询这个Symbol的内容的。这里还有两个特殊的函数，就是SearchClassSubSymbol和SearchCommonBaseClass——当且仅当symbol是ClassType的时候才起作用。为什么有了GetSubSymbolByName，还要这两个api呢？因为我们在搜索一个类的成员的时候，是要搜索他的父类的。而一个类的父类的sub symbol并不是类自己的sub symbol，所以就有了这两个api。所谓的sub symbol的意思现在也很明了了。enum类型里面的值就是enum的sub symbol，成员变量是类的sub symbol，总之只要是声明在一个符号内部的符号都是这个符号的sub symbol。这就是所有符号都有的共性。</p>
<p>当然，有了ParsingSymbol，还要有他的manager才可以完成整个符号表的操作：</p>
<div style="border-bottom: #cccccc 1px solid; border-left: #cccccc 1px solid; padding-bottom: 5px; background-color: #f5f5f5; padding-left: 5px; padding-right: 5px; border-top: #cccccc 1px solid; border-right: #cccccc 1px solid; padding-top: 5px" class="cnblogs_code"><pre><span style="color: #0000ff">class</span> ParsingSymbolManager : <span style="color: #0000ff">public</span><span style="color: #000000"> Object
{
</span><span style="color: #0000ff">public</span><span style="color: #000000">:
    ParsingSymbolManager();
    </span>~<span style="color: #000000">ParsingSymbolManager();

    ParsingSymbol</span>*<span style="color: #000000">                    GetGlobal();
    ParsingSymbol</span>*<span style="color: #000000">                    GetTokenType();
    ParsingSymbol</span>*                    GetArrayType(ParsingSymbol*<span style="color: #000000"> elementType);

    ParsingSymbol</span>*                    AddClass(<span style="color: #0000ff">const</span> WString&amp; name, ParsingSymbol* baseType, ParsingSymbol* parentType=<span style="color: #800080">0</span><span style="color: #000000">);
    ParsingSymbol</span>*                    AddField(<span style="color: #0000ff">const</span> WString&amp; name, ParsingSymbol* classType, ParsingSymbol*<span style="color: #000000"> fieldType);
    ParsingSymbol</span>*                    AddEnum(<span style="color: #0000ff">const</span> WString&amp; name, ParsingSymbol* parentType=<span style="color: #800080">0</span><span style="color: #000000">);
    ParsingSymbol</span>*                    AddEnumItem(<span style="color: #0000ff">const</span> WString&amp; name, ParsingSymbol*<span style="color: #000000"> enumType);
    ParsingSymbol</span>*                    AddTokenDefinition(<span style="color: #0000ff">const</span> WString&amp;<span style="color: #000000"> name);
    ParsingSymbol</span>*                    AddRuleDefinition(<span style="color: #0000ff">const</span> WString&amp; name, ParsingSymbol*<span style="color: #000000"> ruleType);

    ParsingSymbol</span>*                    CacheGetType(definitions::ParsingDefinitionType* type, ParsingSymbol*<span style="color: #000000"> scope);
    </span><span style="color: #0000ff">bool</span>                            CacheSetType(definitions::ParsingDefinitionType* type, ParsingSymbol* scope, ParsingSymbol*<span style="color: #000000"> symbol);
    ParsingSymbol</span>*                    CacheGetSymbol(definitions::ParsingDefinitionGrammar*<span style="color: #000000"> grammar);
    </span><span style="color: #0000ff">bool</span>                            CacheSetSymbol(definitions::ParsingDefinitionGrammar* grammar, ParsingSymbol*<span style="color: #000000"> symbol);
    ParsingSymbol</span>*                    CacheGetType(definitions::ParsingDefinitionGrammar*<span style="color: #000000"> grammar);
    </span><span style="color: #0000ff">bool</span>                            CacheSetType(definitions::ParsingDefinitionGrammar* grammar, ParsingSymbol*<span style="color: #000000"> type);
};</span></pre></div>
<p>这个ParsingSymbolManager有着符号表管理器的以下两个典型作用</p>
<p>1、创建符号。<br>2、讲符号与语法树的对象绑定起来。譬如说我们在一个context下面推导了一个expression的类型，那下次对于同样的context同样的expression就不需要再推导一次了（语义分析有很多个pass，对同一个expression求类型的操作经常会重复很多次），把它cache下来就可以了。<br>3、搜索符号。具体到这个符号表，这个功能被做进了ParsingSymbol里面。<br>4、保存根节点。GetGlobal函数就是干这个作用的。所有的根符号都属于global符号的sub symbol。</p>
<p>因此我们可以怎么使用他呢？首先看上面的Add函数。这些函数不仅会帮你在一个符号表里面添加一个sub symbol，还会替你做一些检查，譬如说阻止你添加相同名字的sub symbol之类的。语法树很复杂的时候，很多时候我们有很多不同的方法来给一个符号添加子符号，譬如说C#的成员变量和成员函数。成员变量不能同名，成员函数可以，但是成员函数和成员变量却不能同名。这个时候我们就需要把这些添加操作封装起来，这样才可以在处理语法树（声明一个函数的方法可以有很多，所以添加函数符号的地方也可以有很多）的时候不需要重复写验证逻辑。</p>
<p>其次就是Cache函数。其实Cache函数这么写，不是用来直接调用的。举个例子，在分析一个文法的时候，我们需要把一个&#8220;类型&#8221;语法树转成一个&#8220;类型&#8221;符号（譬如说要决定一个文法要create什么类型的语法树节点的时候）。因此就有了下面的函数：</p>
<div style="border-bottom: #cccccc 1px solid; border-left: #cccccc 1px solid; padding-bottom: 5px; background-color: #f5f5f5; padding-left: 5px; padding-right: 5px; border-top: #cccccc 1px solid; border-right: #cccccc 1px solid; padding-top: 5px" class="cnblogs_code"><pre>ParsingSymbol* FindType(Ptr&lt;definitions::ParsingDefinitionType&gt; type, ParsingSymbolManager* manager, ParsingSymbol* scope, collections::List&lt;Ptr&lt;ParsingError&gt;&gt;&amp;<span style="color: #000000"> errors)
{
    ParsingSymbol</span>* result=manager-&gt;<span style="color: #000000">CacheGetType(type.Obj(), scope);
    </span><span style="color: #0000ff">if</span>(!<span style="color: #000000">result)
    {
        FindTypeVisitor visitor(manager, (scope</span>?scope:manager-&gt;<span style="color: #000000">GetGlobal()), errors);
        type</span>-&gt;Accept(&amp;<span style="color: #000000">visitor);
        result</span>=<span style="color: #000000">visitor.result;
        manager</span>-&gt;<span style="color: #000000">CacheSetType(type.Obj(), scope, result);
    }
    </span><span style="color: #0000ff">return</span><span style="color: #000000"> result;
}</span></pre></div>

<p>很明显，这个函数做的事情就是，查询一个ParsingDefinitionType节点有没有被查询过，如果有直接用cache，没有的话再从头计算他然后cache起来。因此这些Cache函数就是给类似FindType的函数用的，而语义分析的代码则直接使用FindType，而不是Cache函数，来获取一个类型的符号。聪明的朋友们可以看出来，这种写法蕴含着一个条件，就是语法树创建完就不会改了（废话，当然不会改！）。</p>
<p>这一篇的内容就讲到这里了。现在的进度是正在写文法生成状态机的算法。下一篇文章应该讲的就是状态机究竟是怎么运作的了。文法所需要的状态机叫做下推状态机（push down automaton），跟regex用的NFA和DFA不太一样，理解起来略有难度。所以我想需要用单独的一篇文章来通俗的讲一讲。</p> <img src ="http://www.cppblog.com/vczh/aggbug/195779.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/vczh/" target="_blank">陈梓瀚(vczh)</a> 2012-11-29 00:50 <a href="http://www.cppblog.com/vczh/archive/2012/11/29/195779.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>可配置语法分析器开发纪事（一）&amp;mdash;&amp;mdash;构造语法树</title><link>http://www.cppblog.com/vczh/archive/2012/11/21/195503.html</link><dc:creator>陈梓瀚(vczh)</dc:creator><author>陈梓瀚(vczh)</author><pubDate>Wed, 21 Nov 2012 14:42:00 GMT</pubDate><guid>http://www.cppblog.com/vczh/archive/2012/11/21/195503.html</guid><wfw:comment>http://www.cppblog.com/vczh/comments/195503.html</wfw:comment><comments>http://www.cppblog.com/vczh/archive/2012/11/21/195503.html#Feedback</comments><slash:comments>5</slash:comments><wfw:commentRss>http://www.cppblog.com/vczh/comments/commentRss/195503.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/vczh/services/trackbacks/195503.html</trackback:ping><description><![CDATA[<p>就像<a href="http://www.cppblog.com/vczh/archive/2012/10/30/194052.html" target="_blank">之前的博客文章</a>所说的，（主要还是）因为<a href="http://gac.codeplex.com" target="_blank">GacUI</a>的原因，我决定开发一个更好的可配置轻量级语法分析器来代替之前的落后的版本。在说这个文章之前，我还是想在此向大家推荐一本《编程语言实现模式》，这的确是一本好书，让我相见恨晚。</p> <p>其实说到开发语法分析器，我从2007年就已经开始在思考类似的问题了。当时C++还处于用的不太熟练的时候，难免会做出一些傻逼的事情，不过总的来说当年的idea还是能用的。从那时候开始，我为了锻炼自己，一直在实现各种不同的语言。所以给自己开发一个可配置语法分析器也是在所难免的事情了。于是就有：<br>第一版：<a title="http://hi.baidu.com/geniusvczh/archive/tag/syngram%E6%97%A5%E5%BF%97" href="http://hi.baidu.com/geniusvczh/archive/tag/syngram%E6%97%A5%E5%BF%97">http://hi.baidu.com/geniusvczh/archive/tag/syngram%E6%97%A5%E5%BF%97</a><br>第二版：<a title="http://www.cppblog.com/vczh/archive/2009/04/06/79122.html" href="http://www.cppblog.com/vczh/archive/2009/04/06/79122.html">http://www.cppblog.com/vczh/archive/2009/04/06/79122.html</a><br>第三版：<a title="http://www.cppblog.com/vczh/archive/2009/12/13/103101.html" href="http://www.cppblog.com/vczh/archive/2009/12/13/103101.html">http://www.cppblog.com/vczh/archive/2009/12/13/103101.html</a><br>还有第三版的教程：<a title="http://www.cppblog.com/vczh/archive/2010/04/28/113836.html" href="http://www.cppblog.com/vczh/archive/2010/04/28/113836.html">http://www.cppblog.com/vczh/archive/2010/04/28/113836.html</a></p> <p>上面的所有分析器都致力于在C++里面可以通过直接描述文法和一些语义行为来让系统可以迅速构造出一个针对特定目的的用起来方便的语法分析器，而“第三版”就是到目前为止还在用的一个版本。至于为什么我要做一个新的——也就是第四版——<a href="http://www.cppblog.com/vczh/archive/2012/10/30/194052.html" target="_blank">之前的文章</a>已经说了。</p> <p>而今天，第四版的开发已经开始了有好几天。如果大家关心进度的话，可以去<a href="http://gac.codeplex.com" target="_blank">GacUI的Codeplex页面</a>下载代码，然后阅读Common\Source\Parsing下面的源文件。对应的单元测试可以在Common\UnitTest\UnitTest\TestParsing.cpp里找到。</p> <p>于是今天就说说关于构造语法树的事情。</p> <p>用C++写过parser的人都知道，构造语法树以及语义分析用的符号表是一件极其繁琐，而且一不小心就容易写出翔的事情。但是根据我写过无穷多棵语法树以及构造过无穷多个符号表以及附带的副作用，翔，啊不，经验，做这个事情还是有一些方法的。</p> <p>在介绍这个方法之前，首先要说一句，人肉做完下面的所有事情是肯定要疯掉的，所以这一次的可配置语法分析器我已经决定了一定要TMD写出一个生成语法树的C++代码的工具。</p> <p>一颗语法树，其实就是一大堆互相继承的类。一切成熟的语法树结构所具有的共同特征，不是他的成员怎么安排，而是他一定会附带一个<a href="http://en.wikipedia.org/wiki/Visitor_Pattern" target="_blank">visitor模式</a>的机制。至于什么是visitor模式，大家请自行参考设计模式，我就不多说废话了。这一次的可配置语法分析器是带有一个描述性语法的。也就是说，跟Antlr或者Yacc一样，首先在一个文本文件里面准备好语法树结构和文法规则，然后我的工具会帮你生成一个内存中的语法分析器，以及用C++描述的语法树的声明和实现文件。这个描述性语法就类似下面的这个大家熟悉到不能再熟悉的带函数的四则运算表达式结构：</p> <p>class Expression<br>{<br>}</p> <p>class NumberExpression : Expression<br>{<br>&nbsp;&nbsp;&nbsp; token value;<br>}</p> <p>class BinaryExpression : Expression<br>{<br>&nbsp;&nbsp;&nbsp; enum BinaryOperator<br>&nbsp;&nbsp;&nbsp; {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Add,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Sub,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Mul,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Div,<br>&nbsp;&nbsp;&nbsp; }</p> <p>&nbsp;&nbsp;&nbsp; Expression firstOperand;<br>&nbsp;&nbsp;&nbsp; Expression secondOperand;<br>&nbsp;&nbsp;&nbsp; BinaryOperator binaryOperator;<br>}</p> <p>class FunctionExpression : Expression<br>{<br>&nbsp;&nbsp;&nbsp; token functionName;<br>&nbsp;&nbsp;&nbsp; Expression[] arguments;<br>}</p> <p>token NAME = "[a-zA-Z_]/w*";<br>token NUMBER = "/d+(./d+)";<br>token ADD = "/+";<br>token SUB = "-";<br>token MUL = "/*";<br>token DIV = "//";<br>token LEFT = "/(";<br>token RIGHT = "/)";<br>token COMMA = ",";</p> <p>rule NumberExpression Number<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; = NUMBER : value;</p> <p>rule FunctionExpression Call<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; = NAME : functionName "(" [ Exp : arguments { "," Exp : arguments } ] ")";</p> <p>rule Expression Factor<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; = !Number | !Call;</p> <p>rule Expression Term<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; = !Factor;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; = Term : firstOperand "*" Factory : secondOperand as BinaryExpression with { binaryOperator = "Mul" };<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; = Term : firstOperand "/" Factory : secondOperand as BinaryExpression with { binaryOperator = "Div" };</p> <p>rule Expression Exp<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; = !Term;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; = Exp : firstOperand "+" Term : secondOperand as BinaryExpression with { binaryOperator = "Add" };<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; = Exp : firstOperand "-" Term : secondOperand as BinaryExpression with { binaryOperator = "Sub" };</p> <p>上面的语法树声明借用的C#语法，描述起来特别简单。但是要在C++里面达到可以使用的程度，肯定要有一个自带的visitor模式。所以出来之后的代码大概就类似于下面这个样子：</p> <p>class Expression;<br>class NumberExpression;<br>class BinaryExpression;<br>class FunctionExpression;</p> <p>class Expression : public ParsingTreeCustomBase<br>{<br>public:<br>&nbsp;&nbsp;&nbsp; class IVisitor : public Interface<br>&nbsp;&nbsp;&nbsp; {<br>&nbsp;&nbsp;&nbsp; public:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; virtual void Visit(NumberExpression* node)=0;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; virtual void Visit(BinaryExpression* node)=0;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; virtual void Visit(FunctionExpression* node)=0;<br>&nbsp;&nbsp;&nbsp; };</p> <p>&nbsp;&nbsp;&nbsp; virtual void Accept(IVisitor* visitor)=0;<br>};</p> <p>class NumberExpression : public Expression<br>{<br>public:<br>&nbsp;&nbsp;&nbsp; TokenValue value;</p> <p>&nbsp;&nbsp;&nbsp; void Accept(IVisitor* visitor){visitor-&gt;Visit(this);}<br>};</p> <p>class BinaryExpression : public Expression<br>{<br>public:<br>&nbsp;&nbsp;&nbsp; enum BinaryOperator<br>&nbsp;&nbsp;&nbsp; {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Add, Sub, Mul, Div,<br>&nbsp;&nbsp;&nbsp; };<br>&nbsp;&nbsp;&nbsp; Ptr&lt;Expression&gt; firstOperator;<br>&nbsp;&nbsp;&nbsp; Ptr&lt;Expression&gt; secondOperator;<br>&nbsp;&nbsp;&nbsp; BinaryOperator binaryOperator;</p> <p>&nbsp;&nbsp;&nbsp; void Accept(IVisitor* visitor){visitor-&gt;Visit(this);}<br>};</p> <p>class FunctionExpression : public Expression<br>{<br>public:<br>&nbsp;&nbsp;&nbsp; TokenValue functionName;<br>&nbsp;&nbsp;&nbsp; List&lt;Ptr&lt;Expression&gt;&gt; arguments;</p> <p>&nbsp;&nbsp;&nbsp; void Accept(IVisitor* visitor){visitor-&gt;Visit(this);}<br>};</p> <p>为什么要这样做呢？学习过面向对象开发方法的都知道，把一个明显是继承结构的东西写成一堆union/struct和一个enum来判断他们，是不对的。第一个不好的地方就是，如果其中的成员需要构造函数和析构函数，那union就用不了了，struct就一定会造成大量的内存浪费。因为一颗语法树是可以很大的。其次，当语法树的结构（主要是添加删除了新的语法树类型）之后，我们根本不可能保证我们所有的swtich(node-&gt;enumType)语句都接受到了正确的更新。</p> <p>那要如何解决这两个问题呢？答案之一就是使用visitor模式。尽管刚开始写起来的时候可能会有点别扭，但是我们只要把原本是swtich结构的代码做一下<a href="http://en.wikipedia.org/wiki/Continuation_Passing_Style" target="_blank">Continuation Passing Style变换</a>，就可以写出使用visitor的版本了。在这里我做一个小小的演示，如何把一个“把上面的语法树还原成四则运算式子的函数”给用Expression::IVisitor的框架下实现出来：</p> <p>class FunctionExpression : public Expression<br>{<br>public:<br>&nbsp;&nbsp;&nbsp; TokenValue functionName;<br>&nbsp;&nbsp;&nbsp; List&lt;Ptr&lt;Expression&gt;&gt; arguments;</p> <p>&nbsp;&nbsp;&nbsp; void Accept(IVisitor* visitor){visitor-&gt;Visit(this);}<br>};</p> <p>class ExpressionPrinter : public Expression::IVisitor<br>{<br>public:<br>&nbsp;&nbsp;&nbsp; WString result;</p> <p>&nbsp;&nbsp;&nbsp; void Visit(NumberExpression* node)<br>&nbsp;&nbsp;&nbsp; {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; result+=node-&gt;value.stringValue;<br>&nbsp;&nbsp;&nbsp; }</p> <p>&nbsp;&nbsp;&nbsp; void Visit(BinaryExpression* node)<br>&nbsp;&nbsp;&nbsp; {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; result+=L"(";<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; node-&gt;firstOperand-&gt;Accept(this);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; switch(binaryOperator)<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; case Add: result+=L" + "; break;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; case Sub: result+=L" - "; break;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; case Mul: result+=L" * "; break;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; case Div: result+=L" / "; break;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; node-&gt;secondOperand-&gt;Accept(this);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; result+=L")";<br>&nbsp;&nbsp;&nbsp; }</p> <p>&nbsp;&nbsp;&nbsp; void Visit(FunctionExpression* node)<br>&nbsp;&nbsp;&nbsp; {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; result+=node-&gt;functionName.stringValue+L"(";<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; for(int i=0;i&lt;arguments.Count();i++)<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if(i&gt;0) result+=L", ";<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; arguments[i]-&gt;Accept(this);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; result+=L")";<br>&nbsp;&nbsp;&nbsp; }<br>};</p> <p>WString PrintExpression(Ptr&lt;Expression&gt; expression)<br>{<br>&nbsp;&nbsp;&nbsp; ExpressionPrinter printer;<br>&nbsp;&nbsp;&nbsp; expression-&gt;Accept(&amp;printer);<br>&nbsp;&nbsp;&nbsp; return printer.result;<br>}</p> <p>其实大家可以看到，使用了visitor模式，代码量其实也没有多大变化，本来是递归的地方还是递归，本来该计算什么还计算什么，唯一不同的就是原本这个“函数”的参数和返回值都跑到了一个visitor类的成员变量里面去了。当然，为了便于使用，一般来说我们会把原本的函数的原型写出来，并且在里面调用visitor模式，就像上面的PrintExpression函数一样。如果我们高兴的话，完全可以在ExpressionPrinter这个visitor类里面使用PrintExpression，无非就是在里面构造新的ExpressionPrinter然后获取结构罢了。一般来说，visitor类都是非常的轻量级的，在现今的CPU性能下面，构造多几个完全不会带来多大影响。</p> <p>可配置语法分析器既然拥有一个描述性语法，那么我肯定也针对这个描述性语法写了一颗语法树的。这颗语法树的代码在Common\Source\Parsing\ParsingDefinition.h里面，而ParsingLogging.cpp则是跟上面说的一样，用visitor的方法写了一个庞大的把语法树转回描述性语法的函数。这个函数非常有用，不仅可以用来打log，还可以用来保存程序生成的一个语法规则（反正可以parse回来，所以保存成文本是一件特别方便的事情），甚至是生成错误消息的片段等等。</p> <p>今天就先讲到这里了。现在的可配置语法分析器的开发进度是正在写语义分析的部分。等到语义分析写完了，我会再写一篇纪事来说明开发语义分析程序和构造符号表的一般做法。</p><img src ="http://www.cppblog.com/vczh/aggbug/195503.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/vczh/" target="_blank">陈梓瀚(vczh)</a> 2012-11-21 22:42 <a href="http://www.cppblog.com/vczh/archive/2012/11/21/195503.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>C++使用Uniscribe进行文字自动换行的计算和渲染</title><link>http://www.cppblog.com/vczh/archive/2012/11/06/194817.html</link><dc:creator>陈梓瀚(vczh)</dc:creator><author>陈梓瀚(vczh)</author><pubDate>Tue, 06 Nov 2012 14:34:00 GMT</pubDate><guid>http://www.cppblog.com/vczh/archive/2012/11/06/194817.html</guid><wfw:comment>http://www.cppblog.com/vczh/comments/194817.html</wfw:comment><comments>http://www.cppblog.com/vczh/archive/2012/11/06/194817.html#Feedback</comments><slash:comments>4</slash:comments><wfw:commentRss>http://www.cppblog.com/vczh/comments/commentRss/194817.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/vczh/services/trackbacks/194817.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: Uniscribe是Windows 2000以来就存在于WinAPI中的一个库。这个库能够提供给我们关于字符串渲染的很多信息，譬如说哪里可以换行啦，渲染的时候字符的顺序应该是什么样子啦，还有每一个字符的大小什么的。关于Uniscribe的资料可以在http://msdn.microsoft.com/en-us/library/windows/desktop/dd374091(v=vs.85).as...&nbsp;&nbsp;<a href='http://www.cppblog.com/vczh/archive/2012/11/06/194817.html'>阅读全文</a><img src ="http://www.cppblog.com/vczh/aggbug/194817.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/vczh/" target="_blank">陈梓瀚(vczh)</a> 2012-11-06 22:34 <a href="http://www.cppblog.com/vczh/archive/2012/11/06/194817.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>又到了一年一度重构通用可配置语法分析器的时候了</title><link>http://www.cppblog.com/vczh/archive/2012/10/30/194052.html</link><dc:creator>陈梓瀚(vczh)</dc:creator><author>陈梓瀚(vczh)</author><pubDate>Mon, 29 Oct 2012 16:23:00 GMT</pubDate><guid>http://www.cppblog.com/vczh/archive/2012/10/30/194052.html</guid><wfw:comment>http://www.cppblog.com/vczh/comments/194052.html</wfw:comment><comments>http://www.cppblog.com/vczh/archive/2012/10/30/194052.html#Feedback</comments><slash:comments>6</slash:comments><wfw:commentRss>http://www.cppblog.com/vczh/comments/commentRss/194052.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/vczh/services/trackbacks/194052.html</trackback:ping><description><![CDATA[<p>因为<a href="http://gac.codeplex.com" target="_blank">GacUI</a>需要实现一个文本描述的窗口描述格式，再加上C++经常需要处理xml和json等常用数据结构，还有自己还要时不时开发一些语言来玩一玩之类的理由，每一次遇到自己的技术革新的时候，总是免不了要对可配置语法分析器做出修改。上一个版本的可配置语法分析器可以见之前的博客文章《<strong><a href="http://www.cppblog.com/vczh/archive/2010/04/28/113836.html" target="_blank">Vczh Library++ 语法分析器开发指南</a></strong>》。</p> <p>为什么要重写vlpp的这一部分呢？因为经过多次可配置语法分析器的开发，我感觉到了C++直接用来表达文法有很多弱点：</p> <p>1、C++自身的类型系统导致表达出来的文法会有很多噪音。当然这并不是C++的错，而是通用的语言做这种事情总是会有点噪音的。无论是《<a href="http://blogs.msdn.com/b/lukeh/archive/2007/08/19/monadic-parser-combinators-using-c-3-0.aspx" target="_blank">Monadic Parser Combinators using C# 3.0</a>》也好，我大微软研究院的基于Haskell的<a href="http://www.haskell.org/haskellwiki/Parsec" target="_blank">Parsec</a>也好，还是boost的<a href="http://boost-spirit.com/home/" target="_blank">spirit</a>也好，甚至是F#的<a href="http://fsharpcentral.com/tag/fsyacc/" target="_blank">Fsyacc</a>也好，都在展示了parser combinator这个强大的概念的同时，也暴露出了parser combinator的弱点：在语法分析结果和语言的数据结构的结合方面特别的麻烦。这里的麻烦不仅在于会给文法造成很多噪音，而且复杂的parser还会使得你的结构特别的臃肿（参考Antlr的某些复杂的应用，这里就不一一列举了）。</p> <p>2、难以维护。如果直接用C++描述一个强类型文法的话，势必是要借助parser combinator这个概念的。概念本身是很厉害的，而且实现的好的话开发效率会特别的高。但是对于C++这种非函数式语言来说，parser combinator这种特别函数式的描述放在C++里面就会多出很多麻烦，譬如闭包的语法不够漂亮啦、没有垃圾收集器的问题导致rule与rule的循环引用问题还要自行处理啦（在很早以前的一篇博客论证过了，只要是带完整闭包功能的语言，都一定不能是用引用计数来处理内存，而必须要一个垃圾收集器的）。尽管我一直以来都还是没做出过这方面的bug，但是由于（主要是用来处理何时应该delete对象部分的）逻辑复杂，导致数据结构必须为delete对象的部分让步，代码维护起来也相当的蛋疼。</p> <p>3、有些优化无法做。举个简单的例子，parser combinator就根本没办法处理左递归。没有左递归，写起某些文法来也是特别的蛋疼。还有合并共同前缀等等的优化也不能做，这导致我们必须为了性能牺牲本来就已经充满了噪音的文法的表达，转而人工作文法的共同前缀合并，文法看起来就更乱了。</p> <p>当然上面三个理由看起来好像不太直观，那我就举一个典型的例子。大家应该还记得我以前写过一个叫做<a href="http://www.cppblog.com/vczh/archive/2011/03/20/142261.html" target="_blank">NativeX的语言</a>，还给它做了一个<a href="http://www.cppblog.com/vczh/archive/2011/02/25/140618.html" target="_blank">带智能提示的编辑器</a>（还有<a href="http://www.cppblog.com/vczh/archive/2010/11/07/132876.html" target="_blank">这里</a>和<a href="http://www.cppblog.com/vczh/archive/2010/12/05/135505.html" target="_blank">这里</a>）。NativeX是一个C++实现的C+template+concept mapping的语言，语法分析器当然是用<a href="http://www.cppblog.com/vczh/archive/2010/04/28/113836.html" target="_blank">上一个版本的可配置语法分析器</a>来做的。文法规则很复杂，但是被C++这么以表达，就更加复杂了（<a href="http://vlpp.codeplex.com/SourceControl/changeset/view/95426#1863083" target="_blank">.\Library\Scripting\Languages\NativeX\NativeXParser.cpp</a>），已经到了不仔细看就无法维护的地步了。</p> <p>综上所述，做一个新的可配置语法分析器出来理由充分，势在必得。但是形式上是什么样子的呢？上面说过我以前给NativeX写过一个带智能提示的编辑器。这个编辑器用的是WinForm，那当然也是用C#写的，因此那个对性能要求高到离谱的NativeX编辑器用的语法分析器当然也是用C#写的。流程大概如下：<br>1、用C#按照要求声明语法树结构<br>2、使用我的库用C#写一个文法<br>3、我的库会执行这个文法，生成一大段C#写的等价的递归下降语法分析器的代码<br>当时我把这个过程记录在了<a href="http://www.cppblog.com/vczh/archive/2010/10/17/130202.html" target="_blank">这篇博客文章</a>里面。</p> <p>因此现在就有一个计划，这个新的可配置语法分析器当然还是要完全用C++，但是这就跟正则表达式一样：<br>1、首先语法树结构和文法都声明在一个字符串里面<br>2、可配置语法分析器可以在内存中动态执行这段文法，并按照给定的语法树结构给出一个在内存中的动态的数据结构<br>3、可配置语法分析器当然还要附带一个命令行工具，用来读文法生成C++代码，包括自带Visitor模式的语法树结构，和C++写的递归下降语法分析器</p> <p>所以现在就有一个草稿，就是那个“声明在字符串里面”的语法树结构和文法的说明。这是一个很有意思的过程。</p> <p>首先，这个可配置语法分析器需要在内存中表达语法树结构，和一个可以执行然后产生动态数据结构的文法。因此我们在使用它的时候，可以选择直接在内存中堆出语法树结构和文法的描述，而不是非得用那个字符串的表达形式。当然，字符串的表达形式肯定是十分紧凑的，但这不是必须的，只是推荐的。</p> <p>其次，parse这个“语法树结构和文法都声明”当然也需要一个语法分析器是不是？所以我们可以用上面的方法，通过直接在内存中堆出文法来用自己构造出一个自己的语法分析器。</p> <p>再者，有了一个内存中的语法分析器之后，我就可以将上面第三步的命令行工具做出来，然后用它来描述自己的文法，产生出一段C++写的递归下降语法分析器，用来分析“语法树结构和文法都声明”，然后就有了一对C++代码文件。</p> <p>最后，把产生出来的这对C++代码文件加进去，我们就有了一个C++直接写，而不是在内存中动态构造出来的“语法树结构和文法都声明”的分析器了。然后这个分析器就可以替换掉命令行工具里面那个原先动态构造出来的语法分析器。当然那个动态构造出来的语法分析器这个时候已经没用了，因为有了生成的C++语法分析器，我们就可以直接使用“语法树结构和文法都声明”来描述自己，得到这么一个描述的字符串，然后随时都可以用这个字符串来动态生成语法分析器了。</p> <p>总而言之就是<br>1、实现可配置语法分析器，可以直接用数据结构做出一个产生动态数据结构的parser combinator，记为PC。<br>2、用PC做一个“语法树结构和文法都声明”的语法分析器。这个“语法树结构和文法都声明”记为PC Grammar。<br>3、PC Grammar当然可以用来表达PC Grammar自己，这样我们就得到了一个专门用来说明什么是合法的“语法树结构和文法都声明”的描述的字符串的这么个文法，记为PC Grammar Syntax Definition。<br>4、通过这份满足PC Grammar要求的PC Grammar Syntax Definition，我们就可以用PC来解释PC Grammar Syntax Definition，动态产生一个解释PC Grammar的语法分析器<br>5、有了PC Grammar的语法分析器PC Grammar Parser (in memory version)，之后我们就可以把“文法-&gt;C++代码”的代码生成器做出来，称之为PC Grammar C++ Codegen。<br>6、有了PC Grammar C++ Codegen，我们就可以用他读入PC Grammar Syntax Definition，产生一个直接用C++写的PC Grammar的语法分析器，叫做PC Grammar Parser (C++ version)。</p> <p>到此为止，我们获得的东西有<br>1、PC （Parser Combinator）<br>2、PC Grammar<br>3、PC Grammar Syntax Definition<br>4、PC Grammar Parser (in memory version)<br>5、PC Grammar Parser (C++ version)<br>6、PC Grammar C++ Codegen</p> <p>其中，1、3、4、5、6都是可以执行的，2是一个“标准”。到了这一步，我们就可以用PC Grammar Parser (C++ version)来替换掉PC Grammar C++ Codegen里面的PC Grammar Parser (in memory version)了。这就跟gcc要编译一个小编译器来编译自己得到一个完整的gcc一样。这个过程还可以用来测试PC Grammar C++ Codegen是否写的足够好。</p> <p>那么“语法树结构和文法都声明”到地是什么样子的呢？我这里给出一个简单的文法，就是用来parse诸如int、vl::collections::List&lt;WString&gt;、int*、int&amp;、int[]、void(int, WString, double*)的这些类型的字符串了。下面首先展示如何用这个描述来解决上面的“类型”的语法书声明：</p> <p><font style="background-color: #666666"></font><font style="" color="#00a2e8">class Type{}</font></p> <p><font style="" color="#00a2e8">class DecoratedType : Type<br>{<br>&nbsp;&nbsp;&nbsp; enum Decoration<br>&nbsp;&nbsp;&nbsp; {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Pointer,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Reference,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Array,<br>&nbsp;&nbsp;&nbsp; }<br>&nbsp;&nbsp;&nbsp; Decoration&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; decoration;<br>&nbsp;&nbsp;&nbsp; Type&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; elementType;<br>}</font></p> <p><font style="" color="#00a2e8">class PrimitiveType : Type<br>{<br>&nbsp;&nbsp;&nbsp; token&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; name;<br>}</font></p> <p><font style="" color="#00a2e8">class GenericType : Type<br>{<br>&nbsp;&nbsp;&nbsp; Type&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; type;<br>&nbsp;&nbsp;&nbsp; Type[]&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; arguments;<br>}</font></p> <p><font style="" color="#00a2e8">class SubType : Type<br>{<br>&nbsp;&nbsp;&nbsp; Type&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; type;<br>&nbsp;&nbsp;&nbsp; token&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; name;<br>}</font></p> <p><font style="" color="#00a2e8">class FunctionType : Type<br>{<br>&nbsp;&nbsp;&nbsp; Type&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; returnType;<br>&nbsp;&nbsp;&nbsp; Type[]&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; arguments;<br>}</font></p> <p>然后就是声明语法分析器所需要的词法元素，用正则表达式来描述：</p> <p><font color="#00a2e8">token SYMBOL&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; = &lt;|&gt;|\[|\]|\(|\)|,|::|\*|&amp;<br>token NAME&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; = [a-zA-Z_]\w*</font></p> <p>这里只需要两种token就可以了。接下来就是两种等价的对于这个文法的描述，用来展示全部的功能。</p> <p>========================================================</p> <p><font color="#00a2e8">Type SubableType&nbsp;&nbsp;&nbsp; = NAME[name] as PrimitiveType<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; = SubableType[type] '&lt;' Type[arguments] { ',' Type[arguments] } '&gt;' as GenericType<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; = SubableType[type] '::' NAME[name] as SubType</font></p> <p><font color="#00a2e8">Type Type&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; = @SubableType<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; = Type[elementType](<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ( '*' {decoration = DecoratedType::Pointer}<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; | '&amp;' {decoration = DecoratedType::Reference}<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; | '[' ']' {decoration = ecoratedType::Array}<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; )<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ) as DecoratedType<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; = Type[returnType] '(' Type[arguments] { ',' Type[arguments] } ')' as FunctionType</font></p> <p>========================================================</p> <p><font color="#00a2e8">rule PrimitiveType&nbsp;&nbsp;&nbsp; PrimitiveType&nbsp;&nbsp;&nbsp; = NAME[name]<br>rule GenericType&nbsp;&nbsp;&nbsp; GenericType&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; = SubableType[type] '&lt;' Type[arguments] { ',' Type[arguments] } '&gt;'<br>rule SubType&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; SubType&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; = SubableType[type] :: NAME[name]<br>rule Type&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; SubableType&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; = @PrimitiveType | @GenericType | @SubType</font></p> <p><font color="#00a2e8">rule DecoratedType&nbsp;&nbsp;&nbsp; DecoratedType&nbsp;&nbsp;&nbsp; = Type[elementType] '*' {decoration = DecoratedType::Pointer}<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; = Type[elementType] '&amp;' {decoration = DecoratedType::Reference}<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; = Type[elementType] '[' ']' {decoration = DecoratedType::Array}<br>rule FunctionType&nbsp;&nbsp;&nbsp; FunctionType&nbsp;&nbsp;&nbsp; = Type[returnType] '(' Type[arguments] { ',' Type[arguments] } ')'<br>rule Type&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Type&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; = @SubableType | @DecoratedType | @FunctionType</font></p> <p>========================================================</p> <p>如果整套系统开发出来的话，那么我就会提供一个叫做ParserGen.exe的命令行工具，把上面的字符串转换为一个<strong>可读的、等价与这段文法的、使用递归下降方法来描述的、C++写出来的</strong>语法分析器和语法树声明了。</p><img src ="http://www.cppblog.com/vczh/aggbug/194052.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/vczh/" target="_blank">陈梓瀚(vczh)</a> 2012-10-30 00:23 <a href="http://www.cppblog.com/vczh/archive/2012/10/30/194052.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>使用C++和Windows API操作基于http协议的xml service</title><link>http://www.cppblog.com/vczh/archive/2012/10/27/193945.html</link><dc:creator>陈梓瀚(vczh)</dc:creator><author>陈梓瀚(vczh)</author><pubDate>Sat, 27 Oct 2012 07:19:00 GMT</pubDate><guid>http://www.cppblog.com/vczh/archive/2012/10/27/193945.html</guid><wfw:comment>http://www.cppblog.com/vczh/comments/193945.html</wfw:comment><comments>http://www.cppblog.com/vczh/archive/2012/10/27/193945.html#Feedback</comments><slash:comments>1</slash:comments><wfw:commentRss>http://www.cppblog.com/vczh/comments/commentRss/193945.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/vczh/services/trackbacks/193945.html</trackback:ping><description><![CDATA[<p>在S1死程群@kula的鼓励下，我开始使用kula提供的api来操作那个傻逼的&#8220;鸟窝&#8221;网站（<a style="color: ; text-decoration: underline" href="https://www.niaowo.me" target="_blank">https://www.niaowo.me</a>）。不过由于我自己在业余时间写的程序都喜欢用C++和Windows API，因此我琢磨了几天，真的让我用C++给写了出来。</p>
<p>我写了一个HttpUtility库来实现C++操作http/https服务的功能，这份代码可以在这里获得：</p>
<p>HttpUtility.h：<a style="color: ; text-decoration: underline" href="http://gac.codeplex.com/SourceControl/changeset/view/95641#2295555" target="_blank">http://gac.codeplex.com/SourceControl/changeset/view/95641#2295555</a><br />HttpUtility.cpp：<a title="http://gac.codeplex.com/SourceControl/changeset/view/95641#2295554" style="color: ; text-decoration: underline" href="http://gac.codeplex.com/SourceControl/changeset/view/95641#2295554" target="_blank">http://gac.codeplex.com/SourceControl/changeset/view/95641#2295554</a></p>
<p>使用的时候很简单，只需要HttpRequest里面填满了参数，然后就可以用HttpQuery参数获得一个HttpResponse类型，这个类型里面写满了http服务器的返回值、返回内容和cookie等的数据。譬如说用来post来登陆鸟窝，然后拿到cookie之后查询首页的所有帖子，大概就可以这么写：</p>
<p>WString NestleGetSession(const WString&amp; username, const WString&amp; password, const WString&amp; apiKey, const WString&amp; apiSecret)<br />{<br />&nbsp;&nbsp;&nbsp; WString body=L"api_key="+apiKey+L"&amp;api_secret="+apiSecret+L"&amp;username="+username+L"&amp;password="+password;</p>
<p>&nbsp;&nbsp;&nbsp; HttpRequest request;<br />&nbsp;&nbsp;&nbsp; HttpResponse response;</p>
<p>&nbsp;&nbsp;&nbsp; request.SetHost(L"https://www.niaowo.me/account/token/");<br />&nbsp;&nbsp;&nbsp; request.method=L"POST";<br />&nbsp;&nbsp;&nbsp; request.contentType=L"application/x-www-form-urlencoded";<br />&nbsp;&nbsp;&nbsp; request.SetBodyUtf8(body);<br />&nbsp;&nbsp;&nbsp; HttpQuery(request, response);</p>
<p>&nbsp;&nbsp;&nbsp; if(response.statusCode==200)<br />&nbsp;&nbsp;&nbsp; {<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return response.cookie;<br />&nbsp;&nbsp;&nbsp; }<br />&nbsp;&nbsp;&nbsp; else<br />&nbsp;&nbsp;&nbsp; {<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return L"";<br />&nbsp;&nbsp;&nbsp; }<br />}</p>
<p>WString NestleGetXml(const WString&amp; path, const WString&amp; cookie)<br />{<br />&nbsp;&nbsp;&nbsp; HttpRequest request;<br />&nbsp;&nbsp;&nbsp; HttpResponse response;</p>
<p>&nbsp;&nbsp;&nbsp; request.SetHost(L"https://www.niaowo.me"+path+L".xml");<br />&nbsp;&nbsp;&nbsp; request.method=L"GET";<br />&nbsp;&nbsp;&nbsp; request.cookie=cookie;<br />&nbsp;&nbsp;&nbsp; request.acceptTypes.Add(L"application/xml");<br />&nbsp;&nbsp;&nbsp; HttpQuery(request, response);<br />&nbsp;&nbsp;&nbsp; </p>
<p>&nbsp;&nbsp;&nbsp; if(response.statusCode==200)<br />&nbsp;&nbsp;&nbsp; {<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return response.GetBodyUtf8();<br />&nbsp;&nbsp;&nbsp; }<br />&nbsp;&nbsp;&nbsp; else<br />&nbsp;&nbsp;&nbsp; {<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return L"";<br />&nbsp;&nbsp;&nbsp; }<br />}<br /></p>
<p>于是我们终于获得了一个保存在vl::WString的xml字符串了，那怎么办呢？这个时候需要出动IXMLDOMDocument2来解析我们的xml。只要装了IE的计算机上都是有IXMLDOMDocument2的，而不装IE的Windows PC是不存在的，因此我们总是可以大胆的使用。当然，用IXMLDOMDocument直接来遍历什么东西特别的慢，所以我们需要的是xpath。xpath对于xml就跟regex对于字符串一样，可以直接查询出我们要的东西。首先看一下如何操作IXMLDOMDocument2接口：</p>
<p>IXMLDOMNodeList* XmlQuery(IXMLDOMNode* pDom, const WString&amp; xpath)<br />{<br />&nbsp;&nbsp;&nbsp; IXMLDOMNodeList* nodes=0;<br />&nbsp;&nbsp;&nbsp; BSTR xmlQuery=SysAllocString(xpath.Buffer());<br />&nbsp;&nbsp;&nbsp; if(xmlQuery)<br />&nbsp;&nbsp;&nbsp; {<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; HRESULT hr=pDom-&gt;selectNodes(xmlQuery, &amp;nodes);<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if(FAILED(hr))<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; nodes=0;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; SysFreeString(xmlQuery);<br />&nbsp;&nbsp;&nbsp; }<br />&nbsp;&nbsp;&nbsp; return nodes;<br />}</p>
<p>WString XmlReadString(IXMLDOMNode* node)<br />{<br />&nbsp;&nbsp;&nbsp; WString result;<br />&nbsp;&nbsp;&nbsp; BSTR text=0;<br />&nbsp;&nbsp;&nbsp; HRESULT hr=node-&gt;get_text(&amp;text);<br />&nbsp;&nbsp;&nbsp; if(SUCCEEDED(hr))<br />&nbsp;&nbsp;&nbsp; {<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; const wchar_t* candidateItem=text;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; result=candidateItem;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; SysFreeString(text);<br />&nbsp;&nbsp;&nbsp; }<br />&nbsp;&nbsp;&nbsp; return result;<br />}</p>
<p>void XmlReadMultipleStrings(IXMLDOMNodeList* textNodes, List&lt;WString&gt;&amp; candidates, int max)<br />{<br />&nbsp;&nbsp;&nbsp; candidates.Clear();<br />&nbsp;&nbsp;&nbsp; while((int)candidates.Count()&lt;max)<br />&nbsp;&nbsp;&nbsp; {<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; IXMLDOMNode* textNode=0;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; HRESULT hr=textNodes-&gt;nextNode(&amp;textNode);<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if(hr==S_OK)<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; candidates.Add(XmlReadString(textNode));<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; textNode-&gt;Release();<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; else<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; break;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />&nbsp;&nbsp;&nbsp; }<br />}</p>
<p>IXMLDOMDocument2* XmlLoad(const WString&amp; content)<br />{<br />&nbsp;&nbsp;&nbsp; IXMLDOMDocument2* pDom=0;<br />&nbsp;&nbsp;&nbsp; HRESULT hr=CoCreateInstance(__uuidof(DOMDocument60), NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&amp;pDom));<br />&nbsp;&nbsp;&nbsp; if(SUCCEEDED(hr))<br />&nbsp;&nbsp;&nbsp; {<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; pDom-&gt;put_async(VARIANT_FALSE);<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; pDom-&gt;put_validateOnParse(VARIANT_FALSE);<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; pDom-&gt;put_resolveExternals(VARIANT_FALSE);</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; BSTR xmlContent=SysAllocString(content.Buffer());<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if(xmlContent)<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; VARIANT_BOOL isSuccessful=0;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; hr=pDom-&gt;loadXML(xmlContent, &amp;isSuccessful);<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if(!(SUCCEEDED(hr) &amp;&amp; isSuccessful==VARIANT_TRUE))<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; pDom-&gt;Release();<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; pDom=0;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; SysFreeString(xmlContent);<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />&nbsp;&nbsp;&nbsp; }<br />&nbsp;&nbsp;&nbsp; return pDom;<br />}<br /></p>
<p>有了这几个函数之后，我们就可以干下面的事情，譬如说从鸟窝首页下载第一页的所有topic的标题：</p>
<p>WString xml=NestleGetXml(L&#8221;/topics&#8221;, cookie);<br />IXMLDOMDocument2* pDom=XmlLoad(xml);<br />List&lt;WString&gt; titles;<br />IXMLNodeList* nodes=XmlQuery(pDom, L&#8221;/hash/topics/topic/title/text()&#8221;);<br />XmlReadMultipleStrings(nodes, titles, 100);</p>
<p>为什么上面的xpath是hash/topics/topic/title/text()呢?因为这个xml的内容大概类似于：<br />&lt;hash&gt;<br />&nbsp;&nbsp;&nbsp; &lt;topics&gt;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;topic&gt;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;title&gt;TITLE&lt;/title&gt;<br />&#8230;</p>
<p>剩下的大家就去看代码吧。这个故事告诉我们，只要有一个合适的封装，C++写起这些本来应该让C#来写的东西也不是那么的烦人的，啊哈哈哈哈。</p><img src ="http://www.cppblog.com/vczh/aggbug/193945.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/vczh/" target="_blank">陈梓瀚(vczh)</a> 2012-10-27 15:19 <a href="http://www.cppblog.com/vczh/archive/2012/10/27/193945.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>开始使用VS2012来开发GacUI了</title><link>http://www.cppblog.com/vczh/archive/2012/08/30/188822.html</link><dc:creator>陈梓瀚(vczh)</dc:creator><author>陈梓瀚(vczh)</author><pubDate>Thu, 30 Aug 2012 13:29:00 GMT</pubDate><guid>http://www.cppblog.com/vczh/archive/2012/08/30/188822.html</guid><wfw:comment>http://www.cppblog.com/vczh/comments/188822.html</wfw:comment><comments>http://www.cppblog.com/vczh/archive/2012/08/30/188822.html#Feedback</comments><slash:comments>25</slash:comments><wfw:commentRss>http://www.cppblog.com/vczh/comments/commentRss/188822.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/vczh/services/trackbacks/188822.html</trackback:ping><description><![CDATA[<div>&nbsp;&nbsp;&nbsp; Visual Studio 2012发布的那一天我就把它搞到手了。新的C++ IDE真的是劲爆了，写代码的感觉毫不亚于C#。我最喜欢的部分是智能补全和着色部分。如今C++的宏被渲染成屎红色，类型被渲染成屎绿色，参数被渲染成屎灰色，这样基本不需要要编译，看着颜色都知道有没有写对。智能补全已经赶超VAX，而且还实现了&#8220;缩写过滤&#8221;，譬如说输入PNT就可以在弹出列表里面显示所有大写字母为PNT的对象（譬如说ParsingNodeTransition）等等。这样做的好处是，我只要打有限几个字符就可以补完一整句了，输入速度大大提高。<br /><br />&nbsp;&nbsp;&nbsp; 所以我升级了几乎所有工程。文档生成部分由于还在使用DIA100，所以暂时没有升级到2012，不过这是迟早的事情。不过这次升级遇到了几个小问题。<br /><br />&nbsp;&nbsp;&nbsp; 第一个是，对于没有capture任何外部变量的lambda expression，它可以隐式转换成一个函数指针。这个功能VS2010是没有的，结果升级了之后造成了我几个重载函数的问题，不过解决这个东西还是很简单的，只要把lambda表达式先保存在一个vl::Func变量里面就好了。<br /><br />&nbsp;&nbsp;&nbsp; 第二个是WICImagingFactory。在Windows SDK 7.0里面，CLSID_WICImagingFactory指向了WIC的唯一一个版本。在Windows SDK 8.0里面，出现了CLSID_WICImagingFactory1和CLSID_WICImagingFactory2，并且CLSID_WICImagingFactory等于CLSID_WICImagingFactory2。问题就来了，Windows 7里面并没有WICImagingFactory2，结果我CoCreateInstance就是败了。一开始觉得很奇怪，后来想了想，直接用VS那强大的Go To Definition功能跳到了定义CLSID_WICImagingFactory的地方，然后发现了这个事情。因此我就把代码改成了，如果sdk用的是高级版本，就强制使用1.0的。<br /><br />&nbsp;&nbsp;&nbsp; VS2012对模板语法的检查更加严格了。以前还可以写typename A&lt;T&gt;::B&lt;T&gt;，现在不行了，得写成typename A&lt;T&gt;::template B&lt;T&gt;。其实后面那个才是标准的，而且VS2010也支持。只是VS2010也允许你省略template。<br /><br />&nbsp;&nbsp;&nbsp; VS2012对于C++的改进已经跟C#几乎一模一样了，而且VS2012还支持C++的单元测试项目。总的来说，我十分喜欢。</div><img src ="http://www.cppblog.com/vczh/aggbug/188822.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/vczh/" target="_blank">陈梓瀚(vczh)</a> 2012-08-30 21:29 <a href="http://www.cppblog.com/vczh/archive/2012/08/30/188822.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>合并Visual Studio本地C++XML注释文档和PDB的符号内容</title><link>http://www.cppblog.com/vczh/archive/2012/03/10/167539.html</link><dc:creator>陈梓瀚(vczh)</dc:creator><author>陈梓瀚(vczh)</author><pubDate>Sat, 10 Mar 2012 01:04:00 GMT</pubDate><guid>http://www.cppblog.com/vczh/archive/2012/03/10/167539.html</guid><wfw:comment>http://www.cppblog.com/vczh/comments/167539.html</wfw:comment><comments>http://www.cppblog.com/vczh/archive/2012/03/10/167539.html#Feedback</comments><slash:comments>7</slash:comments><wfw:commentRss>http://www.cppblog.com/vczh/comments/commentRss/167539.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/vczh/services/trackbacks/167539.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要:     终于到了激动人心的时刻了。今天的博客内容将永远消除Visual Studio的本地C++XML注释编译出来的XML文档没有办法生成可读文档的根本原因。<br><br>    首先介绍一下C++的XML注释。在启用注释之前，我们必须先去工程属性里面，把[C/C++ -> Output Files -> Generate Xml Documentation Files]设置成Yes。这样我们就可以在C++的类啊函数上面写XML注释，然后被编译成一份带有符号链接的XML注释集合。这里先给一个GacUI的XML注释的例子：&nbsp;&nbsp;<a href='http://www.cppblog.com/vczh/archive/2012/03/10/167539.html'>阅读全文</a><img src ="http://www.cppblog.com/vczh/aggbug/167539.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/vczh/" target="_blank">陈梓瀚(vczh)</a> 2012-03-10 09:04 <a href="http://www.cppblog.com/vczh/archive/2012/03/10/167539.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>使用VS API开发一个PDB Dumper并且可以在没装VS2010的计算机上运行</title><link>http://www.cppblog.com/vczh/archive/2012/03/10/167538.html</link><dc:creator>陈梓瀚(vczh)</dc:creator><author>陈梓瀚(vczh)</author><pubDate>Fri, 09 Mar 2012 22:43:00 GMT</pubDate><guid>http://www.cppblog.com/vczh/archive/2012/03/10/167538.html</guid><wfw:comment>http://www.cppblog.com/vczh/comments/167538.html</wfw:comment><comments>http://www.cppblog.com/vczh/archive/2012/03/10/167538.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/vczh/comments/commentRss/167538.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/vczh/services/trackbacks/167538.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要:     GacUI到了撰写文档的时候了。虽然GacUI本身的功能还没有全部完成，但是发布一个alpha版还是可以的。因此GacUI需要一份文档。自从.net语言支持XML注释生成文档之后，Visual Studio的本地C++也支持使用XML注释了。只要打开了[工程属性 -> C/C++ -> Output Files -> Generate XML Documentation Files]之后，Visual Studio会在编译本地C++工程之后，将所有的XML注释收集起来，放在和可执行文件同一个目录下的<ProjectName.xml>里面。然后我就尝试bing了一下有没有从C++的XML文档生成可读文档的工具，结果发现只有.net才支持。<br><br>    后来我稍微研究了一下（详细内容将会在下一篇博客透露），发现之所以没人写这个工具，是因为只有.net的可执行文件才包含足够多的元数据，而且这些元数据是必须的，否则无法生成一个完整的文档。举个例子，虽然<ProjectName.xml>包含了xml注释和该注释所在的符号，但是却没有包含该符号的结构信息。结果你试图生成一个函&nbsp;&nbsp;<a href='http://www.cppblog.com/vczh/archive/2012/03/10/167538.html'>阅读全文</a><img src ="http://www.cppblog.com/vczh/aggbug/167538.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/vczh/" target="_blank">陈梓瀚(vczh)</a> 2012-03-10 06:43 <a href="http://www.cppblog.com/vczh/archive/2012/03/10/167538.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>C++反射实现方法设想（GacUI）</title><link>http://www.cppblog.com/vczh/archive/2012/01/11/164003.html</link><dc:creator>陈梓瀚(vczh)</dc:creator><author>陈梓瀚(vczh)</author><pubDate>Wed, 11 Jan 2012 11:39:00 GMT</pubDate><guid>http://www.cppblog.com/vczh/archive/2012/01/11/164003.html</guid><wfw:comment>http://www.cppblog.com/vczh/comments/164003.html</wfw:comment><comments>http://www.cppblog.com/vczh/archive/2012/01/11/164003.html#Feedback</comments><slash:comments>6</slash:comments><wfw:commentRss>http://www.cppblog.com/vczh/comments/commentRss/164003.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/vczh/services/trackbacks/164003.html</trackback:ping><description><![CDATA[<div>&nbsp;&nbsp;&nbsp; C++的反射一直是一个很多人都在做的事情。不过今天我终于有了一个简单的想法，当然只对VC++编译出来的程序有效。首先看下面的一个单元测试：<br /><br />&nbsp;&nbsp;&nbsp; 如果我们有下面的代码： 
<div style="border-bottom: #cccccc 1px solid; border-left: #cccccc 1px solid; padding-bottom: 4px; background-color: #eeeeee; padding-left: 4px; width: 98%; padding-right: 5px; font-size: 13px; word-break: break-all; border-top: #cccccc 1px solid; border-right: #cccccc 1px solid; padding-top: 4px"><!--<br /><br />Code highlighting produced by Actipro CodeHighlighter (freeware)<br />http://www.CodeHighlighter.com/<br /><br />--><span style="color: #008080">1</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff">class</span><span style="color: #000000">&nbsp;A{};<br /></span><span style="color: #008080">2</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff">class</span><span style="color: #000000">&nbsp;B:</span><span style="color: #0000ff">public</span><span style="color: #000000">&nbsp;A{};<br /></span><span style="color: #008080">3</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff">class</span><span style="color: #000000">&nbsp;C:</span><span style="color: #0000ff">public</span><span style="color: #000000">&nbsp;A{};<br /></span><span style="color: #008080">4</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff">class</span><span style="color: #000000">&nbsp;D:</span><span style="color: #0000ff">public</span><span style="color: #000000">&nbsp;B,&nbsp;</span><span style="color: #0000ff">public</span><span style="color: #000000">&nbsp;C{};<br /></span><span style="color: #008080">5</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff">class</span><span style="color: #000000">&nbsp;E:</span><span style="color: #0000ff">virtual</span><span style="color: #000000">&nbsp;</span><span style="color: #0000ff">public</span><span style="color: #000000">&nbsp;A{};<br /></span><span style="color: #008080">6</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff">class</span><span style="color: #000000">&nbsp;F:</span><span style="color: #0000ff">virtual</span><span style="color: #000000">&nbsp;</span><span style="color: #0000ff">public</span><span style="color: #000000">&nbsp;A{};<br /></span><span style="color: #008080">7</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff">class</span><span style="color: #000000">&nbsp;G:</span><span style="color: #0000ff">public</span><span style="color: #000000">&nbsp;E,&nbsp;</span><span style="color: #0000ff">public</span><span style="color: #000000">&nbsp;F{};</span></div>&nbsp;&nbsp;&nbsp; 那么下面的事情一定会发生： 
<div style="border-bottom: #cccccc 1px solid; border-left: #cccccc 1px solid; padding-bottom: 4px; background-color: #eeeeee; padding-left: 4px; width: 98%; padding-right: 5px; font-size: 13px; word-break: break-all; border-top: #cccccc 1px solid; border-right: #cccccc 1px solid; padding-top: 4px"><!--<br /><br />Code highlighting produced by Actipro CodeHighlighter (freeware)<br />http://www.CodeHighlighter.com/<br /><br />--><span style="color: #008080">1</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;D&nbsp;d;<br /></span><span style="color: #008080">2</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;A</span><span style="color: #000000">&amp;</span><span style="color: #000000">&nbsp;da1</span><span style="color: #000000">=</span><span style="color: #000000">static_cast</span><span style="color: #000000">&lt;</span><span style="color: #000000">B</span><span style="color: #000000">&amp;&gt;</span><span style="color: #000000">(d);<br /></span><span style="color: #008080">3</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;A</span><span style="color: #000000">&amp;</span><span style="color: #000000">&nbsp;da2</span><span style="color: #000000">=</span><span style="color: #000000">static_cast</span><span style="color: #000000">&lt;</span><span style="color: #000000">C</span><span style="color: #000000">&amp;&gt;</span><span style="color: #000000">(d);<br /></span><span style="color: #008080">4</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;TEST_ASSERT(</span><span style="color: #000000">&amp;</span><span style="color: #000000">da1</span><span style="color: #000000">!=&amp;</span><span style="color: #000000">da2);<br /></span><span style="color: #008080">5</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;<br /></span><span style="color: #008080">6</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;G&nbsp;g;<br /></span><span style="color: #008080">7</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;A</span><span style="color: #000000">&amp;</span><span style="color: #000000">&nbsp;ga1</span><span style="color: #000000">=</span><span style="color: #000000">static_cast</span><span style="color: #000000">&lt;</span><span style="color: #000000">E</span><span style="color: #000000">&amp;&gt;</span><span style="color: #000000">(g);<br /></span><span style="color: #008080">8</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;A</span><span style="color: #000000">&amp;</span><span style="color: #000000">&nbsp;ga2</span><span style="color: #000000">=</span><span style="color: #000000">static_cast</span><span style="color: #000000">&lt;</span><span style="color: #000000">F</span><span style="color: #000000">&amp;&gt;</span><span style="color: #000000">(g);<br /></span><span style="color: #008080">9</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;TEST_ASSERT(</span><span style="color: #000000">&amp;</span><span style="color: #000000">ga1</span><span style="color: #000000">==&amp;</span><span style="color: #000000">ga2);</span></div><br />&nbsp;&nbsp;&nbsp; 对于这种virtual继承的事情，到这里还是很容易理解的。那现在我们来更进一步： 
<div style="border-bottom: #cccccc 1px solid; border-left: #cccccc 1px solid; padding-bottom: 4px; background-color: #eeeeee; padding-left: 4px; width: 98%; padding-right: 5px; font-size: 13px; word-break: break-all; border-top: #cccccc 1px solid; border-right: #cccccc 1px solid; padding-top: 4px"><!--<br /><br />Code highlighting produced by Actipro CodeHighlighter (freeware)<br />http://www.CodeHighlighter.com/<br /><br />--><span style="color: #008080">&nbsp;1</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff">class</span><span style="color: #000000">&nbsp;Base<br /></span><span style="color: #008080">&nbsp;2</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;{<br /></span><span style="color: #008080">&nbsp;3</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff">public</span><span style="color: #000000">:<br /></span><span style="color: #008080">&nbsp;4</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;size_t&nbsp;size;<br /></span><span style="color: #008080">&nbsp;5</span>&nbsp;<span style="color: #000000"><br /></span><span style="color: #008080">&nbsp;6</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Base()<br /></span><span style="color: #008080">&nbsp;7</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;:size(</span><span style="color: #000000">0</span><span style="color: #000000">)<br /></span><span style="color: #008080">&nbsp;8</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{<br /></span><span style="color: #008080">&nbsp;9</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br /></span><span style="color: #008080">10</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;};<br /></span><span style="color: #008080">11</span>&nbsp;<span style="color: #000000"><br /></span><span style="color: #008080">12</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;template</span><span style="color: #000000">&lt;</span><span style="color: #000000">typename&nbsp;T</span><span style="color: #000000">&gt;</span><span style="color: #000000"><br /></span><span style="color: #008080">13</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff">class</span><span style="color: #000000">&nbsp;Derived&nbsp;:&nbsp;</span><span style="color: #0000ff">public</span><span style="color: #000000">&nbsp;</span><span style="color: #0000ff">virtual</span><span style="color: #000000">&nbsp;Base<br /></span><span style="color: #008080">14</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;{<br /></span><span style="color: #008080">15</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff">public</span><span style="color: #000000">:<br /></span><span style="color: #008080">16</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Derived()<br /></span><span style="color: #008080">17</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{<br /></span><span style="color: #008080">18</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff">if</span><span style="color: #000000">(size</span><span style="color: #000000">&lt;</span><span style="color: #0000ff">sizeof</span><span style="color: #000000">(T))&nbsp;size</span><span style="color: #000000">=</span><span style="color: #0000ff">sizeof</span><span style="color: #000000">(T);<br /></span><span style="color: #008080">19</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br /></span><span style="color: #008080">20</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;};<br /></span><span style="color: #008080">21</span>&nbsp;<span style="color: #000000"><br /></span><span style="color: #008080">22</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff">class</span><span style="color: #000000">&nbsp;H&nbsp;:&nbsp;</span><span style="color: #0000ff">public</span><span style="color: #000000">&nbsp;Derived</span><span style="color: #000000">&lt;</span><span style="color: #000000">H</span><span style="color: #000000">&gt;</span><span style="color: #000000">{};<br /></span><span style="color: #008080">23</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff">class</span><span style="color: #000000">&nbsp;I&nbsp;:&nbsp;</span><span style="color: #0000ff">public</span><span style="color: #000000">&nbsp;H,&nbsp;</span><span style="color: #0000ff">public</span><span style="color: #000000">&nbsp;Derived</span><span style="color: #000000">&lt;</span><span style="color: #000000">I</span><span style="color: #000000">&gt;</span><span style="color: #000000">{};<br /></span><span style="color: #008080">24</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff">class</span><span style="color: #000000">&nbsp;J&nbsp;:&nbsp;</span><span style="color: #0000ff">public</span><span style="color: #000000">&nbsp;I,&nbsp;</span><span style="color: #0000ff">public</span><span style="color: #000000">&nbsp;Derived</span><span style="color: #000000">&lt;</span><span style="color: #000000">J</span><span style="color: #000000">&gt;</span><span style="color: #000000">{};</span></div><br />&nbsp;&nbsp;&nbsp; 首先，H、I和J都各自拥有自己的唯一的一个Base。J虽然继承了Derived&lt;H&gt;、Derived&lt;I&gt;和Derived&lt;J&gt;，但是始终只拥有一个Base。因为Base是virtual继承的。<br /><br />&nbsp;&nbsp;&nbsp; 其次，sizeof(Derived&lt;T&gt;)&gt;sizeof(Base)始终是成立的，因为Base的virtual继承导致了Derived&lt;T&gt;里面至少要保存一个指向Base（或者可以用来找到Base）的指针。这个条件很重要，因为这导致了sizeof(J)&gt;sizeof(I)这个条件是恒成立的。<br /><br />&nbsp;&nbsp;&nbsp; 好了，那么来看J。由于C++并没有规定多重继承的时候，几个父类的构造函数的顺序是什么，所以我们需要sizeof(J)&gt;sizeof(I)这个条件。为什么呢？看Derived类的构造函数&#8212;&#8212;它之让sizeof(T)更大的数据覆盖Base里面的数据。<br /><br />&nbsp;&nbsp;&nbsp; 所以我们就可以确定下面的事情： 
<div style="border-bottom: #cccccc 1px solid; border-left: #cccccc 1px solid; padding-bottom: 4px; background-color: #eeeeee; padding-left: 4px; width: 98%; padding-right: 5px; font-size: 13px; word-break: break-all; border-top: #cccccc 1px solid; border-right: #cccccc 1px solid; padding-top: 4px"><!--<br /><br />Code highlighting produced by Actipro CodeHighlighter (freeware)<br />http://www.CodeHighlighter.com/<br /><br />--><span style="color: #008080">1</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff">const</span><span style="color: #000000">&nbsp;H</span><span style="color: #000000">&amp;</span><span style="color: #000000">&nbsp;h</span><span style="color: #000000">=</span><span style="color: #000000">H();<br /></span><span style="color: #008080">2</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff">const</span><span style="color: #000000">&nbsp;H</span><span style="color: #000000">&amp;</span><span style="color: #000000">&nbsp;i</span><span style="color: #000000">=</span><span style="color: #000000">I();<br /></span><span style="color: #008080">3</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff">const</span><span style="color: #000000">&nbsp;H</span><span style="color: #000000">&amp;</span><span style="color: #000000">&nbsp;j</span><span style="color: #000000">=</span><span style="color: #000000">J();<br /></span><span style="color: #008080">4</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;TEST_ASSERT(h.size</span><span style="color: #000000">&lt;</span><span style="color: #000000">i.size);<br /></span><span style="color: #008080">5</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;TEST_ASSERT(i.size</span><span style="color: #000000">&lt;</span><span style="color: #000000">j.size);<br /></span><span style="color: #008080">6</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;TEST_ASSERT(h.size</span><span style="color: #000000">==</span><span style="color: #0000ff">sizeof</span><span style="color: #000000">(H));<br /></span><span style="color: #008080">7</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;TEST_ASSERT(i.size</span><span style="color: #000000">==</span><span style="color: #0000ff">sizeof</span><span style="color: #000000">(I));<br /></span><span style="color: #008080">8</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;TEST_ASSERT(j.size</span><span style="color: #000000">==</span><span style="color: #0000ff">sizeof</span><span style="color: #000000">(J));</span></div><br />&nbsp;&nbsp;&nbsp; 无论J的三个Derived&lt;T&gt;的构造函数谁先执行，最后能够留下来的Base里面的数据肯定是Derived&lt;J&gt;里面的数据。讲到这里应该很清楚了。如果读者还没想到这跟反射有什么关系的话，那么请想一下，如果Base除了size以外，还有一个ITypeDescriptor** typeDescriptor;成员。然后Derived改成这样： 
<div style="border-bottom: #cccccc 1px solid; border-left: #cccccc 1px solid; padding-bottom: 4px; background-color: #eeeeee; padding-left: 4px; width: 98%; padding-right: 5px; font-size: 13px; word-break: break-all; border-top: #cccccc 1px solid; border-right: #cccccc 1px solid; padding-top: 4px"><!--<br /><br />Code highlighting produced by Actipro CodeHighlighter (freeware)<br />http://www.CodeHighlighter.com/<br /><br />--><span style="color: #008080">&nbsp;1</span>&nbsp;<span style="color: #000000">template</span><span style="color: #000000">&lt;</span><span style="color: #000000">typename&nbsp;T</span><span style="color: #000000">&gt;</span><span style="color: #000000"><br /></span><span style="color: #008080">&nbsp;2</span>&nbsp;<span style="color: #000000"></span><span style="color: #0000ff">class</span><span style="color: #000000">&nbsp;Derived&nbsp;:&nbsp;<img alt="" src="http://www.cppblog.com/Images/dot.gif" /><br /></span><span style="color: #008080">&nbsp;3</span>&nbsp;<span style="color: #000000">{<br /></span><span style="color: #008080">&nbsp;4</span>&nbsp;<span style="color: #000000"></span><span style="color: #0000ff">public</span><span style="color: #000000">:<br /></span><span style="color: #008080">&nbsp;5</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff">static</span><span style="color: #000000">&nbsp;ITypeDescriptor</span><span style="color: #000000">*</span><span style="color: #000000">&nbsp;type;<br /></span><span style="color: #008080">&nbsp;6</span>&nbsp;<span style="color: #000000"><br /></span><span style="color: #008080">&nbsp;7</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;Derived()<br /></span><span style="color: #008080">&nbsp;8</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;{<br /></span><span style="color: #008080">&nbsp;9</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff">if</span><span style="color: #000000">(<img alt="" src="http://www.cppblog.com/Images/dot.gif" />){size</span><span style="color: #000000">=</span><span style="color: #0000ff">sizeof</span><span style="color: #000000">(T);&nbsp;typeDescriptor</span><span style="color: #000000">=&amp;</span><span style="color: #000000">type;}<br /></span><span style="color: #008080">10</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;}<br /></span><span style="color: #008080">11</span>&nbsp;<span style="color: #000000">};</span></div><br />&nbsp;&nbsp;&nbsp; 那么不管你的J拿到手里的类型是什么，哪怕是const H&amp; j，那么j.typeDescriptor肯定就是&amp;Derived&lt;J&gt;::type;<br /><br />&nbsp;&nbsp;&nbsp; 到这里还没有跟VC++有关系的东西。假设ITypeDescriptor是一个足够代表反射功能的高级接口的话，那么我们要怎么实现它呢？我们自己来按照字符串去调用各种函数什么的去实现它肯定麻烦到死了。但是如果大家还记的我前面的<a style="text-decoration: underline" href="http://www.cppblog.com/vczh/archive/2011/12/30/163200.html" target="_blank">这篇博客文章</a>的话，那么大家肯定想到了，我们可以写一个程序来替我们读pdb生成ITypeDescriptor的代码，还有把具体的对象赋值进Derived&lt;T&gt;::type里面去的一个初始化函数！啊哈哈哈！当然pdb只能是从Visual C++编译出来的，就算不是，也至少只能是Windows上面的。不过对GacUI来说并无所谓。因为我只要把GacUI在VisualStudio里面编译生成反射的代码，这个生成之后的代码我还是能放到其他地方编译的。到时候我只要连同这段代码一并发布就好了。<br /><br />&nbsp;&nbsp;&nbsp; 当然，这个程序不仅仅可以帮我实现ITypeDescriptor，还可以帮我实现C语言和C++语言的dll接口的实现，因为dll里面肯定不能暴露模板的。下面就仅需要我去把它做出来就可以了。至此，我们让一个类支持反射的代价很低&#8212;&#8212;只要让他继承自Derived&lt;自己&gt;就好了。<br /></div><img src ="http://www.cppblog.com/vczh/aggbug/164003.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/vczh/" target="_blank">陈梓瀚(vczh)</a> 2012-01-11 19:39 <a href="http://www.cppblog.com/vczh/archive/2012/01/11/164003.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>回来了（VL++3.0计划）</title><link>http://www.cppblog.com/vczh/archive/2009/10/09/98207.html</link><dc:creator>陈梓瀚(vczh)</dc:creator><author>陈梓瀚(vczh)</author><pubDate>Fri, 09 Oct 2009 15:17:00 GMT</pubDate><guid>http://www.cppblog.com/vczh/archive/2009/10/09/98207.html</guid><wfw:comment>http://www.cppblog.com/vczh/comments/98207.html</wfw:comment><comments>http://www.cppblog.com/vczh/archive/2009/10/09/98207.html#Feedback</comments><slash:comments>11</slash:comments><wfw:commentRss>http://www.cppblog.com/vczh/comments/commentRss/98207.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/vczh/services/trackbacks/98207.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要:     之前因为非常忙，加上无聊开发什么类似WCF和WPF的东西，最近终于找到了新的目标了，于是之前那些就不做了。隔了这么久没法文章主要是因为最近没写出什么完整的东西。国庆玩了9天，之前在计划VL++3.0。<br><br>    VL++3.0被定位为一个为了数据处理而开发的C++库。这个库不的特点在于“其他语言的味道很浓”。C++的库用起来不爽主要是因为老是要我按下划线，而且大量应用非OOP特性导致IDE的自动补全无法发挥作用。所以为了弥补这个缺陷我做了一个很不一样的东西，也就是VL++了。经过了三年多的开发，1.0和2.0已经相继出炉，每一次打翻新都解决了一些前一个版本解决不了的问题。&nbsp;&nbsp;<a href='http://www.cppblog.com/vczh/archive/2009/10/09/98207.html'>阅读全文</a><img src ="http://www.cppblog.com/vczh/aggbug/98207.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/vczh/" target="_blank">陈梓瀚(vczh)</a> 2009-10-09 23:17 <a href="http://www.cppblog.com/vczh/archive/2009/10/09/98207.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>C++远程调用类操作支持Callback Interface</title><link>http://www.cppblog.com/vczh/archive/2009/07/18/90409.html</link><dc:creator>陈梓瀚(vczh)</dc:creator><author>陈梓瀚(vczh)</author><pubDate>Sat, 18 Jul 2009 02:20:00 GMT</pubDate><guid>http://www.cppblog.com/vczh/archive/2009/07/18/90409.html</guid><wfw:comment>http://www.cppblog.com/vczh/comments/90409.html</wfw:comment><comments>http://www.cppblog.com/vczh/archive/2009/07/18/90409.html#Feedback</comments><slash:comments>10</slash:comments><wfw:commentRss>http://www.cppblog.com/vczh/comments/commentRss/90409.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/vczh/services/trackbacks/90409.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: 今天展示一下如何使用这里描写的库实现一个简单的聊天工具。Callback Interface是昨晚才加的，但是还有一些features没实现，等做完了再做一个Demo，然后提供源代码。<br><br>使用这个东西可以开发一些C/S模式的程序，然后只需要将服务器和客户端看成同一个程序，将客户端认为是很多个线程就行了。服务器端提供一些类给客户端创建并使用，当这些服务类要求回调的时候，客户端只需要按照回调的接口实现回调，然后将指针提供给服务类就行了。剩下来的链接啊调度啊网络传输的问题就全部不用管了，非常方便。&nbsp;&nbsp;<a href='http://www.cppblog.com/vczh/archive/2009/07/18/90409.html'>阅读全文</a><img src ="http://www.cppblog.com/vczh/aggbug/90409.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/vczh/" target="_blank">陈梓瀚(vczh)</a> 2009-07-18 10:20 <a href="http://www.cppblog.com/vczh/archive/2009/07/18/90409.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>实现C++远程调用类的操作。</title><link>http://www.cppblog.com/vczh/archive/2009/07/04/89211.html</link><dc:creator>陈梓瀚(vczh)</dc:creator><author>陈梓瀚(vczh)</author><pubDate>Sat, 04 Jul 2009 02:07:00 GMT</pubDate><guid>http://www.cppblog.com/vczh/archive/2009/07/04/89211.html</guid><wfw:comment>http://www.cppblog.com/vczh/comments/89211.html</wfw:comment><comments>http://www.cppblog.com/vczh/archive/2009/07/04/89211.html#Feedback</comments><slash:comments>14</slash:comments><wfw:commentRss>http://www.cppblog.com/vczh/comments/commentRss/89211.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/vczh/services/trackbacks/89211.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: 这次展示如何将一个服务器端的C++类让客户端调用。使用早上刚刚开发完的工具，用户可以不用处理任何传输过程中的连接和编码解码等操作。这次实现一个四则运算的语法分析器，客户端发送表达式，服务器端传回语法树（继承树那个模型），客户端将语法树传回去，服务器端传回运算结果。&nbsp;&nbsp;<a href='http://www.cppblog.com/vczh/archive/2009/07/04/89211.html'>阅读全文</a><img src ="http://www.cppblog.com/vczh/aggbug/89211.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/vczh/" target="_blank">陈梓瀚(vczh)</a> 2009-07-04 10:07 <a href="http://www.cppblog.com/vczh/archive/2009/07/04/89211.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>C++基于类似反射机制的函数调用完成</title><link>http://www.cppblog.com/vczh/archive/2009/06/30/88880.html</link><dc:creator>陈梓瀚(vczh)</dc:creator><author>陈梓瀚(vczh)</author><pubDate>Tue, 30 Jun 2009 04:47:00 GMT</pubDate><guid>http://www.cppblog.com/vczh/archive/2009/06/30/88880.html</guid><wfw:comment>http://www.cppblog.com/vczh/comments/88880.html</wfw:comment><comments>http://www.cppblog.com/vczh/archive/2009/06/30/88880.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/vczh/comments/commentRss/88880.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/vczh/services/trackbacks/88880.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: 现在不仅可以阅读类的成员，也可以用函数名的字符串去调用函数并取得结果了。代码与这篇文章的实例类似，因此只贴出更改的部分以及程序截图。&nbsp;&nbsp;<a href='http://www.cppblog.com/vczh/archive/2009/06/30/88880.html'>阅读全文</a><img src ="http://www.cppblog.com/vczh/aggbug/88880.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/vczh/" target="_blank">陈梓瀚(vczh)</a> 2009-06-30 12:47 <a href="http://www.cppblog.com/vczh/archive/2009/06/30/88880.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>残废版HTTP Server之小试牛刀</title><link>http://www.cppblog.com/vczh/archive/2009/06/29/88834.html</link><dc:creator>陈梓瀚(vczh)</dc:creator><author>陈梓瀚(vczh)</author><pubDate>Mon, 29 Jun 2009 13:19:00 GMT</pubDate><guid>http://www.cppblog.com/vczh/archive/2009/06/29/88834.html</guid><wfw:comment>http://www.cppblog.com/vczh/comments/88834.html</wfw:comment><comments>http://www.cppblog.com/vczh/archive/2009/06/29/88834.html#Feedback</comments><slash:comments>4</slash:comments><wfw:commentRss>http://www.cppblog.com/vczh/comments/commentRss/88834.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/vczh/services/trackbacks/88834.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: 为了给C++的反射做Demo，不得不研究一下HTTP的协议。后来发现Windows自带了API可以用，于是就写了个小东西。程序打开之后，如果检测到【http://localhost:8080/vczh/FILENAME】这样子的请求，就将一个目录下面的东西读出来，然后返回。于是就可以用IE来运行某个地方的网页了。代码如下：&nbsp;&nbsp;<a href='http://www.cppblog.com/vczh/archive/2009/06/29/88834.html'>阅读全文</a><img src ="http://www.cppblog.com/vczh/aggbug/88834.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/vczh/" target="_blank">陈梓瀚(vczh)</a> 2009-06-29 21:19 <a href="http://www.cppblog.com/vczh/archive/2009/06/29/88834.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>C++基于类似反射机制的XML序列化和反序列化完成</title><link>http://www.cppblog.com/vczh/archive/2009/06/29/88758.html</link><dc:creator>陈梓瀚(vczh)</dc:creator><author>陈梓瀚(vczh)</author><pubDate>Mon, 29 Jun 2009 04:12:00 GMT</pubDate><guid>http://www.cppblog.com/vczh/archive/2009/06/29/88758.html</guid><wfw:comment>http://www.cppblog.com/vczh/comments/88758.html</wfw:comment><comments>http://www.cppblog.com/vczh/archive/2009/06/29/88758.html#Feedback</comments><slash:comments>10</slash:comments><wfw:commentRss>http://www.cppblog.com/vczh/comments/commentRss/88758.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/vczh/services/trackbacks/88758.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: 跟上一篇文章一样，一样的数据结构，但是添加了对数组、列表和映射的更多的支持。首先是代码，然后是序列化后的XML文件。&nbsp;&nbsp;<a href='http://www.cppblog.com/vczh/archive/2009/06/29/88758.html'>阅读全文</a><img src ="http://www.cppblog.com/vczh/aggbug/88758.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/vczh/" target="_blank">陈梓瀚(vczh)</a> 2009-06-29 12:12 <a href="http://www.cppblog.com/vczh/archive/2009/06/29/88758.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>重写了C++的类似反射的工具</title><link>http://www.cppblog.com/vczh/archive/2009/06/25/88533.html</link><dc:creator>陈梓瀚(vczh)</dc:creator><author>陈梓瀚(vczh)</author><pubDate>Thu, 25 Jun 2009 14:48:00 GMT</pubDate><guid>http://www.cppblog.com/vczh/archive/2009/06/25/88533.html</guid><wfw:comment>http://www.cppblog.com/vczh/comments/88533.html</wfw:comment><comments>http://www.cppblog.com/vczh/archive/2009/06/25/88533.html#Feedback</comments><slash:comments>4</slash:comments><wfw:commentRss>http://www.cppblog.com/vczh/comments/commentRss/88533.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/vczh/services/trackbacks/88533.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: 所谓的反射当然不是自动化的，而是需要自己打标记的。下面会展示两份文件，告诉大家我这个做了一半的反射是怎么工作的。写这个东西的主要目的是，将来可以实现序列化，譬如说序列化到流，或者序列化到XML文件，或者做其他的事情等等（譬如说使用类名创建对象并进行修改）。&nbsp;&nbsp;<a href='http://www.cppblog.com/vczh/archive/2009/06/25/88533.html'>阅读全文</a><img src ="http://www.cppblog.com/vczh/aggbug/88533.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/vczh/" target="_blank">陈梓瀚(vczh)</a> 2009-06-25 22:48 <a href="http://www.cppblog.com/vczh/archive/2009/06/25/88533.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>使用COM实现控件内容的Drag and Drop</title><link>http://www.cppblog.com/vczh/archive/2009/05/30/86163.html</link><dc:creator>陈梓瀚(vczh)</dc:creator><author>陈梓瀚(vczh)</author><pubDate>Sat, 30 May 2009 05:17:00 GMT</pubDate><guid>http://www.cppblog.com/vczh/archive/2009/05/30/86163.html</guid><wfw:comment>http://www.cppblog.com/vczh/comments/86163.html</wfw:comment><comments>http://www.cppblog.com/vczh/archive/2009/05/30/86163.html#Feedback</comments><slash:comments>4</slash:comments><wfw:commentRss>http://www.cppblog.com/vczh/comments/commentRss/86163.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/vczh/services/trackbacks/86163.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要:     查了MSDN，发现Windows支持Drag and Drop的方法是四个COM：IEnumFORMATETC、IDataObject、IDropSource和IDropTarget。为了让自己做的一个代码编辑文本框里面的代码可以被拖出去拖进来，无奈之下只好实现了这四个东西。<br><br>    实现了之后，程序刚开始需要调用OldInitialize(NULL);，结束的时候调用OnUninitialize();，控件创建的时候调用RegisterDragDrop，控件结束的时候调用RevokeDragDrop。然后就可以通过这些COM来做Drag and Drop了。下面是接口的实现：&nbsp;&nbsp;<a href='http://www.cppblog.com/vczh/archive/2009/05/30/86163.html'>阅读全文</a><img src ="http://www.cppblog.com/vczh/aggbug/86163.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/vczh/" target="_blank">陈梓瀚(vczh)</a> 2009-05-30 13:17 <a href="http://www.cppblog.com/vczh/archive/2009/05/30/86163.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Combinator Parser修改错误处理方法</title><link>http://www.cppblog.com/vczh/archive/2009/05/04/81860.html</link><dc:creator>陈梓瀚(vczh)</dc:creator><author>陈梓瀚(vczh)</author><pubDate>Mon, 04 May 2009 10:35:00 GMT</pubDate><guid>http://www.cppblog.com/vczh/archive/2009/05/04/81860.html</guid><wfw:comment>http://www.cppblog.com/vczh/comments/81860.html</wfw:comment><comments>http://www.cppblog.com/vczh/archive/2009/05/04/81860.html#Feedback</comments><slash:comments>1</slash:comments><wfw:commentRss>http://www.cppblog.com/vczh/comments/commentRss/81860.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/vczh/services/trackbacks/81860.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: 在实验了CMinus语法分析器的错误处理之后发现一个问题，Combinator Parser返回的错误是最上级的错误，而不是最底层的错误。因此修改了语法分析器的一部分代码：&nbsp;&nbsp;<a href='http://www.cppblog.com/vczh/archive/2009/05/04/81860.html'>阅读全文</a><img src ="http://www.cppblog.com/vczh/aggbug/81860.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/vczh/" target="_blank">陈梓瀚(vczh)</a> 2009-05-04 18:35 <a href="http://www.cppblog.com/vczh/archive/2009/05/04/81860.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>搞定模板元编程（meta programming）</title><link>http://www.cppblog.com/vczh/archive/2009/04/08/79291.html</link><dc:creator>陈梓瀚(vczh)</dc:creator><author>陈梓瀚(vczh)</author><pubDate>Wed, 08 Apr 2009 13:17:00 GMT</pubDate><guid>http://www.cppblog.com/vczh/archive/2009/04/08/79291.html</guid><wfw:comment>http://www.cppblog.com/vczh/comments/79291.html</wfw:comment><comments>http://www.cppblog.com/vczh/archive/2009/04/08/79291.html#Feedback</comments><slash:comments>13</slash:comments><wfw:commentRss>http://www.cppblog.com/vczh/comments/commentRss/79291.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/vczh/services/trackbacks/79291.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要:     今天闲得无聊，早上起来习惯性瞟一瞟boost，突然看中了它的MPL库，所以自己实现了一个子集消磨时间。<br><br>    已经实现的功能有：整数运算、闭包、列表处理等。我用了自己的unit test框架，通过写一个函数输出一个属于自己的MPL类型的字符串（譬如List<Int<0>,List<Int<1>,Empty>>产生"[0 , 1]"），然后用自己写的字符串比较，可以发现库里面是否有错。<br><br>    一下有两份代码，第一份是使用，第二份是自己的MPL的源代码：&nbsp;&nbsp;<a href='http://www.cppblog.com/vczh/archive/2009/04/08/79291.html'>阅读全文</a><img src ="http://www.cppblog.com/vczh/aggbug/79291.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/vczh/" target="_blank">陈梓瀚(vczh)</a> 2009-04-08 21:17 <a href="http://www.cppblog.com/vczh/archive/2009/04/08/79291.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>实现了Huffman压缩解压算法</title><link>http://www.cppblog.com/vczh/archive/2009/01/11/71693.html</link><dc:creator>陈梓瀚(vczh)</dc:creator><author>陈梓瀚(vczh)</author><pubDate>Sat, 10 Jan 2009 17:16:00 GMT</pubDate><guid>http://www.cppblog.com/vczh/archive/2009/01/11/71693.html</guid><wfw:comment>http://www.cppblog.com/vczh/comments/71693.html</wfw:comment><comments>http://www.cppblog.com/vczh/archive/2009/01/11/71693.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/vczh/comments/commentRss/71693.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/vczh/services/trackbacks/71693.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: 今天终于在流系统里面添加了Huffman的压缩解压算法，现在只需要将一个LZ77流加到Huffman流上面，就能同时使用两种压缩算法进行压缩了。我的Huffman算法使用了Canonical Huffman编码方法进行Huffman树的生成。&nbsp;&nbsp;<a href='http://www.cppblog.com/vczh/archive/2009/01/11/71693.html'>阅读全文</a><img src ="http://www.cppblog.com/vczh/aggbug/71693.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/vczh/" target="_blank">陈梓瀚(vczh)</a> 2009-01-11 01:16 <a href="http://www.cppblog.com/vczh/archive/2009/01/11/71693.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>修改后的LZ77压缩解压源码</title><link>http://www.cppblog.com/vczh/archive/2009/01/07/71418.html</link><dc:creator>陈梓瀚(vczh)</dc:creator><author>陈梓瀚(vczh)</author><pubDate>Wed, 07 Jan 2009 07:35:00 GMT</pubDate><guid>http://www.cppblog.com/vczh/archive/2009/01/07/71418.html</guid><wfw:comment>http://www.cppblog.com/vczh/comments/71418.html</wfw:comment><comments>http://www.cppblog.com/vczh/archive/2009/01/07/71418.html#Feedback</comments><slash:comments>6</slash:comments><wfw:commentRss>http://www.cppblog.com/vczh/comments/commentRss/71418.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/vczh/services/trackbacks/71418.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: 这份代码跟上一次相比，修正了以下部分：<br><br>1、可修改的Window Size。压缩流会把Window Size写进去，解压流能够自动获取。<br>2、发现冗余的地方，每一个标记的压缩块节省了一位。<br>3、如果用户一次性写入的字节不够多则会缓存起来，上一版本则是直接压缩完。这样会丢失某些原本可以压缩的数据，因此修正。&nbsp;&nbsp;<a href='http://www.cppblog.com/vczh/archive/2009/01/07/71418.html'>阅读全文</a><img src ="http://www.cppblog.com/vczh/aggbug/71418.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/vczh/" target="_blank">陈梓瀚(vczh)</a> 2009-01-07 15:35 <a href="http://www.cppblog.com/vczh/archive/2009/01/07/71418.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>LZ77压缩效果测试</title><link>http://www.cppblog.com/vczh/archive/2009/01/07/71390.html</link><dc:creator>陈梓瀚(vczh)</dc:creator><author>陈梓瀚(vczh)</author><pubDate>Tue, 06 Jan 2009 16:36:00 GMT</pubDate><guid>http://www.cppblog.com/vczh/archive/2009/01/07/71390.html</guid><wfw:comment>http://www.cppblog.com/vczh/comments/71390.html</wfw:comment><comments>http://www.cppblog.com/vczh/archive/2009/01/07/71390.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/vczh/comments/commentRss/71390.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/vczh/services/trackbacks/71390.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: 今天将我的VL_LZ77Stream修改成了可以设置窗口大小的压缩流。我用了一些文件进行压缩和对比。&nbsp;&nbsp;<a href='http://www.cppblog.com/vczh/archive/2009/01/07/71390.html'>阅读全文</a><img src ="http://www.cppblog.com/vczh/aggbug/71390.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/vczh/" target="_blank">陈梓瀚(vczh)</a> 2009-01-07 00:36 <a href="http://www.cppblog.com/vczh/archive/2009/01/07/71390.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>实现了一个128长度窗口大小的LZ77压缩解压算法</title><link>http://www.cppblog.com/vczh/archive/2009/01/06/71275.html</link><dc:creator>陈梓瀚(vczh)</dc:creator><author>陈梓瀚(vczh)</author><pubDate>Mon, 05 Jan 2009 17:47:00 GMT</pubDate><guid>http://www.cppblog.com/vczh/archive/2009/01/06/71275.html</guid><wfw:comment>http://www.cppblog.com/vczh/comments/71275.html</wfw:comment><comments>http://www.cppblog.com/vczh/archive/2009/01/06/71275.html#Feedback</comments><slash:comments>5</slash:comments><wfw:commentRss>http://www.cppblog.com/vczh/comments/commentRss/71275.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/vczh/services/trackbacks/71275.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: 这个压缩流是Vczh Library++ 2.0庞大的流与控制器系统的其中一个部分。我准备将其改造成可调大小的，并且打算添加LZW与Huffman压缩解压算法。以下是用C++实现的代码。&nbsp;&nbsp;<a href='http://www.cppblog.com/vczh/archive/2009/01/06/71275.html'>阅读全文</a><img src ="http://www.cppblog.com/vczh/aggbug/71275.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/vczh/" target="_blank">陈梓瀚(vczh)</a> 2009-01-06 01:47 <a href="http://www.cppblog.com/vczh/archive/2009/01/06/71275.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>分解复杂的命令行参数</title><link>http://www.cppblog.com/vczh/archive/2008/12/24/70253.html</link><dc:creator>陈梓瀚(vczh)</dc:creator><author>陈梓瀚(vczh)</author><pubDate>Wed, 24 Dec 2008 09:13:00 GMT</pubDate><guid>http://www.cppblog.com/vczh/archive/2008/12/24/70253.html</guid><wfw:comment>http://www.cppblog.com/vczh/comments/70253.html</wfw:comment><comments>http://www.cppblog.com/vczh/archive/2008/12/24/70253.html#Feedback</comments><slash:comments>2</slash:comments><wfw:commentRss>http://www.cppblog.com/vczh/comments/commentRss/70253.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/vczh/services/trackbacks/70253.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: 当我们的程序需要运行在命令行环境下的时候，分解复杂的命令行参数往往成为一件不难但又麻烦的事情。我们经常发现.net的开发工具的命令行格式都是"/parameterA:valueA /parameterB:valueB"。如果我们希望使用这种格式的命令行参数的话，如何分析就成为我们需要解决的一个问题。&nbsp;&nbsp;<a href='http://www.cppblog.com/vczh/archive/2008/12/24/70253.html'>阅读全文</a><img src ="http://www.cppblog.com/vczh/aggbug/70253.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/vczh/" target="_blank">陈梓瀚(vczh)</a> 2008-12-24 17:13 <a href="http://www.cppblog.com/vczh/archive/2008/12/24/70253.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Vczh Serialization Demo ：在网络上传递复杂对象</title><link>http://www.cppblog.com/vczh/archive/2008/11/22/67612.html</link><dc:creator>陈梓瀚(vczh)</dc:creator><author>陈梓瀚(vczh)</author><pubDate>Sat, 22 Nov 2008 10:26:00 GMT</pubDate><guid>http://www.cppblog.com/vczh/archive/2008/11/22/67612.html</guid><wfw:comment>http://www.cppblog.com/vczh/comments/67612.html</wfw:comment><comments>http://www.cppblog.com/vczh/archive/2008/11/22/67612.html#Feedback</comments><slash:comments>6</slash:comments><wfw:commentRss>http://www.cppblog.com/vczh/comments/commentRss/67612.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/vczh/services/trackbacks/67612.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: 今天将Serialization进行了重构，让其支持容器。于是使用以前的基础设施就能完成这个Demo了。代码如下：&nbsp;&nbsp;<a href='http://www.cppblog.com/vczh/archive/2008/11/22/67612.html'>阅读全文</a><img src ="http://www.cppblog.com/vczh/aggbug/67612.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/vczh/" target="_blank">陈梓瀚(vczh)</a> 2008-11-22 18:26 <a href="http://www.cppblog.com/vczh/archive/2008/11/22/67612.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>