C的Keyword-Label-Value样式配置文件解析库

时间:2012-03-21 15:28:46

标签: c parsing configuration-files

配置解析库是否已存在,将读取以下样式的文件:

Keyword Label Value;

嵌套{ }替换值;可选标签;支持“包含”会很不错。

示例配置文件可能如下所示:

Listen Inside 127.0.0.1:1000;
Listen Outside {
    IP 1.2.3.4;
    Port 1000;
    TLS {
        CertFile /path/to/file;
    };
};
ACL default_acl {
    IP 192.168.0.0/24;
    IP 10.0.0.0/24;
};

3 个答案:

答案 0 :(得分:2)

您熟悉哪些编程语言?我对你问题的印象是C。

看起来像配置语言的标记是正则表达式:

  • 收听
  • 127.0.0.1:1000
  • 1000
  • {
  • }

几乎所有现代编程语言都有某种形式的支持。

如果实现是C,我可能会使用flex。它生成一个函数,它将应用一组正则表达式,将匹配的文本放入C字符串,并返回该正则表达式的类型(只是一个int,您可以选择)。该功能是一个词法分析器'或者' tokeniser'。它将字符串切换成符合您需求的便捷单元,一次一个正则表达式。

Flex很容易使用。它比lex有几个优点。一个是你可以有多个词法分析器功能,所以如果你需要为包含文件做一些奇怪的事情,那么你可以为这个工作设置第二个词法分析器功能。

您的语言看起来很简单。 Bison / Yacc是非常强大的工具,并且具有强大的功能带来了巨大的责任感。 :-)

我认为这很简单,我可能只是手工编写一个解析器。处理其结构可能只是一些功能。一种非常简单的技术称为递归下降解析器。你有CS学位,还是了解这些东西?

很多人会(在这个阶段)告诉你得到' Dragon Book'或者是其中一个较新的版本,通常是因为这是他们在大学时所拥有的。龙书很棒,但它就像告诉别人阅读维基百科的所有内容以了解鲸鱼。如果你有时间,那就太棒了,你会学到很多东西。

合理的开始是Wikipedia Recursive Descent parser文章。递归下降非常受欢迎,因为它相对简单易懂。使它变得简单的事情就是拥有一个适当的语法,这种语法被转换成一种易于递归下降解析的形式。然后你会为每个规则编写一个函数,并使用一个简单的错误处理机制(这就是为什么我问这个问题)。可能有生成它们的工具,但您可能会发现编写它的速度更快。第一次削减可能需要一天,然后你就可以做出决定。

一个非常漂亮的lex / flex功能是任何不匹配的字符,只是回显标准输出。因此,您可以看到正则表达式匹配的内容,并可以逐步添加它们。当输出“干涸”时一切都在匹配。

宗座警报:恕我直言,更多的C程序员应该学会使用flex。它相对易于使用,并且非常强大,适用于文本处理。恕我直言,因为他们也被告知使用yacc / bison,这是更强大,更微妙和复杂的工具。 结束宗座。

如果您需要一些语法帮助,请询问。如果有一个很好的语法(可能不是这样,但到目前为止你的例子看起来还不错)那么实现很简单。

我找到了两个有关stackoverflow答案的链接,看起来很有帮助:

以下是使用flex的示例。

Flex采用'脚本',并生成一个名为yylex()的C函数。这是输入脚本。

请记住,所有正则表达式都在yylex函数中匹配,所以尽管脚本看起来很奇怪,但它实际上是一个普通的C函数。要告诉调用者,这将是你的递归下降解析器,匹配什么类型的正则表达式,它返回一个你选择的整数值,就像任何普通的C函数一样。

如果没有什么可以告诉解析器,如空格,可能还有某种形式的注释,它就不会返回。它默默地'消耗这些字符。如果语法需要来使用换行符,那么它将被识别为令牌,并将合适的令牌值返回给解析器。有时候更容易让它更自由,所以这个例子消耗并忽略所有的空格。

