我正在使用一种服务,该服务将数据提供为类似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。
答案 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)
答案 3 :(得分:0)
答案 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)); .)
.