上周,我们通过阅读Gherkin语法的基础知识为解析做准备。 在本文和下一篇文章中,我们将使用可应用的解析库来解析该语法。 本周,我们将重点介绍该库的基础知识,并建立组合使用的词汇表。 我们将大量使用Applicative类型类。 如果您需要对此进行复习,请查看本文 。 当我们开始编码时,您也可以在Github上跟随示例! 这里的大多数代码在Parser.hs中 。
在接下来的几周中,我们还将看到其他几个解析库。 如果您想获得关于这些的更多想法,请下载我们的生产清单 。 它总结了许多其他有用的库,可用于编写更高级别的Haskell。
如果您从未开始编写Haskell,那么现在就是您的机会! 获取免费的初学者清单,并学习入门基础!
入门
因此,为了开始解析,让我们对输入格式进行一些注释。 首先,我们将输入要素文档视为单个字符串。 我们将删除所有空行,然后修剪每行的开头和结尾空格。
parseFeatureFromFile :: FilePath -> IO Feature
parseFeatureFromFile inputFile = do
fileContents <- lines <$> readFile inputFile
let nonEmptyLines = filter (not . isEmpty) fileContents
let trimmedLines = map trim nonEmptyLines
let finalString = unlines trimmedLines
case parseFeature finalString of
... …
isEmpty :: String -> Bool
isEmpty = all isSpace trim :: String -> String
trim input = reverse flippedTrimmed
where
trimStart = dropWhile isSpace input
flipped = reverse trimStart
flippedTrimmed = dropWhile isSpace flipped 这对于我们的语法意味着几件事。 首先,我们不在乎缩进。 第二,我们忽略多余的行。 这意味着我们的解析器可能会允许某些我们不想要的格式。 但这没关系,因为我们正在尝试使事情保持简单。
RE类型
通过基于应用程序的解析,我们将使用的主要数据类型称为RE ,用于正则表达式。 它代表一个解析器,并通过两种类型进行参数化:
data RE sa = ... s类型是指我们将要解析的基本单位。 由于我们将输入解析为单个String ,因此将为Char 。 那么a类型是解析元素的结果。 各个解析器对此有所不同。 我们可以使用的最基本的组合器是sym 。 这将解析您选择的单个符号:
sym :: s - > RE ss parseLowercaseA :: RE Char Char
parseLowercaseA = sym 'a' 要使用RE解析器,我们将match函数或其等效的等价字符=~ 。 如果我们可以匹配整个输入字符串,这些将返回Just值,否则返回Nothing :
>> match parseLowercaseA “a”
Just 'a'
>> “b” =~ parseLowercaseA
Nothing
>> “ab” =~ parseLowercaseA
Nothing -- (Needs to parse entire input) 谓词和字符串
自然,我们需要一些更复杂的功能。 除了解析单个输入字符外,我们还可以使用psym解析任何适合特定谓词的psym 。 因此,如果我们想读取不是换行符的任何字符,则可以执行以下操作:
parseNonNewline :: RE Char Char
parseNonNewline = psym (/= '\n') string组合器允许我们匹配特定的完整字符串,然后返回它:
readFeatureWord :: RE Char String
readFeatureWord = string “Feature” 尽管经常会最终放弃“结果”,但我们将使用它来解析关键字。
适用组合器
现在, RE类型适用。 这意味着我们可以在其上应用各种应用组合器。 其中之一是many ,这使我们可以多次应用一个解析器。 这是一个我们会经常使用的组合器。 它允许我们读取所有内容,直到换行符并返回结果字符串:
readUntilEndOfLine :: RE Char String
readUntilEndOfLine = many (psym (/= '\n')) 除此之外,我们还要利用适用的<*>运算符组合不同的解析器。 我们还可以使用<$>在这些函数之上应用纯函数(或构造函数)。 假设我们有一个存储两个字符的数据类型。 这是我们可以为其构建解析器的方法:
data TwoChars = TwoChars Char Char parseTwoChars :: RE Char TwoChars
parseTwoChars = TwoChars <$> parseNonNewline <*> parseNonNewline ... >> match parseTwoChars “ab”
Just (TwoChars 'a' 'b') 我们还可以使用<*和*> ,它们是主要应用运算符的近亲。 第一个将解析,但随后忽略右侧的解析结果。 第二个丢弃左侧结果。
parseFirst :: RE Char Char
parseFirst = parseNonNewline <* parseNonNewline parseSecond :: RE Char Char
parseSecond = parseNonNewline *> parseNonnewline … >> match parseFirst “ab”
Just 'a'
>> match parseSecond “ab”
Just 'b'
>> match parseFirst “a”
Nothing 注意最后一个失败,因为解析器需要两个输入! 我们将在一秒钟内回到失败的想法。 但是,既然我们知道这种技术,我们就可以编写另外两个有用的解析器:
readThroughEndOfLine :: RE Char String
readThroughEndOfLine = readUntilEndOfLine <* sym '\n' readThroughBar :: RE Char String
readThroughBar = readUntilBar <* sym '|' readUntilBar :: RE Char String
readUntilBar = many (psym (\c -> c /= '|' && c /= '\n')) 第一个将解析行的其余部分,然后使用换行符本身。 其他解析器完成相同的任务,但竖线字符除外。 下周解析“ Examples部分时将需要这些。
替代方案:处理解析失败
我们在上面介绍了解析器“失败”的概念。 当然,当解析器失败时,我们需要能够提供替代方案! 否则,我们的语言在结构上将非常有限。 幸运的是, RE类型还实现了Alternative 。 这意味着当一个失败时,我们可以使用<|>运算符来确定另一个解析器。 让我们看一下实际情况:
parseFeatureTitle :: RE Char String
parseFeatureTitle = string “Feature: “ *> readThroughEndOfLine parseScenarioTitle :: RE Char String
parseScenarioTitle = string “Scenario: “ *> readThroughEndOfLine parseEither :: RE Char String
parseEither = parseFeatureTitle <|> parseScenarioTitle … >> match parseFeatureTitle “Feature: Login\n”
Just “Login”
>> match parseFeatureTitle “Scenario: Login\n”
Nothing
>> match parseEither “Scenario: Login\n”
Just “Login” 当然,如果所有选项都失败,那么解析器仍然会失败!
>> match parseEither “Random: Login\n”
Nothing 我们将需要它来将某些级别的选择引入我们的解析系统。 例如,由用户决定是否将Background作为其功能的一部分。 因此,我们需要能够读取背景(如果有的话),否则就可以解析场景。
结论
到此结束了对应用程序解析的基本组合器的介绍。 下周,我们将介绍在这里开发的所有内容,并将它们用于Gherkin语法本身。 到目前为止,一切似乎都很小。 但是,我们将看到,一旦具备基本要素,我们就能非常迅速地建立结果!
如果您想查看更多对重要的Haskell任务有用的库,请查看我们的生产清单 。 它将向您介绍一些用于解析的库,数据库,API等等!
如果您是Haskell的新手,那就没有更好的开始时间了! 下载我们的免费初学者清单 ! 它将帮助您下载正确的工具并开始学习该语言。
From: https://hackernoon.com/applicative-parsing-i-building-the-foundation-23e8a9c2fb5d