考虑一个简单的Haskell Brainf*ck解释器。只需查看interpret
函数即可。
import Prelude hiding (Either(..))
import Control.Monad
import Data.Char (ord, chr)
-- function in question
interpret :: String -> IO ()
interpret strprog = let (prog, []) = parse strprog
in execBF prog
interpretFile :: FilePath -> IO ()
interpretFile fp = readFile fp >>= interpret
type BF = [BFInstr]
data BFInstr = Left | Right | Inc | Dec | Input | Output | Loop BF
type Tape = ([Integer], [Integer])
emptyTape = (repeat 0, repeat 0)
execBFTape :: Tape -> BF -> IO Tape
execBFTape = foldM doBF
execBF :: BF -> IO ()
execBF prog = do
execBFTape emptyTape prog
return ()
doBF :: Tape -> BFInstr -> IO Tape
doBF ((x:lefts), rights) Left = return (lefts, x:rights)
doBF (lefts, (x:rights)) Right = return (x:lefts, rights)
doBF (left, (x:rights)) Inc = return (left, (x+1):rights)
doBF (left, (x:rights)) Dec = return (left, (x-1):rights)
doBF (left, (_:rights)) Input = getChar >>= \c -> return (left, fromIntegral (ord c):rights)
doBF t@(_, (x: _)) Output = putChar (chr (fromIntegral x)) >> return t
doBF t@(left, (x: _)) (Loop bf) = if x == 0
then return t
else do t' <- execBFTape t bf
doBF t' (Loop bf)
simpleCommands = [('<', Left),
('>', Right),
(',', Input),
('.', Output),
('+', Inc),
('-', Dec)]
parse :: String -> (BF, String)
parse [] = ([], [])
parse (char:prog) = case lookup char simpleCommands of
Just command -> let (rest, prog') = parse prog
in (command : rest, prog')
Nothing ->
case char of
']' -> ([], prog)
'[' -> let (loop, prog') = parse prog
(rest, prog'') = parse prog'
in (Loop loop:rest, prog'')
_ -> parse prog
所以我有一个像interpret "[->+<]"
这样的函数。这给了我IO ()
一元动作,该动作执行了给定的程序。它具有正确的类型,可以成为某个程序的main
。
比方说,我想将此动作编译为可执行文件,也就是说,我想生成一个以interpret ...
为主要功能的可执行文件。当然,该可执行文件必须包含GHC运行时系统(用于无限列表,整数算术等)。
问题:
编辑
对不起,我在原始问题中过分简化了。当然,我可以这样写一个文件:
main = interpret "..."
但是,当我们尝试编译某些内容时,通常不会这样做,因此请考虑使用interpretFile :: FilePath -> IO ()
。让BF程序保存在文件(helloworld.bf
)中。
我该如何创建一个可执行文件,该可执行文件执行helloworld.bf
的内容而实际上不需要该文件?
$ ./MyBfCompiler helloworld.bf -o helloworld
答案 0 :(得分:2)
我不确定您是在问如何编写可将文件helloworld.bf
作为输入的编译器,还是如何编译运行helloworld.bf
的Haskell程序。 / p>
在前一种情况下,您想要的东西比这还充实一些:
import System.Environment (getArgs)
main :: IO ()
main = do
(_:fileName:_) <- getArgs
source <- readFile fileName
interpret source
interpret :: String -> IO ()
interpret = undefined -- You can fill in this piddly little detail yourself.
如果要使用后者,则有一些不同的选择。首先,您可以将*.bf
文件的内容存储在字符串常量(或更好的Text
或严格的ByteString
)中,并将其传递给您的解释器函数。如果GHC足够乐观地在编译时完全内联并扩展该调用,我会感到惊讶,但原则上Haskell编译器可以。
第二个方法是使用您定义的运算符将Brainfuck转换为特定领域的语言,以便您实际上可以编写类似
的内容interpret [^<,^+,^>,^.]
如果定义(^<)
和其他运算符,则Brainfuck命令将编译为表示Brainfuck程序的字节码。
在这种情况下,与第一种方法相比没有明显的好处,但是使用结构化的语言,您可以进行优化遍历,将源代码编译为基于堆栈的字节码,更适合于解释程序的执行,或者生成更复杂的AST。
您也可以将此想法表示为
interpret
(^< ^+ ^> ^.)
input
在这里,如果Brainfuck命令是具有从右到左优先级的高阶函数,并且interpret bf input = (bf begin) input
,则Brainfuck代码将简单地编译为解释程序调用的函数。这很有可能会变成快速的本机代码。
在某些情况下,编译器可以内联函数调用(GHC中有编译指示要其执行此操作)。如果命名闭包,编译器也更有可能执行您想要的操作,例如:
main = interpret foo
在GHC中,您可以通过添加
为编译器提供提示{-# INLINE main #-}
甚至
{-# INLINE interpret #-}
您可以检查通过使用-S
编译模块并查看源代码生成的GHC代码。
答案 1 :(得分:2)
答案基本上是。
有多种方法构造IO
值:
putStrLn
之类的功能return
或>>=
之类的Monad操作一旦有了IO值,就可以通过三种方法对其进行细分:
main
设置为等于值unsafePerformIO
所有这些分解为将IO a
转换为a
的过程。没有其他方法可以对其进行检查以查看其作用。
类似地,您对函数只能执行的操作是将其放入变量中或调用它们(或将它们转换为C函数指针)。
没有健全的方法来检查功能。
您可以做的一件不编译但要链接的事情是,使解释器主函数在某个外部c字符串上运行,将其构建为静态对象,然后您的“编译器”可以由此创建一个新对象程序中的C字符串,并将其链接到您已有的文件。
这种局部评估理论说,如果您对应用于输入的解释器的局部评估器进行局部评估,那么您得到的是编译器,但是ghc并不是足够高级的局部评估器。