【问题标题】:Tokenizing a custom text file format file using C#使用 C# 标记自定义文本文件格式文件
【发布时间】:2011-05-07 20:45:21
【问题描述】:

我想解析一种语法稍显古怪的基于文本的文件格式。以下是一些有效的示例行:

<region>sample=piano C3.wav key=48 ampeg_release=0.7 // a comment here
<region>key = 49 sample = piano Db3.wav
<region>
group=1
key = 48
    sample = piano D3.ogg

我认为想出一个有意义的正则表达式对我来说太复杂了,但我想知道是否有一种很好的方法可以在不编写自己的解析器的情况下标记这种类型的输入?即我想要读取上述输入并吐出“令牌”流的东西,例如,我的示例格式开头的输出将类似于:

new Region(), new Sample("piano C3.wav"), new Key("48"), new AmpegRelease("0.7"), new Region()

是否有一个好的库/教程可以为我指明正确的方向,以一种优雅的方式来实现它?

更新:我用 Irony 尝试过这个,但是我需要解析的语法的怪癖(特别是 sample= 后面的数据可以有一个空格)导致他们建议我最好基于 String.Split 编写自己的代码。见讨论here

【问题讨论】:

  • 这个文件格式是 sfz 吗?很难google,如果是请注明
  • 是的,格式是here,但我通常对解决此类问题的最佳方法感兴趣,而不管 sfz 的具体情况如何

标签: c# parsing tokenize


【解决方案1】:

对于这种类型的东西,我会选择轻量级但功能强大的 CoCo/R。如果您向我展示更多示例输入,我可能会想出一个语法起点。


我之前用过lex和yacc,所以有一些解析经验。 – Mark Heath 17 分钟前

你很幸运:我在 Fedora 的 soundfont-utils 包中找到了 sfz 的 lex 语法。该软件包包含sfz2pat util。您可以在此处获取(源)包:

http://rpmfind.net//linux/RPM/fedora/14/i386/soundfont-utils-0.4-10.fc12.i686.html (src.rpm)

根据快速调查,最新版本的语法来自 2004 年 11 月,但相当复杂(sfz2pat.l 中的 58k)。这里有一个样品来品尝一下:

%option noyywrap
%option nounput
%option outfile = "sfz2pat.c"

nm  ([^\n]+".wav"|[^ \t\n\r]+|\"[^\"\n]+\")
ipn [A-Ga-g][#b]?([0-9]|"-1")

%s  K

%%

"//".*  ;

<K>"<group>"    {
    int i;
    leave_region();
    leave_group();
    if (!enter_group()) {
        SFZERR
        "Can't start group\n");
        return 1;
    }
    am_in_group_scope = TRUE;
    for (i = FIRST_SFZ_PARM; i < MAX_SFZ_PARM; i++) group_parm[i] = default_parm[i];
    for (i = 0; i < MAX_FLOAT_PARM; i++) group_flt_parm[i] = default_flt_parm[i];
    group_parm[REGION_IN_GROUP] = current_group;
    BEGIN(0);
}
<K>"<region>"   {
    int i;
    if (!am_in_group) {
        SFZERR
        "Can't start region outside group.\n");
        return 1;
    }
    leave_region();
    if (!enter_region()) {
        SFZERR
        "Can't start region\n");
        return 1;
    }
    am_in_group_scope = FALSE;
    for (i = 0; i < MAX_SFZ_PARM; i++) region_parm[i] = group_parm[i];
    for (i = 0; i < MAX_FLOAT_PARM; i++) region_flt_parm[i] = group_flt_parm[i];
    BEGIN(0);
}
<K>"sample="{nm} {
    int i = 7, j;
    unsigned namelen;
    if (yytext[i] == '"') {
        i++;
        for (j = i; j < yyleng && yytext[j] != '"'; j++) ;
    }
    else j = yyleng;
    namelen = (unsigned)(j - i + 1);
    sfzname = strncpy( (char *)malloc(namelen), yytext+i, (unsigned)(j-i) );
    sfzname[j-i] = '\0';
    for (i = 0; i < (int)namelen; i++) if (sfzname[i] == '\\') sfzname[i] = '/';
    SFZDBG
    "Sample name is \"%s\"", sfzname);
    SFZNL
    if (read_sample(sfzname)) {
#ifndef LOADER
        fprintf(stderr, "\n");
#endif
        return 0;
    }
    BEGIN(0);
}
[...snip...]

【讨论】:

  • 谢谢,我会调查的。文件格式规范为here
【解决方案2】:

假设语言相当常规,我建议使用ANTLR 编写一个快速解析器。对于具有解析经验的人来说,它的学习曲线非常简单,并且可以输出 C#(除其他外)。

【讨论】:

  • 谢谢你会试试这个。之前用过lex和yacc,所以有一些解析经验。
【解决方案3】:

我使用Gardens Point LEXGardens Point Parser Generator 来生成解析器。如果您有一些 lex/yacc 知识,它们尤其适用。

IMO,这两个是 .NET 的最佳解析器生成器。

一个奖励点:创建者可以快速响应错误报告和建议,如 here 所示。

【讨论】:

    猜你喜欢
    • 2014-08-13
    • 1970-01-01
    • 1970-01-01
    • 2021-08-12
    • 1970-01-01
    • 1970-01-01
    • 2022-01-14
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多