RSS 2.0 Feed
2004-06 Entries
摘要:假设只接受public/private两个修饰符,并且只接受void/bool/int三个返回类型,接受字段、属性和函数。函数不允许有参数,属性有get/set(为简单起见,先get后set),字段只能是bool/int两个类型(不允许初始化)。函数和属性的内容为空,只有一对花括号。那是不是应该这么写呢? (?(?public|private)\s+(?void|bool|int)\s+(?\w+)\s*\(\s*\){\s*})|(?(?public|private)\s+(?void|bool|int)\s+(?\w+)\s*{\s*get\s*{\s*}\s*set\s*{\s*}\s*})|(?(?public|private)\s+(?bool|int)\s+(?\w+)\s*;)这么写是对的,但是效率方面却不是很好。对于这个简单的情况可能还是体现不出来,但是如果更加复杂的话,可能就会出现效率低下的问题了。我们应该怎么怎么改呢?(?(?public|private)\s+(?>(?void)\s+(?\w+)\s*(?>(?\(\s*\)\s*{\s*})|(?{\s*get\s*{\s*}\s*set\s*{\s*}\s*}))|(?bool|int)\s+(?\w+)\s*(?>(?\(\s*\)\s*{\s*})|(?{\s*get\s*{\s*}\s*set\s*{\s*}\s*})|(?;)))) 这种写法跟上面那一种写法有什么不一样呢?对于public int aaaaaaaaaaaaaa;这一个句子,前面的正则表达式需要尝试function这一组的匹配,在分号位置匹配失败之后退到匹配的开始位置。然后再尝试匹配property这一组,在同一个位置匹配失败,然后回溯到起点,最后才成功匹配variable这一组。换句话说,对于这句话来说,几乎每一个字符都被比较和匹配了三次。如果问题更复杂一点,这样的写法可能就会存在多个地方需要回溯匹配,效率就更低下了。而后面一种写法,在每个地方都是确定的,如果不能够匹配的话,整个匹配就必然失败,而完全不需要回溯。注意上面所有A|B的关系的地方都会用(?>A|B)的形式来指定,如果匹配成功了,就不会再回溯到原来的地方尝试另外一种可能。在正则表达式里面,总是会尝试最大匹配,如果不使用(?>A|B)的形式的话。这是什么意思呢?留给大家思考吧。...[阅读全文]

posted @ | Feedback (2) | Filed Under [ .NET 技术内幕 其他 ]

