我试图实现一种非常简单的标记语言。我有一个中间表示,看起来像:
data Token = Str Text
| Explode Text
type Rep = [Token]
因此,我们的想法是转换形式的任意文本:
快速棕色%% fox %% %%在%%懒惰%%狗身上跳过%%。
成:
[Str "The quick brown", Explode "fox", Explode "jumps", Str "over the", Explode "lazy", Str "dog"]
进一步处理。此外,我们必须对待:
%% fox %% %%跳跃%%
与
不同%% fox跳跃%%
后者应该(爆炸"狐狸跳跃")
我尝试使用attoparsec实现这一点,但我不认为我有我需要的工具。但我对解析理论不太满意(我研究数学,而不是CS)。这是什么样的语法?我应该使用什么样的解析器组合库?我考虑使用Parsec和有状态monad变换器堆栈来跟踪上下文。听起来合理吗?
答案 0 :(得分:1)
如果没有合适的解析器,您可以采用便宜而简单的方法。要认识到的重要一点是,这个语法实际上相当简单 - 它没有递归等等。它只是Str
和Explode
s。
因此,我们可以首先将字符串分解为包含文本和分隔符的列表作为单独的值。我们需要一种数据类型来将分隔符(%%
)与实际文本(其他所有内容)分开。
data ParserTokens = Sep | T Text
然后我们需要将列表分解为其成员。
tokenise = intersperse Sep . map T . Text.splitOn "%%"
这将首先在%%
上拆分字符串,因此在您的示例中它将变为
["The quick brown ","fox"," ","jumps"," over the ","lazy"," dog."]
然后我们map T
覆盖它,将其从[Text]
转换为[ParserTokens]
。最后,我们intersperse Sep
覆盖它,重新引入%%
分隔符,但形状更容易处理。结果是,在您的示例中,
[T "The quick brown ",Sep,T "fox",Sep,T " ",Sep,T "jumps",Sep,T " over the ",Sep,T "lazy",Sep,T " dog."]
完成这一切后,剩下的就是将这个东西解析成你想要的形状。解析这相当于发现Sep-T的“1-2”冲击“某事”-Sep并用Explode "something"
替换它。我们编写了一个递归函数来执行此操作。
construct [] = []
construct (T s : rest) = Str s : construct rest
construct (Sep : T s : Sep : rest) = Explode s : construct rest
construct _ = error "Mismatched '%%'!"
这会将T s
转换为Str s
,将分隔符和T s
组合转换为Explode s
。如果模式匹配失败,那是因为某处存在一个杂散分隔符,所以我只是将其设置为使程序崩溃。您可能希望更好地处理错误 - 例如将结果包装在Either String
或类似的内容中。
完成后,我们可以创建函数
parseTemplate = construct . tokenise
最后,如果我们通过parseTemplate运行你的例子,我们得到预期的结果
[Str "The quick brown ",Explode "fox",Str " ",Explode "jumps",Str " over the ",Explode "lazy",Str " dog."]
答案 1 :(得分:0)
对于这样简单的解析器,即使Attoparsec似乎也有点矫枉过正:
parse = map (\w -> case w of
'%':'%':expl -> Explode $ init $ init expl
str -> Str str) . words
当然,此代码需要对Explode
案例进行一些完整性检查。
答案 2 :(得分:0)
这不会按照您指定的方式处理空格,但它应该让您走上正确的轨道。
parseMU = zipWith ($) (cycle [Str,Explode]) . splitps where
splitps :: String -> [String]
splitps [] = [[]]
splitps ('%':'%':r) = [] : splitps r
splitps (c:r) = let
(a:r') = splitps r
in ((c:a):r')