【发布时间】:2013-12-18 09:58:38
【问题描述】:
A legacy application I work with 有一种时髦的数据格式,称为 SGS。我已经考虑并开始使用一些蛮力解决方案,包括手动生成的有限状态机和自定义递归下降解析器,但我正在努力构建一个应用程序,其中(非库)源代码的数量是足以表达需要做的事情。
所以我一直在研究基于 Clojure 的解析器。我一直在摆弄
这些都没有足够的文档/支持在网络上让我开始运行。因此,我正在寻找对其中一种工具(或不错的替代工具)有经验的人来帮助我。
这是数据语言:
数据由具有标签(从第 1 列开始)和 1 个或多个字段的行表示,由一个或多个空格分隔。
字段由一个或多个子字段组成,以逗号分隔。为了便于阅读,逗号可能后跟空格,但这些并不重要。
标签是由集合 [-$0-9A-Z_*%] 中的字符组成的标识符,不必是唯一的。
子字段要么是标识符,要么是带引号的字符串,要么是缺失的 (nil)。带引号的字符串由 2 前导单引号和 2 尾随单引号分隔。带引号的字符串不包含单引号,因此无需担心引号嵌套或转义。
Space-dot-space 开始行后注释。行尾的空格点是空的剩余行注释。行首的点空间使整行成为注释。仅由一个点组成的行也是行注释。
行可以跨越两行或多行。分号作为一行中最后一个非空白、非注释字符意味着该行在下一行继续,就好像分号和换行符都不存在一样。
分号和点(带或不带空格)在 cmets 或带引号的字符串中没有特殊意义。
示例:
. Comment
.
LAB1 F1S1 . Minimal data row, with line comment
LAB1 F1S1,F1S2,F1S3 F2S1 F3S1 . 2nd row with same label
LAB2 , , , F1S4 ''Field #2 (only 1 subfield)'' F3S1,,F3S3
LAB99 F1S1, . Field 1 has 2 subfields, 2nd is nil
LAB3 F1S1,F1S2, ;
F1S3 ;
F2S1 . Row continued over 3 lines.
手动解析我的示例,我想要这样的结果:
[
("LAB1" ["F1S1"])
("LAB1" ["F1S1" "F1S2" "F1S3"] ["F2S1"] ["F3S1"])
("LAB2" [nil nil nil "F1S4"] ["Field #2 (only 1 subfield"] ["F3S1" nil "F3S3"])
("LAB99" ["F1S1" nil])
("LAB3" ["F1S1" "F1S2" "F1S3"] ["F2S1"])
]
更新:
@edwood 建议展示我自己的实现供人们用作起点。我曾犹豫要不要这样做以避免预先偏向某个特定方向的人,但由于缺乏回应,这可能“聊胜于无”。
那么,这里是我自己的 InstaParse 解决方案,它可以正常工作:
SGS = (<COMMENT_LINE> / DATA_LINES) *
COMMENT_LINE = #' *\\.(?: [^\\n]*)?\\n'
DATA_LINES = LABEL FIELDS SEPARATOR? (LINE_COMMENT | '\\n')
LABEL = IDENTIFIER
FIELDS = '' | (SEPARATOR FIELD)+
SEPARATOR = CONTINUATION #' +' | #' +' (CONTINUATION #' *')?
CONTINUATION = #'; *\\n'
LINE_COMMENT = #' .[^\\n]*\\n'
FIELD = SUBFIELD (',' SEPARATOR? SUBFIELD)*
SUBFIELD = IDENTIFIER | QUOTED_STRING | ''
IDENTIFIER = #'[-$0-9A-Z_*%]+'
QUOTED_STRING = #'\\'\\'[^\\']*\\'\\''
在调试时,它设法处理了 249 行,然后才遇到我需要调试的错误。但是一旦我解决了这个问题,它大概可以处理我的全部 431 行数据,并在大约 2 分钟后结束了,
CompilerException java.lang.OutOfMemoryError: Java 堆空间, 编译:(sgs2.clj:40:13)
我将易于处理正则表达式的内容移至正则表达式,这似乎有助于提高性能。例如,注释行现在解析起来很简单,因为它们直接匹配单个正则表达式,或者不匹配。
如果我将输入数据缩减为 228 行,解析器会运行并在 16 秒内产生正确的结果。我认为对于非常少量的数据来说,这是一个很长的时间。我做错了什么?
【问题讨论】:
-
我经常听到的一个 clojure 解析器是 parsley - github.com/cgrand/parsley。但我以前从未尝试过。
-
instaparse 有一个很好的自述文件/文档。先试试吧。
-
@edbond:我尝试了 Instaparse,很高兴快速创建了一个在小型数据集上运行良好的解析器。然而,令人费解的是,我的解析器在一个只有几百行的文件上爆炸(即停止并产生异常)。
-
也许你应该添加一个解析器和示例文件,有人帮忙
-
对于后来到达这里的其他人:Parsley 是一个 LR(0) 解析器生成器,它非常适合解析诸如 S 表达式之类的东西,它具有清晰、明确的开始和结束标记。对于其他任何事情,您都将竭尽全力让 Parsley 解析器工作。