摘要:就是那个“正则表达式构造器”啦! 下载点这里注意:1、这个是Alpha版(我有好多东西都只有Alpha版本),因此不保证完全正确,只能够说能够用。2、因此请记住多一点保存,否则有可能因为一个Exception导致程序终止,那你的工作就完全丢失了!3、私人物品,保留版权。4、免费&自由使用,但请您独立承担因此带来的后果。我不提供任何担保。使用说明:1、打开和保存都在第四个页面里面(很丑吧,这个是我随便乱拖乱放弄出来的),请尽量选择使用文本方式保存。(上面的文件菜单不要管他,没有用的。)2、编辑在第一个页面里面。3、编辑的时候,如果选中了一项已经存在的项目,则表示修改该项内容,包括修改名称或者内容。4、如果是新建的话,请点击列表里面的“点击此处添加”。5、连续添加模式指,点击“添加”按钮(或者回车)后自动清空内容,继续新建。6、输入的格式如下:  a.  name:context      表示用name作为名字表示context这个内容  b. 其中name不接受*?+<>{}()/等字符  c. 如果name的开头有"_"(下划线),则表示这个定义不需要被特定的组捕获。否则表示用该名字命名的组捕获。eg:      <_name>:a      <name>:b      前者表示遇到<_name>的时候替换成a,后者表示遇到<name>的时候替换成(?<name>b)  d. context的格式是在标准的正则表达式的基础上进行扩展的,因此普通的正则表达式这里是接受的,扩展部分请看下一条。  e. 在context里面可以用<name>来表示一个已经定义的内容(如果尚未定义,则会显示成红色的字符,否则应该是蓝色的),后面可以直接跟+*?{n,m}等表示重复的项目。因此context里面"<"(左尖括号,小于号)需要用\来转义。  f. 某些时候可能会在编辑的时候出现死机的状态,这个是因为我所使用的语法分析正则表达式有问题。(所以多多保存啊!不然就会死得很难看哦。)解决的办法是:用notepad书写完整之后,拷贝上去,一般都不会有问题的。7、编辑框下面的那个文本框是一个自动展开窗口,你可以通过这里预览整个表达式的情况,或者拷贝粘贴来进行测试。注意:不要拷贝前面的“name:”,这个不是正则表达式的内容。8、测试在第二个页面,NFA实际上就是正则表达式啦,测试字符就是指输入文字,测试结果表示的是匹配情况。9、你可以去掉View Group、View Capture的勾,如果你不想显示哪些组的捕获情况,或者不想知道具体捕获情况。10、捕获组一般都会显示你所定义的组的名称。11、在第三个页面里面是目前的树状图,每一个节点下面的是当前节点里面所包含的非终结符。选中某一个节点就会在右边显示定义,及其展开情况。在这里可以很容易的掌握你的这个正则表达式的具体情况。这里也可以拷贝某一个节点的展开情况,进行局部的分析。例如:你发现_Root这个正则表达式无法匹配,可以看看下面的每一个非终结符是否能够正确进行部分匹配,这样很容易就能够找出问题所在。12、为了让你更加了解整个的使用方式,特意给你附带了两个非常实际的例子。rxSearch2是这个软件本身的语法分析器的正则表达式(至少语法加亮就用到这个,还有一些其它的用到的地方)。PDA加速测试就是我那个PDA源代码级加速器的分析器的正则表达式。你可以看看树形结构,就知道看起来会比较容易懂,而且设计起来也比较轻松。事实上,这个软件只是一个原形,为了能够分析那个我定义的扩展正则表达式的格式,需要一个构造分析字符串的东西,有点像用编译器编译这个编译器的源代码的感觉。虽然破旧,但是我可是爱不释手,用来构造了好些东西。很多稍微简单点的正则表达式,只需要十分钟就能够做得非常好了。(我是指100%正确)...[阅读全文]

posted @ | Feedback (4) | Filed Under [ .NET 技术内幕 ]

