我正在尝试使用Haskell创建一个编译器,作为我大学课程的一部分。
我想创建一个匹配任何字符串的方法:
int a = 5
int foo = 3
所以这就是我创建的方法:
readInstruction :: String -> String
readInstruction ( 'i' : 'n' : 't' : ' ' : varName : ' ' : '=' : ' ' : val : []) =
"Declare Int " ++ [varName] ++ " = " ++ [val]
但是这仅适用于1个字母的变量名称。我该怎么做?
另外,作为附注,我还注意到以下内容无法编译:
readInstruction ( "int " ++ varName ++ " = " ++ val ) =
"Declare Int " ++ varName ++ " = " ++ val
为什么?
请注意,我是Haskell的新手,只知道基础知识。我不知道任何其他库函数,并且不想使用它们(因为我不鼓励将它们用于我的课程)。
答案 0 :(得分:4)
有很多方法可以解决这个问题....这将是我的偏好顺序。
使用解析器库,如Parsec。
使用正则表达式。
使用诸如splitAt
之类的前奏函数。
但是既然你不能使用任何libs,那么你将不得不使用凌乱的解决方案4。
您已经向我们展示了如何匹配int
部分,因此您只需要剩余的内容。由于这是作业,我不会给你答案,但给你一个可能的大纲。
您可以做的是将问题分解为多个部分,并编写多个类型
的匹配器函数showPart::String->String
其中的部分类似于showVarName
,showEq
等。每个部分都需要消耗部分文本,然后调用下一部分(所以最终你只需要调用第一部分,其余的woulc按顺序消耗)。上面提到的唯一大修改就是需要在showVarName
等可变长度部分进行递归。
showVarName (c:rest) | isAlphanum c = c ++ showVarName
showVarName x = .... --call the next part here
(是的,我添加了一个新函数isAlphaNum
....你需要类似的东西,尽管可以根据需要使用模式匹配来编写)
这将解决问题,但请注意解决方案将非常脆弱....很难对零件的排序,类型进行任何更改(如果RHS可以是变量,那该怎么办? ,或者一个完整的表达式),允许的格式(即,如果varname可以是[alpha] [alphaNum] *),或者输出(如果你想输出一个完全解析的表达式树,那么在那里使用它)多种方式,包括将其插入show
函数)。
在实践中,没有人会真正用这种方式解析,我假设这可能是你教授的课程之一。可能正试图为你说明。
答案 1 :(得分:3)
当您进行模式匹配时,您只能在构造函数上进行模式匹配。对于列表,您的两个构造函数是:
和[]
,而++
是列表中的函数。编译器不能从函数应用程序向后工作,但它可以来自构造函数应用程序(一种非常特殊的函数,甚至可以存在于Haskell中自己的命名空间中)。
更好的替代方法是将输入标记化,这样可以防止错误模式不足,并且从长远来看更容易处理。特别是因为您想要编写编译器,所以应该使用tokenizer,因为这几乎是编写解析器的可接受方式。你可以改为
-- A very simple tokenizer, only splits on whitespace
-- so `int x=1` won't be tokenized correctly
tokenize :: String -> [String]
tokenize = words
readInstructions :: [String] -> (String, [String])
readInstructions ("int" : varName : "=" : val : rest) = ("Declare Int" ++ varName ++ " = " ++ val, rest)
readInstructions otherPatterns = undefined
我返回(String, [String])
的原因是您可以迭代地应用readInstructions
并让它只消耗每个命令所需的令牌数。所以你可以做到
main = do
program <- readFile "myProgram.prog"
let tokens = tokenize program
(firstInstr, tokens') = readInstructions tokens
(secondInstr, tokens'') = readInstructions tokens'
putStrLn firstInstr
putStrLn secondInstr
如果您认为这看起来很费力,那么您是对的。这是因为在Haskell中有更好的方法来处理这种事情,而且非常优雅。完成作业后,我会鼓励您查看Parsec库和State monad。 Parsec库专门为你编写了一个标记器并将这些标记转换成有意义的东西,并且状态monad就是库真正建立在它之上的。对状态monad有一个很好的理解将帮助你成为一名Haskell程序员,因为它在很多不同的问题上被大量使用。