开始使用 Parsec 进行解析
我不会为你写出全部内容,但我会让你从每一点开始。我们将经历的三个阶段是:
- 定义语法
- 制作一个抽象语法树。 (这看起来像语法,所以会很简单。)
- 编写一个解析器。 (这看起来也像语法,所以会很简单。)
我们可以在 2 和 3 之间创建一个单独的词法分析阶段,但 Parsec 很乐意同时完成这两个级别。词法分析是将输入拆分为标记(输入中有意义的位)的地方,相当于人类语言中的单词,也称为词位。跳过单独的词法分析阶段意味着我们需要对空格等更加明确。
1. 语法
首先您需要定义语法。最好用纸和铅笔完成,但我会帮助你开始:
program ::= line {[newline line]}
line ::= num dot whitespace statement
statement ::= declaration | write | ifstatement | goto | assignment | stop
declaration = "Int" whitespace varname equals int
varname = letter {[alphanum]}
-- more things here, including the more interesting ifstatement:
ifstatement = "if" whitespace expression whitespace equals expression whitespace statement
-- lower level stuff:
dot = "."
int = digit {[digit]}
digit = "0" | "1" | ... | "9"
whitespace = " " okspace | "\t" okspace
okspace = "" | whitespace
考虑一下它如何与您的示例程序相匹配,并考虑如何完成它:
1. Int n=5
2. write n
3. Int fac=1
4. if 0 n goto 8 -- unusual
5. fac := fac * n
6. n := n+1 -- complex
7. goto 4
8. write fac
9. stop
第 4 行中的 if 语句不寻常,因为没有=
or ==
在里面。也许这是为了简化语法,它只能接受单个变量或整数,中间有一个空格。也许这是一个错字,您的意思是有一个等号和任意表达式。找出并重写ifstatement
语法的一部分。
第 6 行的赋值很复杂,因为这里你必须解析任意算术表达式。据我记得有很多这样的例子,所以我很乐意暂时跳过它。如果您遇到了这一点,请提出另一个问题,但希望您首先可以通过其余部分建立您的解析技能。
2. 抽象语法树(AST)
抽象语法树表示组成输入的标记的组合。在 Haskell 中,我们可以定义自己的以适应上下文,这将使生活变得更加简单
我其实是编译这个答案 https://stackoverflow.com/questions/12666723/i-taught-ghci-to-compile-my-stackoverflow-posts-can-i-make-it-slicker(检查拼写错误等的好方法),这就是为什么我需要在代码顶部进行一些声明:
module ParseLang where
import Text.Parsec hiding (Line)
import Text.Parsec.String
import Control.Applicative hiding ((<|>), many)
我们只会做一个Program
的列表Line
s,但强制解析器必须至少有一个。
type Program = [Line]
For a Line
,它需要一个数字和一个语句,但点只是我们不需要存储的语法。我们可以将行号存储为Int
,因此尽管允许在类型声明中使用负数,但解析器同样不会接受负数。
data Line = Line {aNum::Int, aStatement :: Statement}
deriving Show
多个选项很容易定义:
data Statement = Declaration VarName Int
| Write VarName
| IfStatement Expression Expression Statement
| Goto LineNumber
| Assignment VarName Expression
| Stop
deriving Show
请注意,没有所有语法错误/连接词/等号,仅留下更改的位。
我就到此为止了——你可以完成:
data Expression = Expression -- I've left this one for you
deriving Show
type VarName = String -- could use newtype for type safety for these to
type LineNumber = Int
较低级别的语法不需要在 AST 中表示,因为我们将使用字符串来表示。
3. 解析器
现在这一点既美好又简单。让我们从语法树的底部开始并逐步展开。
num :: Parser Int
num = read <$> many digit
我们用过<$>
,这是一个同义词fmap
我们通过导入得到的Control.Applicative
。在这里,它使用左侧的纯函数更改解析器返回的值,在本例中,read
。看一下这个另一个答案 https://stackoverflow.com/questions/13134825/how-do-functors-work-in-haskell/13137359#13137359的介绍fmap
如果你不习惯的话。
让我们创建一个解析器来解析字符串文字然后解析一些空格:
whitespace = space >> spaces -- a space then optional spaces
lit :: String -> Parser String
lit xs = string xs <* whitespace
现在<*
很有趣。看起来像<*>
,它实际上结合了两个解析器,并与<$>
这实际上将纯函数映射到结果上。*>
and <*
组合两个解析器,但忽略其中一个解析器的输出,所以string "goto" <* whitespace
解析一个"goto"
和一些空格,但丢弃了空格。
现在我们准备解析 goto 语句:
goto :: Parser Statement
goto = Goto <$> (lit "goto" *> num)
现在我们来看看 varName
varName :: Parser VarName
varName = (:) <$> letter <*> many (alphaNum <|> oneOf "'_")
那里正在发生一些事情。
1. <|>
是替代选择 - 一个或另一个,所以(alphaNum <|> oneOf "'_")
接受字母数字字符或一对无辜字符之一'
and _
您可能想包含在变量名称中。
2. f <$> parser1 <*> parser2
是组合解析器的一种非常好的方法。它运行parser1,然后运行parser2,然后映射函数f
他们产生的结果。它适用于许多解析器:
--ifstatement = "if" whitespace expression whitespace equals whitespace expression whitespace statement
ifStatement :: Parser Statement
ifstatement = IfStatement
<$> (lit "if" >> expression)
<*> (lit "=" >> expression) -- I put = in, but see below
<*> (whitespace >> statement) -- I'd be happier with a then in here
如果您只允许VarName
or Int
而不是一般的Expression
,那么就不需要等号。
以下是如何将它们组合在一起:
statement :: Parser Statement
statement = goto
<|> stop
<|> declaration
<|> write
<|> ifStatement
<|> assignment
--program ::= line {[newline line]}
program :: Parser Program
program = many1 line
--line ::= num dot whitespace statement
line :: Parser Line
line = Line <$> (num <* char '.') <*> (statement <* char '\n')
但是,每次您尝试使用尚未完成的解析器时,我都会给您留下错误消息,因此整个事情都会编译正常,并且您定义的位应该可以工作。
stop = error "You've not yet defined stop"
declaration = error "You've not yet defined declaration"
write = error "You've not yet defined write"
ifStatement = error "You've not yet defined ifStatement"
assignment = error "You've not yet defined assignment"
expression = error "You've not yet defined expression"