摘要:什么是正则表达式呢?正则表达式实际上是一个主要用来描述字符串匹配的工具,当然也可以用来匹配其它的东西例如二进制数据,用在字符串方面可能是最常见的。说到这里,可能大家会联想到如下几个主题:怎么排除恶意的脚本代码?要写一个脚本语言引擎或者编译器,是否可以用正则表达式来完成?编译原理?实际上要说清楚这里面的所有问题也许已经超出了我的能力范围了,但是我还是要写下来,再不写下来也许哪天我就真的忘得一干二净了。首先说正则表达式吧。正则表达式实际上使用的是一个不确定有穷自动机,Non-deterministic finite automaton,简称NFA。而编译原理一般使用的是确定有穷自动机(一般就叫有穷自动机,或者有限自动机,其他的说法还包括确定的有限状态自动机),Deterministic finite automaton,简称DFA。关于这几个概念的简单解释,随便Goo一下有穷自动机就能够找到一大堆,这里推荐一个。NFA和DFA有什么区别呢?最简单的区别就是NFA对同一个字符串输入完全有可能有多种理解方式,而DFA则只有唯一的一种理解方式。从这里我们可以简单的理解到NFA所能够接受的规则并不一定都能够转化为DFA所理解的规则。事实上区别在NFA和DFA的分析过程当中就能够体现出来了,对于NFA来说,很可能需要探测某一种接受方式,当出现不接受的时候就可能需要退出一层,尝试另外一种可能的接受方式。而对于DFA则不一样,因为只可能有一种确定的理解方式,因此一旦不匹配,就不需要再做别的尝试,而可以直截了当的说“不匹配”。因此反过来说,由于DFA只有一种理解方式,所以效率明显应该比NFA要高。在上面的推荐的地方给出了更为简洁的说法:NFA和DFA的唯一区别就在于状态转移函数不一样。编译器进行词法分析时所用到的分析公式实际上看起来应该跟正则表达式很相似,那么这个表达式应该是一个什么样的样子呢?实际上我们都很熟悉这样的东西:S -> AA -> aA|BB -> b上面则三条式子就是状态转移函数了,或者你可以理解成推导公式。里面的SABab就是所有有可能的状态集合,其中SAB三个是非终结符,ab两个是终结符,S是初始状态。对于上面这样的推导公式,可以知道这个系统只接受两种输入:a...a或者a...ab。为什么这里要引入A、B这样的非终结符呢?因为很多时候我们要描述这样一个系统的时候,会经常地遇到一些重复的可定义的部分,就比如一个到多个空格\s+,我们完全可以把这些东西直接写出来,甚至直接用一条式子表达出来,但是这样做就会非常的麻烦和困难。为了简便,很多时候都会将一些重复的,或者比较冗长的,或者比较重要的部分归纳成一个非终结符。非终结符的定义也不是随随便便的,而是有一定的规则的,这个规则这里就不讨论了。对于编译器来说,主要有两种构造DFA的方式,一种是LL分析方法,另外一种是LR分析方法。这两种都是基于输入预测的分析方法,但是分析的方法却大有不同,LL属于推导的方式,LR则属于归纳的方式。如果这么说不好记,那么就记住LL指的是从左向右输入,从左向右推导;LR是从左向右输入,从右向左归纳。LL比较符合人的思维方式,但是却有一些局限性。LR则比较难理解,但是适用范围却比LL要广泛一点,效率也高一点。说了半天好像跟正则表达式搭不上边,其实那些使一些准备知识,下面来仔细的说正则表达式。大家看上面我给的那三条推导公式,实际上如果全部用终结符来表示,那么应该是:a*(a|b)这就是正则表达式了。因为给整个机器定义一大堆的规则和状态转移函数是比较复杂和不必要的,对于一般的字符串匹配来说,因此给正则表达式的执行机构提供一个简单的、完全用终结符描述出来的匹配字符串就足够了。但是这并不代表着我们也应该直接用终结符来构造整个的正则表达式,这样做会非常复杂和痛苦的,因为一个稍微复杂一点的匹配,正则表达式就会复杂到让你看不明白,或者看的头痛。光是括号的配对就足够让你忙上半天的了,还有转义符等其他的东西呢。因此,我们完全有必要像上面说的那样,定义出非终结符,高效率的创造正则表达式的关键就在于此。然而很可惜的是正则表达式本身并没有这样的定义,而.NET也没有给我们提供这样的定义接口。更可惜的是,绝大部分在那里制作正则表达式创建工具的人,根本就没有想到这一点。拿上一次我提到的Regulator来说,有Analyzer把整个的表达式翻译成英语,有Snippet提供编写的便利性,还有很好的文本编辑器,不过仅此而已。Analyzer对于看懂一个正则表达式也许是有帮助的,但是它对于你想自己构造一个正则表达式没有什么帮助。Snippet是仿照C#2.0里面的先进东西,然而构造正则表达式不是写程序,写几个括号尖括号等等并不是最影响效率的问题,同时正则表达式也没有什么Pattern的问题,因此实际上Snippet对于构造一个正则表达式也没有什么帮助。而那个很完美的文本编辑器更是没有跳出用终结符来构造的框框,也不会对提高效率有什么帮助。大家可以试一下用我的正则表达式构造器就知道我所提出的概念是什么意思了。 那么构造正则表达式需要注意一些什么呢?虽然说NFA因为不确定,所以限制比DFA要少,构造起来也比较方便一点。但是不好的构造方式会引起效率低下的问题,因此要尽量:1、避免不确定的情况发生。解决的办法是尽可能使用(?>A|B)来代替A|B或者(?:A|B)这样的形式。(A、B代表一个很长的正则表达式字符串)2、尽量避免递归层次过高的情况。eg:a*(b*(c|d)|b*(e|f)|b*(g|h))|a*(b*(i*(j|k)|i*(l|m)|i*(n|o))这样对于一个a...b...i...o 这样的字符串将会是一件非常痛苦的事情,因为匹配的过程将会经历:a*b*c -> a*b* ->a*b*d -> a* -> a*b*e -> a*b* -> a*b*f -> a* -> a*b*g ->... 这样一系列的分析->回溯的过程,才能够到达a*b*i*o这个匹配。上述表达式最好能够改造成:a*b*(c|d|e|f|g|h|i*(j|k|l|m|n|o)) 这种形式。如果因为需要通过“组”来归类,那么建议你修改你的“语言格式”,或者尽量减少分组的情况,或者通过匹配之后再用程序代码来确定实际的分类情况。3、如果可以的话,比如你要处理的问题比较复杂,就进行简单的分步处理,以减少表达式的复杂度。表达式越简单,就越有可能效率高,而设计失误的可能性也比较少。分布也不要太多,毕竟一次分析也是需要花费时间的。...[阅读全文]

