65.9K
CodeProject 正在变化。 阅读更多。
Home

扫描仪和扫描仪生成器

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.95/5 (9投票s)

2001年4月10日

2分钟阅读

viewsIcon

101214

downloadIcon

2710

支持在同一个对象中实现两种常见的扫描器方法。

Sample Image - ScanGen.gif

引言

扫描器将字符流分解为一系列标记。这与人类读者将字符分组为单词、数字和标点符号,从而达到更高的抽象层次类似。文本

0-201-05866-9, cancelled,    "Parallel Program Design"

例如,可以被翻译成以下标记

T_ISBN T_COMMA T_CANCELLED T_COMMA T_TITLE 

其中 T_ISBNT_COMMA 等是整数常量。实现通用扫描器有两种方法。

  • 扫描器是类的一个对象。

    搜索的标记通过调用成员函数来指定。

  • 扫描器是根据正则表达式自动生成的。

    这是一个两阶段方法。首先,你指定扫描器,然后运行生成器,它会输出“C”源代码。

第一种方法更适合于频繁更改的项目。第二种方法可以提供卓越的性能,但缺点是生成的“C”代码对于人类来说几乎无法阅读,因此不应进行编辑。

本文中介绍的扫描器和扫描器生成器结合了两种方法,并为两种实现策略提供了一个接口。

扫描器类的接口

扫描器 REXI_Scan 基于已经发表在文章“快速正则表达式”中的正则表达式设施。要使用它,您需要
  • 为每个要识别的标记指定一个正则表达式。
  • 设置源字符串。
  • 重复调用 Scan,直到它返回 REXI_Scan::eEos
class REXI_Scan : public REXI_Base
{
public:
    REXI_Scan(char cLineBreak= '\n'); //related function 'GetNofLines'

/*initialize scanner with symbol definitions    1.STEP    */
    REXI_DefErr     AddSymbolDef        (string strRegExp,int nIdAnswer);
    REXI_DefErr     AddHelperRegDef     (string strName,string strRegExp);

    REXI_DefErr     SetToSkipRegExp     (string strRegExp= "[ \r\n\v\t]*");

/* set source                     2.STEP    */
    inline  void    SetSource           (const char* pszSource);

/* Read next token, then return symbolId ('nIdAnswer' from 'AddSymbolDef') 
                        3.STEP    */
    int            Scan                ();

/* retrieve,set information after a call to 'Scan'    */
    inline  string    GetTokenString    ()const;
    inline  void    SkipChars    (int nOfChars=1);
    inline  int        GetLastSymbol    ()const;
    inline  int        GetNofLines    ()const;
};

示例用法

enum ESymbol{T_ERR,T_AVAILABLE,T_CANCELLED,
            T_COMMA,T_LINEBREAK,T_ISBN,T_TITLE};
struct Info{
        Info():m_eKey(T_ERR){}  
        string  m_sISBN;    ESymbol m_eKey;  string  m_sTitle;
};
int main(int argc,char* argv[])
{
    const int ncOk= REXI_DefErr::eNoErr;
    const char szTestSrc[]= 
    "3-8272-5737-9,AVAILABLE,    \"XML praxis und referenz\"\r\n"
    "0-201-05866-9,cancelled,    \"Parallel Program Design\"\r\n";

    REXI_Scan scanner;
    REXI_DefErr err;
/* STEP 1: initialize scanner with symbol definitions */
    err= scanner.AddSymbolDef ("(AVAILABLE)\\i",T_AVAILABLE);
    assert(err.eErrCode==ncOk);
    err= scanner.AddSymbolDef ("(CANCELLED)\\i",T_CANCELLED);
    assert(err.eErrCode==ncOk);
    err= scanner.AddSymbolDef (",",T_COMMA);
    assert(err.eErrCode==ncOk);
    err= scanner.AddSymbolDef ("\\n",T_LINEBREAK);
    assert(err.eErrCode==ncOk);
    err= scanner.AddHelperRegDef("$Int_","[0-9]+\\-");
    assert(err.eErrCode==ncOk);
    err= scanner.AddSymbolDef ("$Int_ $Int_ $Int_ [0-9]+", T_ISBN);
    assert(err.eErrCode==ncOk);
    err= scanner.AddSymbolDef (" \"( [^\"\\n] | \\\"] )* \"", T_TITLE);
    assert(err.eErrCode==ncOk);
    err= scanner.SetToSkipRegExp("[ \\t\\v\\r]*");
    assert(err.eErrCode==ncOk);
/* STEP 2 : set source */
    scanner.SetSource(szTestSrc);
    int nNofLines=0;
    int nRes;
    Info info;
    vector<Info> vecInfos;
/* STEP 3: read until eos */
    while( (nRes=scanner.Scan())!=REXI_Scan::eEos ){
        switch(nRes){
        case T_AVAILABLE: 
        case T_CANCELLED:
            info.m_eKey= (enum ESymbol)nRes;
            break;
        case T_TITLE:
            info.m_sTitle= scanner.GetTokenString();
            break;
        case T_ISBN:
            info.m_sISBN=  scanner.GetTokenString();
            break;
        case T_LINEBREAK:
            vecInfos.push_back(info); info= Info();
            break;
        case REXI_Scan::eIllegal: 
            cout    <<  "Illegal:"    <<  
                scanner.GetTokenString()  <<  endl;
            while( (nRes=scanner.Scan())!=REXI_Scan::eEos 
                                && nRes!= T_LINEBREAK);
            info= Info();
            break;
        }
    }
    cout   << "Number of correct read records: "  
           <<  vecInfos.size() <<  endl;
    char c; cin >> c;
    return 0;
}

扫描器生成器的接口

扫描器生成器是一个非常简单的 GUI 程序。它允许您指定和运行测试扫描器,并最终生成指定扫描器的源代码。生成的代码使用一个 REXI_Scan 派生的扫描器,并提供两个不同的代码部分。通过条件指令 #ifdef REXI_STATIC_SCANNER 控制,激活一个高效的硬编码扫描器或像上面描述的那样工作的扫描器。

要生成的扫描器的规范使用正则表达式,并支持 4 种不同的方式来指定一个标记,如下所示。

if    #T_Quote= '[^']'    $Int= [0-9]+    ##T_FLOAT= $Int (\. $Int)?

重要的是,您需要用制表符分隔标记定义。现在,让我们看看上述 4 种定义意味着什么。

1. Token   if    The scanner searches for exactly the word 'if' 
          and automagically creates a constant T_if for the token
2. Token   #T_Quote= '[^']'    The leading # means: 
          The next identifier up to the = is the name of the token constant, 
          then the token definition follows.
3. Helper  $Int= [0-9]+    Defines a helper definition, 
          which can be used later.
4. Token   ##T_FLOAT= $Int (\. $Int)? 
          The leading ## means: Same as # but do postprocessing 
          after recognizing this token.

生成的扫描器片段

int    Simple::Scan()
{
#ifdef REXI_STATIC_SCANNER
    int nRes= FastScan();
#else
    int nRes= REXI_Scan::Scan();
#endif
    switch(nRes){
        case eIllegal:{
            m_sIllegal= GetTokenString();
            return nRes;
        }
        case T_PRICE:{
            // add your postprocessing code here
            return nRes;
        }
        default: return nRes;
    }
}

预期用途

扫描逗号分隔的文件、为 C++ 源代码实现美化打印器或为解释器构建扫描器都是潜在的应用领域。 还有很多免费可用的扫描器生成器(lex、bison),但据我所知,没有一个生成具有如此简洁接口的扫描器。

© . All rights reserved.