4

我编写了一个库来将字符串与一组模式进行匹配,现在我可以轻松地将词法扫描器嵌入到 C 程序中。

我知道有许多完善的工具可用于创建词法扫描器(lex 和 re2c,仅列出想到的前两个)这个问题与词法分析器无关,而是关于“扩展”C 语法的最佳方法。词法分析器示例只是一般问题的具体案例。

我可以看到两种可能的解决方案:

  1. 编写一个预处理器,将带有嵌入式词法分析器的源文件转换为纯 C 文件,并可能转换为要在编译中使用的一组其他文件。
  2. 编写一组 C 宏来以更易读的形式表示词法分析器。

我已经做了这两个,但问题是:“根据以下标准,你认为哪一个更好?”

  • 可读性。词法分析器逻辑应该清晰易懂
  • 可维护性。查找和修复错误不应该是一场噩梦!
  • 干扰构建过程。预处理器在构建过程中需要一个额外的步骤,预处理器必须在路径中等等。

换句话说,如果你必须维护或编写一个使用这两种方法之一的软件,哪一种会让你失望更少?

例如,以下是针对以下问题的词法分析器:

  • 对所有数字求和(可以是十进制形式,包括像 1.3E-4.2 这样的指数形式)
  • 跳过字符串(双引号和单引号)
  • 跳过列表(类似于 LISP 列表: (3 4 (0 1)() 3) )
  • 在遇到单词 end(大小写无关)或缓冲区末尾时停止

在两种风格中。

/**** SCANNER STYLE 1 (preprocessor) ****/
#include "pmx.h"

t = buffer

while (*t) {
  switch pmx(t) { /* the preprocessor will handle this */
    case "&q" :         /* skip strings */
      break; 

    case "&f<?=eE>&F" : /* sum numbers */ 
      sum += atof(pmx(Start,0));
      break;

    case "&b()":        /* skip lists */
      break;

    case "&iend" :      /* stop processing */ 
      t = "";
      break;

    case "<.>":         /* skip a char and proceed */
      break;
  }
}

/**** SCANNER STYLE 2 (macros) ****/
#include "pmx.h"
/* There can be up to 128 tokens per scanner with id x80 to xFF */
#define TOK_STRING x81
#define TOK_NUMBER x82
#define TOK_LIST   x83
#define TOK_END    x84
#define TOK_CHAR   x85

pmxScanner(   /* pmxScanner() is a pretty complex macro */
   buffer
 ,
   pmxTokSet("&q"         , TOK_STRING)
   pmxTokSet("&f<?=eE>&F" , TOK_NUMBER)
   pmxTokSet("&b()"       , TOK_LIST)
   pmxTokSet("&iend"      , TOK_END)
   pmxTokSet("<.>"        , TOK_CHAR)
 ,
   pmxTokCase(TOK_STRING) :   /* skip strings */
     continue; 

   pmxTokCase(TOK_NUMBER) :   /* sum numbers */ 
     sum += atof(pmxTokStart(0));
     continue;

   pmxTokCase(TOK_LIST):      /* skip lists */
     continue;

   pmxTokCase(TOK_END) :      /* stop processing */ 
     break; 

   pmxTokCase(TOK_CHAR) :     /* skip a char and proceed */
     continue;
);

如果有人对当前的实现感兴趣,代码在这里:http ://sites.google.com/site/clibutl 。

4

2 回答 2

6

预处理器将提供更健壮和通用的解决方案。另一方面,宏可以快速启动,提供良好的概念验证,并且在示例关键字/标记空间较小时很容易。在某个点之后,使用宏扩展/包含新功能可能会变得乏味。我会说启动宏开始,然后将它们转换为您的预处理器命令。

此外,如果可能的话,尽量使用通用预处理器而不是编写自己的预处理器。

[...] 我将有另一个依赖项来处理(例如 Windows 的 m4)。

是的。但是你写的任何解决方案也是如此:)——必须维护它。您命名的大多数程序都有可用的 Windows 端口(例如,请参阅m4 for windows)。使用这种解决方案的优点是您可以节省大量时间。当然,不利的一面是,如果出现奇怪的错误,您可能必须尽快了解源代码(尽管维护这些错误的人非常有帮助,并且肯定会确保您得到所有​​帮助)。

再说一次,是的,我更喜欢打包的解决方案而不是我自己的解决方案。

于 2009-03-28T07:36:29.533 回答
3

自定义预处理器是解析器/解释器生成器中的典型方法,因为宏的可能性非常有限,并且在扩展阶段会出现潜在问题,从而使调试变得非常困难。

我建议您使用久经考验的工具,例如经典的 Yacc/Lex Unix 程序,或者如果您想“扩展”C,请使用 C++ 和 Boost::spirit,这是一个广泛使用模板的解析器生成器。

于 2009-03-28T07:35:14.843 回答