前段时间试着用Arduino UNO做一个串口通信检测仪,在报文检测这部分遇到了困难。通常应该是用正则表达式来做报文模式匹配。找来正则表达式库,是用跳转表实现的,需要2个256元素的整型数组,这就用掉了1K内存,而Arduino UNO一共才2K内存……坑爹呢?!! 于是只能尝试自己做模式匹配了。
一、概念验证
正则表达式库都是动态构造有限自动机来做模式匹配,故而需要大量内存。而我的需求不需要动态构造,if判断即可。例如,要匹配“[a-z]{2}[0-9]{4}”这样的模式,可以将此正则表达式看作2段规则,“[a-z]{2}”和“[0-9]{4}”,每段都是一个字符判定和一个数量判定,可用级联if分段判定,代码如下:
const char* str = "a1bcd23456ef78"; //待检测字符串
size_t seg1=0, //“[a-z]{2}”段匹配计数
seg2=0, //“[0-9]{4}”段匹配计数
start=0; //匹配起点索引号
//遍历字符串
for (size_t index=0; str[index]; index++)
{
//取当前字符
char ch=str[index];
if (seg1<2)
{ //第一段数量不满
if (ch>='a' && ch<='z')
{ //第一段匹配成功,部分匹配
cout<<ch<<" matching from "<<start<<endl;
seg1++;
}
else
{ //第一段不匹配
cout<<ch<<" mismatched"<<endl;
//复位
seg1=0;
seg2=0;
start=index+1;
}
}
else
{ //第一段数量已满,匹配第二段
if (seg2<4)
{ //第二段数量不满
if (ch>='0' && ch<='9')
{ //第二段匹配成功
seg2++;
if (seg2==4)
{ //第二段数量已满,匹配完成
cout<<ch<<" matched from "<<start<<endl;
//复位
seg1=0;
seg2=0;
start=index+1;
}
else
{ //第二段部分匹配
cout<<ch<<" matching from "<<start<<endl;
}
}
else
{ //第二段不匹配,平移回滚
cout<<ch<<" slide and recheck"<<endl;
//平移第一段
seg1--;
seg2=0;
start++;
index--;
}
}
}
}
运行结果:
a matching from 0
1 mismatched
b matching from 2
c matching from 2
d slide and recheck
d matching from 3
2 matching from 3
3 matching from 3
4 matching from 3
5 matched from 3
6 mismatched
e matching from 10
f matching from 10
7 matching from 10
8 matching from 10
结果符合预期,相比正则表达式库,内存代价是n(规则段数量)个整型变量,且可以在字符串未完整获得的情况下逐字匹配,每次均可返回匹配结果。更长的正则表达式也可依此级联下去,但是将变得更加复杂,且难以复用此代码。
二、级联模板链
在《Modern C++ Design》中介绍了一种模板技巧,可以将模板作为自己的参数,构造一个模板链,最后用部分特化模板来终结模板链即可。将此技巧应用于本例,代码如下:
typedef bool (*CharPredType)(const unsigned char);
typedef int (*NumPredType) (const unsigned int);
template <CharPredType CharPred, CharPredType NumPred, typename RuleTail>
class LogicRule
{
public:
LRResult operator()(unsigned char c)
{
//...
}
};
class LogicRuleEnd;
template <CharPredType CharPred, CharPredType NumPred>
class LogicRule<CharPred, NumPred, LogicRuleEnd>
{
public:
LRResult operator()(unsigned char c)
{
//...
}
};
通用版的LogicRule用于构造模板链,CharPred和NumPred是字符和数量的判定谓词,RuleTail是后续模式规则。部分特化版LogicRule用于终结模板链。LogicRuleEnd类仅有声明,用来指示模板链终结,不可用来定义对象。用这个模板链定义本例的正则表达式,代码如下:
LogicRule<Char<'a','z'>, Num<2>, LogicRule<Char<'0','9'>, Num<4>, LogicRuleEnd> > ruler;
ruler是可用于匹配判定的函数对象,可以这样用:
using namespace std;
LogicRule<Char<'a','z'>, Num<2>, LogicRule<Char<'0','9'>, Num<4>, LogicRuleEnd> > ruler; //模式匹配逻辑尺
const char* str = "a1bcd23456ef78"; //待检测字符串
size_t start=0; //匹配起点索引号
//遍历字符串
for (size_t index=0; str[index]; index++)
{
//取当前字符
char ch=str[index];
//模式匹配
switch (ruler(ch))
{
case LRMatching:
cout<<ch<<" matching from "<<start<<endl;
break;
case LRMatchAndContinue:
cout<<ch<<" matched from "<<start<<endl;
break;
case LRMatched:
cout<<ch<<" matched from "<<start<<endl;
start=index+1;
break;
case LRMismatched:
cout<<ch<<" mismatched"<<endl;
start=index+1;
break;
case LRMismatchAndRecheck:
cout<<ch<<" mismatched and recheck"<<endl;
start=index;
index--;
break;
case LRSlideAndRecheck:
cout<<ch<<" slide and recheck"<<endl;
start++;
index--;
break;
}
}
运行结果:
a matching from 0
1 mismatched and recheck
1 mismatched
b matching from 2
c matching from 2
d slide and recheck
d matching from 3
2 matching from 3
3 matching from 3
4 matching from 3
5 matched from 3
6 mismatched
e matching from 10
f matching from 10
7 matching from 10
8 matching from 10
和手工编写代码相比,由于逻辑规则段落不能获知自己是否是第一段,因而第一段逻辑规则在非首字符匹配失败时都是回滚,后续逻辑规则匹配失败时才直接报告失败,略显冗余。如果阁下有更好的解决办法请赐教。
完整模板库代码如下:
#ifndef LogicRuler_h
#define LogicRuler_h
//要支持面向对象调用,可将LogicRuler类里的方法声明为虚函数,并增加虚析构函数。代价是每段规则都增加了虚函数表指针的占用空间
/**************************************/
//匹配结果
enum LogicRulerResult
{
LRMatching, //部分匹配
LRMatchAndContinue, //匹配成功,且还可能有更长可匹配部分
LRMatched, //完全匹配成功,且匹配完成
LRMismatched, //完全不匹配
LRMismatchAndRecheck,//不匹配, 但可从当前字符重新开始,当前字符需要回滚再匹配一次
LRSlideAndRecheck //不匹配, 但可将起点位置平移1位继续从当前字符开始,当前字符需要回滚再匹配一次 (此情况仅出现在第一段规则有数量上限时)
};
/**************************************/
//匹配单一字符谓词
template
inline bool Char(const unsigned char c) { return c==SingleChar; }
//匹配范围字符谓词
template
inline bool Char(const unsigned char c) { return c>=BeginChar&&c<=EndChar; }
//匹配所有可打印字符谓词
inline bool CharAllPrintable(const unsigned char c) { return Char<'\x20','\x7E'>(c); };
//匹配所有字母字符谓词
inline bool CharAllAlphabet(const unsigned char c) { return Char<'A','Z'>(c)||Char<'a','z'>(c); };
//匹配所有数字字符谓词
inline bool CharAllNumber(const unsigned char c) { return Char<'0','9'>(c); };
//匹配所有字母数字字符谓词
inline bool CharAllWord(const unsigned char c) { return CharAllAlphabet(c)||CharAllNumber(c); };
//匹配除指定单一字符外其它字符谓词
template
inline bool CharExcept(const unsigned char c) { return !Char
(c); }
//匹配除指定范围字符外其它字符谓词
template
inline bool CharExcept(const unsigned char c) { return !Char
(c); } /**************************************/ //匹配单一数量谓词 template
inline int Num(const unsigned int n) { return static_cast
(n-SingleNum); } //匹配范围数量谓词 template
inline int Num(const unsigned int n) { return n
EndNum?1:0; } //匹配上限数量谓词 template
inline int NumAtMost(const unsigned int n) { return n>SingleNum?1:0; } //匹配下限数量谓词 template
inline int NumAtLeast(const unsigned int n) { return n
class LogicRule : public LogicRuler { protected: //后续规则链对象 RuleTail Tail; public: //复位 (必须手动调用基类复位函数) void Reset() { LogicRuler::Reset(); Tail.Reset(); } //匹配函数调用操作符 int operator()(const unsigned char c) { if (NumPredicate(Compared)<0) { //数量不满 if (CharPredicate(c)) { //匹配成功 return (Compared++,LRMatching); } else { //不匹配 return Compared?(Reset(),LRMismatchAndRecheck):(Reset(),LRMismatched); } } else { //数量满足,先匹配后续规则 switch (Tail(c)) { case LRMatching: //后续部分匹配 return LRMatching; case LRMatchAndContinue: //后续匹配成功且可延展 return LRMatchAndContinue; case LRMatched: //后续完全匹配成功 return (Reset(),LRMatched); case LRMismatched: //后续完全不匹配 if (NumPredicate(Compared+1)>0) { //本段数量已满,尝试平移,不可平移则完全不匹配 return Compared?(Compared--,LRSlideAndRecheck):(Reset(),LRMismatched); } else { //本段数量可延展 if (CharPredicate(c)) { //匹配成功 return (Compared++,LRMatching); } else { //不匹配 return Compared?(Reset(),LRMismatchAndRecheck):(Reset(),LRMismatched); } } case LRMismatchAndRecheck: case LRSlideAndRecheck: //后续回滚 return (Reset(),LRMismatchAndRecheck); } } } };//class LogicRule
//特化逻辑规则类模板,用于终结规则链 //CharPredicate:字符匹配谓词 //NumPredicate :数量匹配谓词 template
class LogicRule
: public LogicRuler { protected: public: //复位 (必须手动调用基类复位函数) void Reset() { LogicRuler::Reset(); } //匹配函数调用操作符 int operator()(const unsigned char c) { if (CharPredicate(c)) { //匹配成功 Compared++; if (NumPredicate(Compared)<0) { //数量不满 return LRMatching; } else if (NumPredicate(Compared+1)>0) { //数量已满 return (Reset(),LRMatched); } else { //数量可延展 return LRMatchAndContinue; } } else { //不匹配 return Compared?(Reset(),LRMismatchAndRecheck):(Reset(),LRMismatched); } } };//class LogicRule
#endif