有效地,yylex函数是从第一个%%到第二个%%的所有内容。它表现得像一个很大的switch()语句。
正则表达式就像(非常奇特的)case:标签。
{ ... }中的代码是普通的C.它可以包含任何C语句,并且必须正确嵌套在{ ... }

第一个%%之前的内容是放置弹性定义的地方,以及一些'指令'弯曲。 %{ ... %}中的内容是普通的C,可以包含文件中C所需的任何头,甚至可以定义全局变量。

第二个%%之后的内容是普通的C,不需要额外的语法,所以没有%{ ... %]

/* scanner for a configuration files */ 
%{ 
    /* Put headers in here */ 
#include <config.h>

%} 
%%
[0-9]+                  { return TOK_NUMBER; } 
[0-9]+"."[0-9]+"."[0-9]+"."[0-9]+":"[0-9]+ { return TOK_IP_PORT; } 
[0-9]+"."[0-9]+"."[0-9]+"."[0-9]+"/"[0-9]+ { return TOK_IP_RANGE; } 
"Listen"                { return TOK_KEYWORD_LISTEN; } 
[A-Za-z][A-Za-z0-9_]*   { return TOK_IDENTIFIER; }
"{"                     { return TOK_OPEN_BRACE; }
"}"                     { return TOK_CLOSE_BRACE; }
";"                     { return TOK_SEMICOLON; }
[ \t\n]+        /* eat up whitespace, do nothing */ 
.           { fprintf(stderr,  "Unrecognized character: %s\n", yytext ); 
              exit(1); 
            }
%%

/* -------- A simple test ----------- */
int main(int argc, char *argv[])
{ 
    int tok;
    yyin = stdin;
    while (tok=yylex()) {
        fprintf(stderr, "%d %s\n", tok, yytext);
    }
}

它有一个最小的虚拟主,它调用yylex()函数来获取下一个标记 (枚举)价值。 yytext是正则表达式匹配的字符串,因此main只打印它。

警告,这几乎没有经过测试,仅仅是:

flex config.l
gcc lex.yy.c -ll
./a.out <tinytest

值只是整数,因此标题中的枚举:

#ifndef _CONFIG_H_
#define _CONFIG_H_

enum TOKENS { 
 TOK_KEYWORD_LISTEN = 256, 
 TOK_IDENTIFIER = 257, 
 TOK_OPEN_BRACE = 258, 
 TOK_CLOSE_BRACE = 259, 
 TOK_SEMICOLON = 260, 
 TOK_IP_PORT = 261, 
 TOK_IP_RANGE = 262, 
 TOK_NUMBER = 263, 
};
#endif _CONFIG_H_

在解析器中,在需要下一个值时调用yylex。在将令牌类型值交还给解析器之前,您可能会将yylex包装在复制yytext的内容中。

你需要舒服地处理记忆。如果这是一个大文件,可以使用malloc来分配空间。但是对于小文件,并且为了使其易于上手和调试,编写自己的“笨蛋”可能是有意义的。分配器。一个哑巴&#39;内存管理系统,可以使调试更容易。最初只有一个静态分配的大字符数组和一个mymalloc()分发片段。我可以想象配置数据永远不会自由()&#39; d。最初可以用C字符串保存所有内容,因此调试很简单,因为输入的确切顺序在char数组中。一个改进的版本可能会&#39; stat&#39;一个文件,并分配足够大的一块。

您如何处理实际配置值有点超出我的描述。可能只需要文本字符串,或者可能已经有了一种机制。通常不需要存储“关键字”的文本值,因为解析器已经识别出它的含义,并且程序可能会转换其他值,例如, IP地址,进入一些内部表示。

答案 1 :(得分:1)

你看过lex and yacc(或者是flex和bison)吗?它有点毛茸茸,但我们使用它来解析看起来与配置文件完全相同的文件。您可以使用括号定义子结构,使用相同的键解析可变长度列表等。

标签是指意见吗?您可以定义自己的注释结构,我们使用'#'表示注释行。

它不支持包括AFAIK。

答案 2 :(得分:0)

将C库存放到JSONYAML。它们看起来就像你需要的那样。