在 .NET Micro Framework 中进行字符串操作
一篇关于 .NET Micro Framework 中字符串操作的文章。
序言
那是 2011 年 8 月 18 日。这篇文章是 http://blogs.msdn.com/b/netmfteam/archive/2011/08/18/netmf-4-2-regular-expressions.aspx。
.NET Micro Framework 的正则表达式实现刚刚被公开提及。社区承受着找到 Bug 的压力,果然,在一段时间后,一些开发者在使用该库时发现了一个 Bug,但不在 RegEx 引擎中,而是在 StringBuilder(也由我贡献)中 http://forums.netduino.com/index.php?/topic/2717-string-manipulation-in-net-41-micro/。
在不到 24 小时内迅速修复了该问题后,我立即测试了其他支持的表达式,甚至利用了我新的周期计时功能,以确保我没有在非托管内存中漫游并意外创建访问冲突。
本文的主要目的是确定缺失的方法是否对使用该库的开发人员有影响,以及是否基于本文进一步开发是值得的。
后一个目的是确保差异和相似之处得到适当的突出显示,并以一种使现有 Regex 用户易于理解和使用的方式解释周期计时功能,最后但同样重要的是,通过时间本身提供一些笑料。。
引言
向正则表达式语法的开发者以及 Unix 操作系统发明者,更不用说凭籍创造了驱动它的语言的创造者,我将说,正则表达式只不过是一个神话般的黑盒子。
我非常自豪地学习了自动机理论是如何形成和实现的。当我得知有两种截然不同的可能表示形式,它们反映了我所设想的“有偏见”和“无偏见”的确定性有限自动机(DFA)和非确定性有限自动机(NFA)时,我充满了喜悦之情。当我得知您可以相互转换它们,并且实际上没有区别,仅仅是性能或完成确定所需的指令时,这种感觉更棒了。
我怀着极大的悲伤和……或者“谋杀意图”得知,通过研究时间的历史,这两种思想流派从未统一,并且无休止地斗争,就像物理学家们所做的那样,这激发了他们的一些推理。差异的疾病永远根植于程序员的头脑中,其症状是每个人都会天真地利用 DFA 最小化或其他等价原理,直到他们醒悟过来,意识到问题可以而且应该通过将编译和演进的特性结合到 RegExNext 或类似的东西中来解决。即使大多数人意识到这一点,他们反而会在他们的语言实现中实现各种解释器,并试图通过阴谋来迫使摩尔定律的失败,并允许高容量硬盘的概念得以实现。虽然最初是成功的,但这一阴谋被一场洪水和更新的固态技术所挫败,后者实际上通过增加成本并确保固态技术拥有研究所需的资金,从而使该活动受益。
在不暴露我的 CIA 接线员以及我确认这些各种操作的方法的情况下,我知道我的故事可能看起来非常可疑,但是,我可以告诉你的是,我敦促你研究一下爱因斯坦,以及他是如何恰巧想逃往美国,以及他之后是如何提出广义相对论的。
我还想说,恰当的常识、良好的设计和优化总是能(绝对地/毋庸置疑地)胜过正则表达式引擎。
现在,说完了这些,我将进入文章的重点,我希望这是你们来这里要找的,而不是我那些可能只有一点点关联,甚至很可能不属实的胡言乱语。
如果您关心小猫,请积极发表评论。
文章
用户通常将正则表达式用作一种方法,来利用看似捷径的方法处理一些看似复杂的字符串处理任务。但在我们开始讨论如何使用该库以及它与完整框架实现有何不同之前,我将请大家花点时间认识到正则表达式的其他可能用途。
开发人员实际上可以使用正则表达式对二进制数据做相当多的工作。一旦你了解了一些关于字符串编码的知识,并且接触到更复杂的正则表达式(以下简称 Regex(es)),或者如果你已经这样做了,那么你可以跳到主要观点和示例。
你可以使用 Regex 来匹配代码分析中的逻辑部分,以及使用它们从另一个图像中获取相关或相似的部分,以及字符串匹配技术的许多其他变体,因为 char 就是 byte,反之亦然,没有损失,甚至没有进行转换。这更像是一个 typedef
或一个别名,它为开发人员提供了这些结构。
现在,你可能比之前更加困惑,或者可能变得开明,甚至产生争论,我请求我们暂时放下这个话题,继续讨论主要观点。
新的计时机制,也称为 RegexOptions.Timed
,用于确保匹配操作不会花费超过以 tick 为单位定义的给定时间。
这通过比较自初始执行以来的给定 tick 数来轻松实现,如下面的代码所示。
//If we are keeping time and the time we started matching at is more then MaxTick
//ticks ago something went wrong allow for a break - JRF
if (timed && (DateTime.Now.Ticks - startTicks) >= MaxTick)
throw new RegexExecutionTimeException(idxStart);
else startTicks = DateTime.Now.Ticks;
它确保包含可能的无限结果的表达式,例如 '\w+' 或 '\b+',能够安全执行,并且系统不会无谓地浪费时间进行匹配。
我还考虑了添加一个 Regex.Lazy
标志的可能性,该标志将选择性地睡眠给定的 tick 数,然后可能增加分配的 tick 数直到异常。
RegexExecutionTimeException
类在匹配堆栈中占位,然后允许捕获者通过提供最后评估的索引并保持堆栈寄存器和类状态在异常发生之前的状态,来重新启动匹配过程,或者稍后重新开始。下面是一个无意义的例子:
int catchCount = 0;
//Should match for a long time and throw the Timing Excpetion
//This could be changed up a bit to show partial matching
string pattern = @"\b(?:(\w+))\s+(\1)\b+";
Regex rx = new Regex(pattern, RegexOptions.IgnoreCase |
RegexOptions.Timed);
rx.MaxTick = ushort.MaxValue * 2;
// Define a test string.
string text = "The the quick brown fox fox jumped over the lazy dog dog.";
int start = 0;
TryToMatch:
try
{
// Find matches.
MatchCollection matches = rx.Matches(text, start);
//Log.Comment("Found Matches:" + matches.Count);
//If we get here you have a really fast processor and timing is not working as expected
return MFTestResults.Fail;
}
catch (RegexExecutionTimeException ex)
{
if (++catchCount > 5)
return MFTestResults.Pass;
else
{
//Try to match again at the index
//Could check index or access the Groups
//up the Index here but in this example we never matched.
start = ex.Index;
goto TryToMatch;
}
}
catch
{
return;
}
用户可以通过对具有已知结果的字符串执行匹配来找到 MaxTick
值的更好值,以获得一个可以应用于算法(或缺乏算法)的统计数据,以确保更严格的操作。默认值为 long.MaxValue / 2
。
我曾想添加一个内部的 Profile
方法,但我未能获得关于实现的反馈,更不用说我不想往一个看似快乐的理论和科学中添加更多的废话,而这些理论和科学在没有付出巨大的尊严和骄傲的代价的情况下是不接受改变的,在我看来,在目前写作的时候,这显然是最糟糕的哲学罪过。
我不确定其他实现是否有此功能,更不用说人类是否也表达过类似我在本文中列出的担忧或想法,我也不确定这是否是与本文相关的方面...
从下面的测试示例中,您会看到,与完整框架相比,C# 编译器视角下的语法尽可能地保持一致,并且对它或 StringBuilder
类都没有应用任何偏离。
//MSDN Test from http://msdn.microsoft.com/en-us/library/system.text.regularexpressions.match.aspx
string input = "int[] values = { 1, 2, 3 };\n" +
"for (int ctr = values.GetLowerBound(1); ctr <= values.GetUpperBound(1); ctr++)\n" +
"{\n" + " Console.Write(values[ctr]);\n" + " if (ctr < values.GetUpperBound(1))\n" +
" Console.Write(\", \");\n" + "}\n" + "Console.WriteLine();\n";
string pattern = "Console.Write(Line)?";
Match match = Regex.Match(input, pattern);
int matchCount = 0;
string expected0 = null;
string expected1 = null;
while (match.Success)
{
Log.Comment("'" + match.Value + "' found in the source code at position " + match.Index + ".");
if (matchCount == 0) expected0 = match.ToString();
else if (matchCount == 2) expected1 = match.ToString();
match = match.NextMatch();
++matchCount;
}
return matchCount == 3 && expected0 == "Console.Write" &&
expected1 == "Console.WriteLine" ? MFTestResults.Pass : MFTestResults.Fail;
您还将看到 MatchCollection
是存在的。
// Define a regular expression for repeated words.
Regex rx = new Regex(@"\b(?<word>\w+)\s+(\k<word>)\b",
RegexOptions.Compiled | RegexOptions.IgnoreCase);
// Define a test string.
string text = "The the quick brown fox fox jumped over the lazy dog dog.";
// Find matches.
MatchCollection matches = rx.Matches(text);
// Report the number of matches found.
Log.Comment(matches.Count + " matches found in:\n " + text);
// Report on each match.
foreach (Match match in matches)
{
GroupCollection groups = match.Groups;
Log.Comment("'" + groups["word"].Value + "'" +
" repeated at positions " + groups[0].Index + " and " + groups[1].Index);
}
//Required Named Capture
return MFTestResults.Pass;
}
catch
{
//Known to Fail until Named Capture is implemented
//See https://regexper.cn/named.html
return MFTestResults.KnownFailure;
}
现在,我们将开始探讨与代码被移植的 Jakarta 库以及完整框架实现之间的差异。
// Define a regular expression for repeated words.
//Was trying to add ?: to the second group to force its presence
//atleast once but you cant use back references when you do this with this engine,
//there must be another way!
//Has correct Index's but extra matches!!!
//@"\b(?:\w+)\s+(\1)\b"
//pattern = @"\b(?:\w+)\s+((\s+(\1))\b)";
////Only 1 Match
//pattern = @"\b(\w+)\s+\w+(\k\1)\b";
//Almost perfect
//pattern = @"\b(?:\w+)\s+(\w+)\1\b";
//Correct Positions
//pattern = @"\b(?:\w+)\s+(\w+\1)\b";
//Correct Positions extra matches
//pattern = @"\b(\w+)\b"
//pattern = @"\b\w+\s+\b(\1)"
//These both work in Jakarta Applet... check for bugs
//http://jakarta.apache.org/regexp/applet.html
string pattern = @"\b(?:(\w+))\s+(\1)\b";
//string pattern = @"\b(\w+)\s+\1\b";
Regex rx = new Regex(pattern, RegexOptions.IgnoreCase);
// Define a test string.
string text = "The the quick brown fox fox jumped over the lazy dog dog.";
//Need dumpProgram to determine if my compiler compiled the same... I
//think the problem lies in the Match object... I am not passing something
//correclty to the concstructor....
// Find matches.
MatchCollection matches = rx.Matches(text);
TestTestsHelper.ShowParens(ref rx);
// Report the number of matches found.
Log.Comment(matches.Count + " matches found in:\n " + text);
// Report on each match.
foreach (Match match in matches)
{
GroupCollection groups = match.Groups;
Log.Comment("'" + groups[0].Value + "'" + " repeated at positions " +
groups[0].Index + " and " + groups[1].Index);
}
//May be bugs in engine but is likely due to differences in Regex
//engines because I have no implemented Transfer Caputre,
//this is known to Fail for now so long as no exception is thrown.
return MFTestResults.KnownFailure;
}
catch
{
return MFTestResults.Fail;
}
从上面的代码示例和注释中,您会看到,对引擎的更改会产生一些与任一其他实现相比可能不会立即预期的结果。这是 Regex 实现中的一个经典问题,需要非常小心,以确保尽可能紧密地遵循理论,以确保兼容性。
总的来说,这是最困难的部分,因为 Micro Framework 中的 UTF-8 编码以及完整框架中的 Unicode 的概念。我甚至因为这个原因而考虑使用 short 而不是 char 来表示操作码,但事实证明,在没有如此剧烈的改变的情况下,最终是可以的。
底线是,结果与 Full Framework 在绝大多数情况下使用支持的语法时返回的结果一致。
例如,命名捕获不受支持,但要添加到当前实现中很容易,因为成员已经存在,引擎只需要进行一些小的更改即可使其正常工作。(这可能会成为此系列中的另一篇文章,取决于反馈)同样,其他各种语法部分也可以并且很可能被添加,这取决于反馈和社区讨论的融合以及我的工作量如何处理。
该实现还维护一个最近编译的正则表达式缓存(就像完整框架实现一样)。通过将静态成员 Regex.CacheSize 设置为 -1,可以清除缓存,该设置会清除缓存并阻止新编译的表达式被添加。默认缓存大小为 25 个实例,届时它们将从缓存中以 FILO(先进后出)的方式进行回收。
缺失或不同的内容?
在 4.2 实现中,Regex 类缺少一些成员和方法,例如操作 Capture 的那些。我将进一步阐述,您将有望决定这是否影响开发……
例如,以先前所述的 UTF-8 与 Unicode 字符问题和以下代码示例为例。
/*
// The example displays the following output:
// : Microsoft
// : Excel
// : Access
// : Outlook
// : PowerPoint
// : Silverlight
// Found 6 trademarks or registered trademarks.
*/
bool result = true;
int expectedCount = 6;
string pattern = @"\b(\w+?)([ ])";
string input = "Microsoft Office Professional Edition combines several office " +
"productivity products, including Word, Excel , Access , Outlook , " +
"PowerPoint , and several others. Some guidelines for creating " +
"corporate documents using these productivity tools are available " +
"from the documents created using Silverlight on the corporate " +
"intranet site.";
Regex test = new Regex(pattern);
MatchCollection matches = test.Matches(input);
foreach (Match match in matches)
{
GroupCollection groups = match.Groups;
Log.Comment(groups[2] + ": " + groups[1]);
}
Log.Comment("Found "+matches.Count+" trademarks or registered trademarks.");
if (matches.Count != expectedCount)
{
result = false;
}
if (matches[0].ToString() != "Microsoft ")
{
result = false;
}
else if (matches[1].ToString() != "Excel ")
{
result = false;
}
else if (matches[2].ToString() != "Access ")
{
result = false;
}
else if (matches[3].ToString() != "Outlook ")
{
result = false;
}
else if (matches[4].ToString() != "PowerPoint ")
{
result = false;
}
else if (matches[5].ToString() != "Silverlight ")
{
result = false;
}
return result ? MFTestResults.Pass : MFTestResults.Fail;
奇怪的字符应该是 ©、™、℠ 或 ®,但由于框架中使用的 UTF-8 编码,这并不是一个“拦路虎”,因为人们基本上只需要理解这个平台差异。我本可以做一些奇怪或花哨的事情来尝试解决这个问题,但我同时也看到框架正在成长并支持各种不同的区域设置,所以我必须等待反馈来确定这到底有多烦人。我怀疑,因为如果你不小心,这在完整框架上技术上也可能发生,而且它不像我说的那么糟糕。但我同时也要说,字符字面量可能不是匹配的最佳方式,将来可能需要为表达式语法解析器提供特定的代码以及编码,或者至少为 Regex 类本身提供一个 Encoding 成员,以确保它能够选择性地匹配替代编码。
移植代码的整个过程只用了 4 个小时。我将表达式字符串编译成完整的 RegexProgram,并且所有测试都通过了!我对自己非常满意,因为我没有使用代码工具来完成移植,而是手工完成的,结果不言而喻。因此,我可以像使用自己的代码一样进出代码。
一些注释缺失,但我尽力确保为方法提供了适当的描述,并且对每一行稍微难以阅读的代码都附有某种注释,以确保未来的开发人员能够理解我为何使用一系列变量等;
我基本上然后从 MSDN 示例开始自上而下,我知道这些示例必须在两种实现中产生一致的结果。
这就是出现头痛问题的地方,匹配是按照引擎中的理论进行的,但是成员的名称与完整框架实现中的名称完全不同。API 也大相径庭。
这又花了大约 2 到 3 天才完全弄好,并且没有一个从移植的库的单元测试代码中失败。
我合作的代码审查员遇到了一些不幸的问题,这推迟了我的项目审查过程约 2 个月。在微软看到我无聊到在不到 48 小时内完成了完整的 WebSocket 实现后,他们给我和代码审查员发邮件,询问我为什么现在在做 WebSocket……长话短说,Lorenzo 直接审查并帮助我做了一些决定,例如重做完整框架代码中的 StringBuilder,以及实现最少的方法集,以便使用相同的代码通过 MSDN 示例。
起初这听起来很容易,但我很快就了解到,这就是引擎差异最大的地方,完整框架实现根据给定的表达式和各种匹配算法背后的理论,使用各种专门的匹配算法。
我不深入研究 NFA 和 DFA 或 BoyerMoore 和 Thompson,我只是按照完整框架实现命名了属性,而且很容易做到,我甚至提升和降级了一些字段和属性,事情仍然有效,但一些 MSDN 示例需要 Match
、MatchCollection
、Capture
和 Group
类。
所以我直接从完整框架移植了它们,以确保 API 相同,并且示例返回的结果匹配。
我被不允许从完整框架中获取 Pop 和 Crawl 等方法,除非我先证明我理解了代码,并且确实有一个符合要求的实现,需要完整框架的方法,不是因为我想偷懒,而是因为我想确保我的类返回的结果与完整框架一致,并且我还想确保反射调用与完整框架上的相似。
当我在一周内证明只有一些非常小的问题,并且我拥有除少数几个小功能之外的所有内容时,这个想法最终被接受了。
现在,我们将花一些时间来探讨实现中缺失或不同的内容,甚至提供一些关于未来实现的有趣想法。
移植代码的主要理论是根据给定的表达式创建一个 RegexProgram
。这个程序本质上是一个状态机,允许它具有某些函数,例如 IsMatch,使程序能够返回有意义的结果,如 true 或 false,或者匹配的索引等。
有一个 Regex Compiler 类,负责接收给定的表达式并将其编译成 RegexProgram
实例。
有一个 DebugRegexCompiler
,它公开了一些通常用于调试和 Bug 测试的异常,但也可以在运行时与动态创建的正则表达式一起使用。
using System;
using System.Text;
using System.Collections;
namespace System.Text.RegularExpressions
{
#if DEBUG
/// <summary>
/// A subclass of RECompiler which can dump a regular expression program for debugging purposes.
/// </summary>
public class RegexDebugCompiler : RegexCompiler
{
/// <summary>
/// Mapping from opcodes to descriptive strings
/// </summary>
static Hashtable hashOpcode = new Hashtable()
{
{OpCode.ReluctantStar, "Star"},
{OpCode.ReluctantPlus, "ReluctantPlus"},
{OpCode.ReluctantMaybe, "ReluctantMaybe"},
{OpCode.EndProgram, "EndProgram"},
{OpCode.BeginOfLine, "BeginOfLine"},
{OpCode.EndOfLine, "EndOfLine"},
{OpCode.Any, "Any"},
{OpCode.AnyOf, "AnyOf"},
{OpCode.Branch, "Branch"},
{OpCode.Atom, "Atom"},
{OpCode.Star, "Star"},
{OpCode.Plus, "Plus"},
{OpCode.Maybe, "Maybe"},
{OpCode.Nothing, "Nothing"},
{OpCode.GoTo, "GoTo"},
{OpCode.Continue, "Continue"},
{OpCode.Escape, "Escape"},
{OpCode.Open, "Open"},
{OpCode.Close, "Close"},
{OpCode.BackRef, "BackRef"},
{OpCode.PosixClass, "PosixClass"},
{OpCode.OpenCluster, "OpenCluster"},
{OpCode.CloseCluster, "CloseCluster"}
};
/// <summary>
/// Returns a descriptive string for an opcode.
/// </summary>
/// <param name="opcode">Opcode to convert to a string</param>
/// <returns> Description of opcode</returns>
String OpcodeToString(char opcode)
{
// Get string for opcode
String ret = (String)hashOpcode[opcode];
// Just in case we have a corrupt program
if (ret == null)
{
ret = "UNKNOWN_OPCODE";
}
return ret;
}
/// <summary>
/// Return a string describing a (possibly unprintable) character.
/// </summary>
/// <param name="c">Character to convert to a printable representation</param>
/// <returns>String representation of character</returns>
String CharToString(char c)
{
// If it's unprintable, convert to '\###'
if (c < ' ' || c > 127)
{
return "\\" + (int)c;
}
// Return the character as a string
return c.ToString();
}
/// <summary>
/// Returns a descriptive string for a node in a regular expression program.
/// </summary>
/// <param name="node">Node to describe</param>
/// <returns>Description of node</returns>
String NodeToString(int node)
{
// Get opcode and opdata for node
char opcode = Instructions[node /* + RE.offsetOpcode */];
int opdata = (int)Instructions[node + Regex.offsetOpdata];
// Return opcode as a string and opdata value
return OpcodeToString(opcode) + ", opdata = " + opdata;
}
/// <summary>
/// Dumps the current program to a {@link PrintStream}.
/// </summary>
/// <param name="p">PrintStream for program dump output</param>
public void DumpProgram(System.IO.TextWriter p)
{
// Loop through the whole program
for (int i = 0, e = Instructions.Length; i < e; )
{
// Get opcode, opdata and next fields of current program node
char opcode = Instructions[i /* + RE.offsetOpcode */];
char opdata = Instructions[i + Regex.offsetOpdata];
int next = (short)Instructions[i + Regex.offsetNext];
// Display the current program node
p.Write(i + ". " + NodeToString(i) + ", next = ");
// If there's no next, say 'none', otherwise give absolute index of next node
if (next == 0)
{
p.Write("none");
}
else
{
p.Write(i + next);
}
// Move past node
i += Regex.nodeSize;
// If character class
if (opcode == OpCode.AnyOf)
{
// Opening bracket for start of char class
p.Write(", [");
// Show each range in the char class
// int rangeCount = opdata;
for (int r = 0; r < opdata; r++)
{
// Get first and last chars in range
char charFirst = Instructions[i++];
char charLast = Instructions[i++];
// Print range as X-Y, unless range encompasses only one char
if (charFirst == charLast)
{
p.Write(CharToString(charFirst));
}
else
{
p.Write(CharToString(charFirst) + "-" + CharToString(charLast));
}
}
// Annotate the end of the char class
p.Write("]");
}
// If atom
if (opcode == OpCode.Atom)
{
// Open quote
p.Write(", \"");
// Print each character in the atom
for (int len = opdata; len-- != 0; )
{
p.Write(CharToString(Instructions[i++]));
}
// Close quote
p.Write("\"");
}
// Print a newline
p.WriteLine("");
}
}
/// <summary>
/// Dumps the current program to a <code>System.out</code>.
/// </summary>
public void dumpProgram()
{
//PrintStream w = new PrintStream(System.out);
//dumpProgram(w);
//w.flush();
}
}
#endif
}
您甚至可以将正则表达式编译成 RegexProgram
实例,然后使用反射将它们序列化或将其字节码存储在 SD 卡上,并在重启后实例化 Regex 类时将其设置回来。有一个 RegexPrecompiler
类,可以在模拟器或硬件上使用,它专门为此目的而设计。
using System;
using Microsoft.SPOT;
namespace System.Text.RegularExpressions
{
/// <summary>
/// Class for precompiling regular expressions for later use
/// </summary>
public class RegexPrecompiler
{
/// <summary>
/// Main application entrypoint.
/// Might make this have methods and be a class rathern the a program...
/// Then the class can Serialise and Deserialiaze the Regexps
/// </summary>
/// <param name="arg">Command line arguments</param>
static public void Main(String[] arg)
{
// Create a compiler object
RegexCompiler r = new RegexCompiler();
// Print usage if arguments are incorrect
if (arg.Length <= 0 || arg.Length % 2 != 0)
{
Debug.Print("Usage: recompile <patternname> <pattern>");
return;
}
// Loop through arguments, compiling each
for (int i = 0, end = arg.Length; i < end; i += 2)
{
try
{
// Compile regular expression
String name = arg[i];
String pattern = arg[i + 1];
String instructions = name + "Instructions";
// Output program as a nice, formatted char array
Debug.Print("\n // Pre-compiled regular expression '" + pattern + "'\n"
+ " private static char[] " + instructions + " = \n {");
// Compile program for pattern
RegexProgram program = r.Compile(pattern);
// Number of columns in output
int numColumns = 7;
// Loop through program
char[] p = program.Instructions;
for (int j = 0; j < p.Length; j++)
{
// End of column?
if ((j % numColumns) == 0) Debug.Print("\n ");
// Print char as padded hex number
String hex = (0).ToHexString();
while (hex.Length < 4) hex = "0" + hex;
Debug.Print("0x" + hex + ", ");
}
// End of program block
Debug.Print("\n };");
Debug.Print("\n private static REProgram " + name + " = new REProgram(" + instructions + ");");
}
catch (RegexpSyntaxException e)
{
Debug.Print("Syntax error in expression \"" + arg[i] + "\": " + e.ToString());
}
catch (Exception e)
{
Debug.Print("Unexpected exception: " + e.ToString());
}
}
}
}
}
总而言之,这项任务从第一次复制/粘贴到最终提交给微软,大约花了 4 个月的时间。 (而且请注意,这是在没有任何其他文档或帮助(来自任何人)的情况下完成的,除了您(读者)自己去做时会得到的帮助)。
幸运的是,每个人都很满意,而且显然没有 Bug,这增加了我对自身技能的信心,并开始让我有点飘飘然。
最终,如果我能重做一遍,我可能会更主动地将语法和引擎调整成我想要的样子,同时添加用于特殊本地化匹配的方法。
例如,大致如下:
//在少于 65535 个 tick 内匹配给定的表达式‘(\.?\)’,将结果传递给存在的函数,并返回结果。
“\ \In{65535} (\.?\) \-> EvaluateMatch\” //其中 EvaluateMatch 是 MatchEvaluator 或类似的
//在少于 65535 个 tick 内匹配给定的表达式‘(\b+)’,将结果传递给休眠 100 毫秒并返回匹配的函数。
“\ \In{100} (\b+) \-> { Sleep(_GivenTime_); return $1; }\” //_GivenTime_ 将隐含地为 100,因为它已被提供给 In 构造。
我还会改变一些编译方式,使其尽可能多地使用 CLR 字符串方法,而不是自己编译类型,但这对于 Micro Framework 来说可能在动态执行时过于繁重,尽管技术上是可能的。
我想象,如果语言实现普遍使用这种语法,那么反射和脚本将成为每种语言内置的功能,前提是存在一个符合标准的正则表达式引擎。
试想一下,如果能够编写一个正则表达式来查找数据,并且该表达式能够调用委托,或者你在编写病毒扫描器或内存监视器时,可以执行这样的任务,例如过滤以太网适配器上的原始数据……你基本上可以使用匹配引擎来找出代码何时发生变化,最后但同样重要的是,想象一下能够直接跳转到汇编/MSIL/字节码,并且能够动态创建代码或智能地修补段,或者调用其他函数来完成这些工作,或者两者兼而有之。
我真诚地认为,有了这个机会,开发人员甚至能够更好地实现与解释代码的互操作性,因为它将直接传递给正则表达式层,并转换为机器语言并执行,从而提供比各种不同的 API 和经典的指针传递更高的安全性和标准化程度。只要记住,将指针传递到另一个内存区域很容易,但跨机器边界传递指针几乎总是无效的,有了这个新概念,开发人员就可以将正则表达式传递到机器边界,并对代码执行具有与他们正在运行的系统相同的期望;偏移量和指针变得仅限于表达式,并且它们再次有意义,并且它们也可以在几乎没有麻烦的情况下跨机器边界传输,只要接收者按照与所有网络通信相同的标准结构进行解析。
我认为这才是原始实现者在开发“正则表达式”时所设想的,然而每个人都沉迷于数学,然后我们又想起那是关于字符串的数学,却忘了字符串也只是字节……
结束语
总之,我也希望看到更多与二进制匹配相关的方法,但每个实现都有自己的特色,开发人员可以轻松地包装 Regex 类并仍然轻松地创建它。
如果我可以更改完整框架实现,我唯一会做的更改是减少执行各种匹配类型和状态机转换的类的数量。
将创建一个单一的超类,它将分析表达式,同时考虑用于匹配的字符串。它将确定要使用的分支,并构建一个 RegexProgram
,其中包含专门为给定字符串优化的分支,而不是依赖于几个简单的检查来默认使用单一算法。这将允许所有类型的节点搜索逻辑在一次匹配中动态应用,而不会比当前实现使用的开销更多。
您可以在这里找到 Collin Miller 的两篇原始 MSDN 文章。
- http://blogs.msdn.com/b/netmfteam/archive/2011/08/18/netmf-4-2-regular-expressions.aspx
- http://blogs.msdn.com/b/netmfteam/archive/2011/06/07/netmf-version-4-2-beta-now-available.aspx
这是一篇与 Micro Framework 无关的精彩文章,但它解释了如何将正则表达式转换为 NFA -> DFA 工作原理,并且有插图,并且是由学术来源撰写的:http://lambda.uta.edu/cse5317/notes/node9.html。
[引用/感谢]我自己,
CIA,
Dennis Ritchie、Boyer、Moore 和 Thompson、Fravia 以及往日时光的鬼魂
最后但同样重要的是大麻
[致敬]
Anonymous,
LulzSec,
AntiSec,
Identology - MYiDONE.com,(0d fa real and isocdftpz)
Federation Studios,Marsh,Gaijin,t4w2,Gracktov,Gellpak 以及 Eclipse 的其他人。
iDenGodz (顺便说一句,我黑了你),
iDEN Insider,tommy 和 crazysim
PLA Blue,
Zi Hackademy,
MIT EECS
[挑衅]
Area 69,- 尤其是 NexVision 和他的走狗们。
howardforums,- 你们所有想成为 Area 69 一员的菜鸟(尤其是 yoDude)。
iDENGodz,- M00$3 iDEN Insider,AaronASB & Rickster