0

単純な 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 ランタイム システム (無限リスト、整数演算など) が含まれている必要があります。

質問:

  1. 私の意見では、モナド アクションを実行して新しいファイルとして保存することはまったく不可能です。これは本当ですか?
  2. 同等の解決策に到達するにはどうすればよいでしょうか? GHC API とヒントは役に立ちますか?

編集

申し訳ありませんが、元の質問を単純化しすぎました。もちろん、次のようなファイルを書くこともできます。

main = interpret "..."

しかし、これは何かをコンパイルしようとするときに通常行うことではないため、interpretFile :: FilePath -> IO ()代わりに検討してください。BF プログラムをファイル ( helloworld.bf) に保存するとします。

helloworld.bf実際にファイルを必要とせずにコンテンツを実行する実行可能ファイルを作成するにはどうすればよいですか?

$ ./MyBfCompiler helloworld.bf -o helloworld
4

2 に答える 2

2

答えは基本的にノーです。

IO値を構築するには多くの方法があります。

  1. 次のような組み込み関数putStrLn
  2. returnorのようなモナド演算>>=

IO 値を取得したら、それを分解する 3 つの方法があります。

  1. main値に等しく設定
  2. unsafePerformIO
  3. エクスポートされた C 関数の戻り値として

これらはすべて、 を に変換することに分解されIO aますa。それが何をするかを調べるためにそれを検査する他の方法はありません。

同様に、関数でできることは、関数を変数に入れるか、呼び出す (または C 関数ポインターに変換する) ことだけです。

それ以外の場合、関数を検査する正気の方法はありません。

コンパイルではなくリンクでできることの 1 つは、インタープリターのメイン関数を外部の c 文字列で実行し、それを静的オブジェクトに構築することです。その後、「コンパイラ」は、この C 文字列を使用して新しいオブジェクトを作成できます。その中のプログラムと、それをあなたがすでに持っているものにリンクします。


ある入力に適用されたインタープリターに適用された部分評価器の部分評価を行うと、得られるのはコンパイラーですが、ghc は十分に高度な部分評価器ではないという、部分評価のこの理論があります。

于 2018-07-19T08:47:03.920 に答える