我最近开始学习Haskell,我正在尝试使用Haskell为FORTH语言编写解释器。但是我在尝试运行最基本的操作时遇到了问题。例如,FORTH中的一个简单程序(在Haskell中作为字符串)将如下:“1 2 +”返回包含整数3的堆栈,在Haskell中可以表示一个整数列表:[3]。
我还有从堆栈中推送,删除和弹出元素的功能。我有一个添加函数来从堆栈中添加两个元素。
现在,手头有这些功能,如何解析简单程序“1 2 +”才能返回[3]?我希望这个例子看起来像这样:
forthInterpreter "1 2 +"
[3]
任何帮助都会非常感激。如果您有任何澄清问题,请告诉我。 感谢。
答案 0 :(得分:5)
您拥有的每个命令都是来自Stack -> Stack
的函数。这使得解释命令变得容易。我使用[Int]
作为Stack
的类型,其中数字位于列表顶部的堆栈顶部。
interpretCommand :: String -> [Int] -> [Int]
interpretCommand "+" = lift2 (+)
interpretCommand "-" = lift2 (-)
interpretCommand "*" = lift2 (*)
interpretCommand "/" = lift2 div
interpretCommand "MOD" = lift2 mod
interpretCommand number = \zs -> read number : zs
其中lift2
将2参数函数提升到列表前2个元素的函数。参数x
和y
从第seems to have the convention开始交换,第一个参数被推入堆栈是函数的第一个参数,堆栈顶部的参数是第二个参数。功能。
lift2 :: (a -> a -> a) -> [a] -> [a]
lift2 f (x:y:zs) = f y x:zs
lift2 f _ = error "not enough data on the stack"
为了解释多个命令,我们用words
将它们分开,它在空白处拆分一个字符串,解释每个命令,然后将它们组合在一起。
composition :: [a -> a] -> a -> a
composition = foldl (flip (.)) id
interpretCommands :: String -> [Int] -> [Int]
interpretCommands = composition . map interpretCommand . words
然后我们可以通过调用interpretCommands
并将初始(空)堆栈传递给它来解释一串命令。以下是两个例子。
main = do
print $ interpretCommands "1 2 +" []
print $ interpretCommands "1 2 + 4 - 3" []
print $ interpretCommands "10 5 / 3" []
print $ interpretCommands "13 7 MOD 2 / 4 *" []
输出
[3]
[3,-1]
[3,2]
[12]
请注意,[3,-1]
的顺序与您建议的顺序相反,因为3
位于堆栈顶部,因此位于列表的开头。 (这是您在fourthadd
的评论中预测的行为。)
答案 1 :(得分:1)
与Cirdec相同的答案,但也许这对初学者来说更容易阅读:
-- Data type to represent a command.
data Command = Push Int | Add | Subtract deriving Show
-- Type synonym: a Program is a list of commands.
type Program = [Command]
-- Type synonym: a Stack is a list of Int.
type Stack = [Int]
-- The interpreter entry point.
interpretCommands :: String -> Int
interpretCommands str = runProgram (parseProgram str)
-- Parse a string and turn it into the corresponding Program.
parseProgram :: String -> Program
parseProgram str = map toCommand (words str)
where toCommand "+" = Add
toCommand "-" = Subtract
toCommand x = Push (read x)
-- Run a Program on the empty stack, return the result at the top of the result stack.
runProgram :: Program -> Int
runProgram program = head (runProgram' program [])
-- Run a program on a given stack, return the result stack.
runProgram' :: Program -> Stack -> Stack
runProgram' [] stack = stack
runProgram' (command:rest) stack = runProgram' rest (runCommand command stack)
-- Run an individual command.
runCommand :: Command -> Stack -> Stack
runCommand (Push n) stack = n:stack
runCommand Add (n:m:stack) = n+m:stack
runCommand Subtract (n:m:stack) = n-m:stack
我已将该计划分为两部分:parser
和runProgram
。第一个接受字符串并将其转换为要运行的程序的抽象表示。第二个解释这个抽象表示。这是一个很好的分离用于这样的问题;例如,当您希望编写更好的解析逻辑时,您可以修改parser
,而不必担心违反runProgram
逻辑。