posted @ | Feedback (24) | Filed Under [ .NET 技术内幕 ]

摘要:最近不知道各位有没有到MS的网站上面看,在VS的页面里面有一个.NET的十个必用工具的Post。这个Post里面介绍的好几个工具都确实非常有用,比如说Reflector、NUnit、Regulator等等。说起来真巧,这里面介绍的Reflector最近就被我检查出一个Bug,这个Bug在查看Microsoft.WindowsCE.Forms.dll里面的_SIPWnProc函数,翻译出来的C#代码竟然是:internal PAL_ERROR _SIPWnProc(IntPtr hwnThis, WM wm, int wParam, int lParam){     if (this.EnabledChanged == null)     {           return PAL_ERROR.Handled;     }     this.EnabledChanged.Invoke(null, EventArgs.Empty);}竟然有可能没有返回值,这个Bug出现在4.0.7.0的版本。在反馈的email发出之后,作者跟我联系了,说最新的4.0.7.0版本没有这个问题。我但是不知道我用的已经是4.0.7.0了,还say sorry了。然后我立刻就发现这个问题,马上又给他发了一封信,后来没过多久就出现了4.0.8.0的版本了。其实有时候还是直接用ILDasm比较让人放心,至少不会有很多逻辑性的错误。(我还试过用Reflector看Terrarium的程序的时候崩溃掉了,看的是一个import dll的函数,也不知道目前是否有修正。)另外一个Regulator就更让我觉得还是自己写东西比较安全。虽然说这个东西好像挺好的,但是实际上我还是觉得他没有脱离写正则表达式的繁琐和重复性的工作。而让我感到更加吃惊的是,我的一个工作得非常好的正则表达式,竟然在他那里出错了,不知道为什么。这个正则表达式就是我上一次提供的.NET CF程序源代码级优化器的第一阶段的表达式,主要就是去除代码里面的注解、字符串等可能会引起错误理解的部分。这个表达式如下:(?>/(?>/(?:[^\n])*|\*(?>\*(?![^/])|[^*])*\*/)|(?=[@""'])(?>@(?>\s|/(?>/(?:[^\n])*|\*(?>\*(?![^/])|[^*])*\*/))*""(?>""""|[^""])*""|""(?>\\.|[^""])*""|'(?>\\.|[^'])*')|(?(?:(?>\\.|[^@""'/#]|/(?![*/])))+)|(?>(?#endif(?>\s)*//(?>\s)*NACC|#if(?>\s)+ACC(?>\s)+(?>/(?>/(?:[^\n])*|\*(?>\*(?![^/])|[^*])*\*/)|(?=[@""'])(?>@(?>\s|/(?>/(?:[^\n])*|\*(?>\*(?![^/])|[^*])*\*/))*""(?>""""|[^""])*""|""(?>\\.|[^""])*""|'(?>\\.|[^'])*')|[^#]|#(?!else(?>\s)*//NACC))+#else(?>\s)*//(?>\s)*NACC)|(?#)))+……很夸张吧?我不知道像这种级别的东西如果用Regulator怎么才能够设计出来,反正我用的是另外一种技术来做的,这个技术以及我所用的软件我下一次再介绍。大家可以试一下,这个表达式在Regulator里面会出现什么样的情况?输入可以是任何C#源代码,输出里面的Catchable组里面的所有内容拼起来之后就是已经去掉了注释和字符串的内容(还去掉了#if ACC到#else //NACC之间的内容)。可是在Regulator里面却出错了,出错的信息说")"这个符号不足,我也不知道这个问题到底是我用错了呢,还是Regulator有问题。关于这个问题,我也给Regulator的support去信了,给了一个FeedBack,不知道是否在最新的版本里面有所修正呢?我所使用的是2.0.3.0的版本。写到这里,我就觉得正则表达式的构造还是用我自己写的那个软件比较放心,而且能够比较轻松的构造出比较复杂的内容。上面的那个还不是最长的,至少加速器里面的第二步骤所用到的正则表达式就有783个字符(上面那一个有516个字符)。像这么复杂的东西,如果要用Regulator这样的工具去创造(注意是创造,而不是有人指点你怎么去做),真是不可想象的一件事情。不知道有没有人想知道我的那个正则表达式的编写器有什么“优点”呢?这个东西和上面的The Regulator相比较有什么最大的区别的?这个就恕我留下来卖个关子了。...[阅读全文]

