在C#中解析具有已知模式的Lisp S表达式

时间:2010-06-16 07:02:46

标签: c# .net parsing s-expression

我正在使用一种服务,该服务将数据提供为类似Lisp的S-Expression字符串。这个数据越来越快,我希望尽可能快地通过它,理想情况下直接在字节流上(它只是单字节字符)没有任何回溯。这些字符串可能非常冗长,我不希望GC为整个消息分配一个字符串。

我目前的实现使用CoCo / R语法,但它有一些问题。由于回溯,它将整个流分配给字符串。对于我的代码用户来说,如果他们必须改变它也有点繁琐。我宁愿拥有纯粹的C#解决方案。 CoCo / R也不允许重用解析器/扫描器对象,因此我必须为每条消息重新创建它们。

从概念上讲,数据流可以被认为是一系列S表达式:

(item 1 apple)(item 2 banana)(item 3 chainsaw)

解析此序列将创建三个对象。每个对象的类型可以由列表中的第一个值确定,在上面的情况“item”中。传入流的模式/语法是众所周知的。

在我开始编码之前,我想知道是否有那些已经存在的库。我确定我不是第一个遇到这个问题的人。


修改

以下是我想要的更多细节,因为我认为原来的问题可能有点模糊。

给出一些SExpressions,例如:

(Hear 12.3 HelloWorld)
(HJ LAJ1 -0.42)
(FRP lf (pos 2.3 1.7 0.4))

我想要一个与此等价的对象列表:

{
    new HearPerceptorState(12.3, "HelloWorld"),
    new HingeJointState("LAJ1", -0.42),
    new ForceResistancePerceptorState("lf", new Polar(2.3, 1.7, 0.4))
}

我正在处理的实际数据集是list of perceptors from a robot model in the RoboCup 3D simulated soccer league。我可能还需要反序列化another set of related data with a more complex structure

5 个答案:

答案 0 :(得分:3)

在我看来,解析生成器不需要解析只包含列表,数字和符号的简单S表达式。手写的递归下降解析器可能更简单,至少同样快。一般模式看起来像这样(在java中,c#应该非常相似):

Object readDatum(PushbackReader in) {
    int ch = in.read();
    return readDatum(in, ch);
}
Object readDatum(PushbackReader in, int ch) {
    if (ch == '(')) {
        return readList(in, ch);
    } else if (isNumber(ch)) {
        return readNumber(in, ch);
    } else if (isSymbolStart(ch)) {
        return readSymbol(in, ch);
    } else {
        error(ch);
    }
}
List readList(PushbackReader in, int lookAhead) {
    if (ch != '(') {
        error(ch);
    }
    List result = new List();
    while (true) {
        int ch = in.read();
        if (ch == ')') {
            break;
        } else if (isWhiteSpace(ch)) {
            skipWhiteSpace(in);
        } else {
            result.append(readDatum(in, ch);
        }
    }
    return result;
}
String readSymbol(PushbackReader in, int ch) {
    StringBuilder result = new StringBuilder();
    result.append((char)ch);
    while (true) {
       int ch2 = in.read();
       if (isSymbol(ch2)) {
           result.append((char)ch2);
       } else if (isWhiteSpace(ch2) || ch2 == ')') {
           in.unread(ch2);
           break;
       } else if (ch2 == -1) {
           break;
       } else {
           error(ch2);
       }
    }
    return result.toString();
}

答案 1 :(得分:1)

我使用OMeta#在C#中编写了一个S-Expression解析器。它可以解析您在示例中给出的S-Expressions类型,只需要在解析器中添加十进制数。

代码在github上以SExpression.NET的形式提供,相关文章可用here。作为替代方案,我建议看一下使用OMeta#编写的用于.NET的YaYAML YAML解析器。

答案 2 :(得分:0)

考虑使用Ragel。它是一个状态机编译器,可以生成相当快的代码。

主页上可能并不明显,但Ragel确实有C#支持。 Here's如何在C#

中使用它的一个简单例子

答案 3 :(得分:0)

查看gplexgppg

或者,您可以将S表达式简单地转换为XML,然后让.NET完成剩下的工作。

答案 4 :(得分:0)

Drew,也许你应该在这个问题上添加一些上下文,否则这个答案对其他用户没有意义,但试试这个:

CHARACTERS

    letter = 'A'..'Z' + 'a'..'z' .
    digit = "0123456789" .
    messageChar = '\u0020'..'\u007e' - ' ' - '(' - ')'  .

TOKENS

    double = ['-'] digit { digit } [ '.' digit { digit } ] .
    ident = letter { letter | digit | '_' } .
    message = messageChar { messageChar } CONTEXT (")") .

哦,我必须指出'\u0020'是unicode SPACE,您随后将使用“- ' '”删除它。哦,如果你不需要多个字符前瞻,你可以使用CONTEXT (')')

FWIW:CONTEXT不会使用所附序列,您仍必须在制作中使用它。

修改

好的,这似乎有效。真的,我的意思是这次:)

CHARACTERS
    letter = 'A'..'Z' + 'a'..'z' .
    digit = "0123456789" .
//    messageChar = '\u0020'..'\u007e' - ' ' - '(' - ')'  .

TOKENS

    double = ['-'] digit { digit } [ '.' digit { digit } ] .
    ident = letter { letter | digit | '_' } .
//    message = letter { messageChar } CONTEXT (')') .

// MessageText<out string m> = message               (. m = t.val; .)
// .

HearExpr<out HeardMessage message> = (. TimeSpan time; Angle direction = Angle.NaN; string messageText; .)
    "(hear" 
        TimeSpan<out time>
        ( "self" | AngleInDegrees<out direction> )
// MessageText<out messageText>    // REMOVED    
{ ANY } (. messageText = t.val; .) // MOD
    ')' (. message = new HeardMessage(time, direction, new Message(messageText)); .)
    .