posted @ | Feedback (4) | Filed Under [ .NET 技术内幕 ]

摘要:最近在某处装了一个Whidbey的CTP May版本(应该是May吧?还是March?忘了),因此兴冲冲去玩了一把。稍微熟悉了一下环境,然后就开始试用那些新的功能了。比如说Generics……不过没有我想象当中的兴奋,因为这些东西基本上看过演示和介绍,都知道运行后大概会有什么结果,所以我连运行一下的兴趣都没有。倒是一些从来没有介绍过的功能比较吸引我的注意力,比如说Performance的测试器(相当于Profiler),还有自动生成测试代码。前者的功能有点像以前介绍过的一个Profiler,不过功能甚至没有原来那个强。至于后者,以前听说过,不过没有解图也没有详细的讲解,所以还是挺吸引人的,于是我就迫不及待的想尝试这个功能。用什么来尝试好呢?恩,就用.Game Framework的代码来测一下吧,光测一下DemoCharacter这个类好了。于是右键点击生成测试,结果发现把整个Test目录下面的所有东西都选中了。我想,难道VS2k5这么强,认为一般应该测试整个目录的代码?好吧,那就妥协一下,就测试Test目录下面的吧。点击了Finish还是OK,乖乖,等了半天没见反应,除了CPU几乎全满之外。心想应该在生成代码,确实比较占用资源。那我就去吧,完了还没好,再看看报纸吧,报纸全看完了还没有好……-_-b 等了半天终于好了,一看,原来就是自动套用模版生成NUnit的测试代码啊……我想好像我的那个优化工具似乎也用不了这么长的时间啊,实际上有好几个需要进行代码分析地方速度都会有点慢,是不是代码分析器的分析速度的问题呢?由于速度实在太慢了,而且测试的范围太庞大了,我已经没有心思手动写expected应该是什么的代码了,放弃。可是我还是实在不甘心啊,怎么能不试一下这个功能呢?于是还是想测试一下DemoCharacter这个类,结果发现没有这个类——我忘了abstract的类是不能够测试的,可是如果我非要测试其中一些功能怎么办?百思不得其解,难道非要通过派生类测试?那如果我有一个abstract的BaseX派生出ChildA、ChildB、……、ChildZ,Children.Calc()根据BaseX.Calc()的计算结果来进行不同的计算,那么怎么测试BaseX.Calc()是否正确呢?难道要派生一个BlankChild来测试?那如果这样的问题比较多是不是太不好玩了,也许我的想法太特殊了。OK,回归正题。原来的.Game Framework的代码在VS2k3的时候好好的,没有warning,跑到Whidbey下面却出现了一大堆的warning。仔细一看,原来说的是Thread.Suspend和Resume已经obsoleted,建议使用Threading下面的其他东西来进行同步,例如Monitor、ManualResetEvent等等来进行线程间的同步。Oops! 这可怎么好?我还是比较讨厌有warning的,先看看这个到底是怎么回事,为什么要废弃这种用法呢?查了一下Whidbey的MSDN,原来说因为suspend的时候无法控制代码在什么地方被停止,也许正在等待一个SecurityPermission,或者在等待某个资源,或者在……,因此很容易引起死锁。打个比方吧:线程A已经占用了资源1,正在wait资源2,但是资源2被线程B占用了。线程B占用着资源2的同时,也在等待资源1,但是等着等着不耐烦了,他决定做点其他的事情,比如说把线程A挂起来,然后继续等待资源1。本来A等得不耐烦的时候就会释放资源1,可是这样的事情就会被B给彻底阻止了。但是我的问题似乎还比较不好解决,如果不使用Suspend/Resume的话。因为我正好需要“无论运行到哪里,我都要他暂停”这样的能力。我的问题是什么一个问题呢:有一个循环需要处理一些任务,但是这个任务必须要能够限制一轮的执行时间在比如50ms内完成,如果不能够完成也必须让位给后面的工作了。一开始我是打算用Thread.Join(timeout) + Thread.Abort() 的方式来进行的,但是考虑到这样需要不停的产生线程和销毁(甚至终止)线程,开销有点大。此外Abort除了引发Exception占用更多的开销之外,还可能引发不可控制的后果,比如某个资源正在初始化到一半的时候就终止了。因此最终的修改方案是通过AutoResetEvent.WaitOne(timeout) * 2 + Thread.Suspend() + Thread.Resume来完成的。大致的代码如下:AutoResetEvent evMain = new ...(false);AutoResetEvent evLoop = new ...(false);Thread loopThread = new ...(new ThreadStart(Loop));// 该函数会被外部的一个大循环不断的调用void MainLoop(){   if (loopThread.ThreadState == ThreadState.Unstarted)   {      loopThread.Start(); // 开始循环线程   }   else   {      loopThread.Resume(); // 唤醒上一次被挂起的线程      evLoop.Set(); // 允许进行下一轮的处理(无论上一次是否因为超时被提前暂停)   }   evMain.WaitOne(timeout); // 等待处理,直到完成该轮循环或者超时   loopThread.Suspend(); // 挂起循环线程,无论他是否已经完成当前的处理了。}// 需要限制时间的任务void Loop(){   for (i = 0; i < c; i++)   {      call_the_process[i](); // 外部的、可能是其他人写的代码。   }   evMain.Set(); // 当前循环已经完成,允许主线程继续下面的代码。   evLoop.WaitOne(); // 等待下一次循环的机会}对于这样的一个情况,有没有可能不用suspend呢?需要指出的是,我没有办法控制任何一个call_the_process[i]所可能花费的时间,完全有可能某一个调用需要花费1000ms,因此不太可能在每一次循环的时候检查状态。另外,对于这样的线程间同步的问题(实际上应该说是限制某个循环任务的执行时间的问题),是否有一个比较好的Pattern呢?上面这个是我凭空想出来的,目前工作还算良好。不过到了whidbey下面有warning实在是太难看了,能够有更好的方法解决自然最好,否则怎么说也得要禁止这个warning的显示才行。诸位有什么好建议呢?...[阅读全文]

posted @ | Feedback (8) | Filed Under [ .NET 技术内幕 游戏 ]

摘要:如果你正在用.NET CF 进行开发,如果你正好觉得你的程序的启动速度非常得慢,如果你愿意对你的源代码进行一定的优化,那么可以看一下我关于这方面的经验:详细内容请点击这里阅读……或者跳过我的内容,直接试一下我的源代码级优化器。下载位置:.NET CF开发的源代码级优化器(C#)(Alpha) 注意:1、该优化器仅仅对.NET CF的程序有效,请不要试图用于.NET Framework下面,一定没有效果。2、该优化器进保证对.NET CF v1.0 SP1以前(包括SP1)的系统有优化效果,对于SP2无法保证(估计效果不会明显)。3、该优化器属于私人物品,保留版权,但可以放心自由使用(包括你可以免费在发布)。4、由于这个是Alpha版本,并且免费自由使用,因此不做任何的担保。请在使用前进行数据备份(虽然我自己从来不这么做),避免数据丢失和错误。5、基于上面一点的理由,请不要进行恶意操作,那肯定会出现问题的。比如没有进行优化处理就保存数据。6、某些功能可能没有实现,或者效果不正确,请记住这是Alpha版本。(后续版本不一定会有,因为.NET CF 2.0不一定有这样的问题)...[阅读全文]

posted @ | Feedback (1) | Filed Under [ .NET CF ]

摘要:有多少人对.NET CF开发感兴趣的呢?是否已经遇到一些莫名其妙的问题了呢?如果是的话,那就请看这一篇。这一篇的Artical主要讲了.NET CF里面会出现的一些Bug,以及避免的办法。也许内容不全,请见谅。点击这里看详细内容…… (该系列的第一部分请参见 在PPC上面用.NET CF开发应用需要注意些什么——(一)CPU、OS以及CLR差别简述 )...[阅读全文]

posted @ | Feedback (2) | Filed Under [ .NET CF ]