PureScriptで作るBrainfuckインタプリタ 4/4 Halogenの利用

Halogenの利用 続いて、GUIでBrainfuckを動かすことをやってみる。 GUIのフレームワークとして、ここではpurescript-halogenを使ってみる。 Halogenについてはまだ勉強中で、この記事は解説記事というより勉強記録である(いままでの記事もそうではあるのだが)。 % spago install halogen 雛形 src/Main.pursを以下のようにする。 ここのコードはほとんどHalogen Guideと同様。 関数名的におそらく、body要素を取得して、その中でcomponentを走らせるのだと思う。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 module Main where import Prelude import Effect (Effect) import Halogen.Aff (awaitBody, runHalogenAff) as HA import Halogen.VDom.Driver (runUI) import Component (component) main :: Effect Unit main = HA.runHalogenAff do body <- HA.awaitBody runUI component unit body componentはsrc/Component.pursで定義する。とりあえず雛形を作成。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 module Component where import Prelude import Halogen as H import Halogen.HTML as HH data Action = Dummy type State = {} initialState :: forall input. input -> State initialState _ = {} component :: forall query input output m. H.Component query input output m component = H.mkComponent { initialState , render , eval: H.mkEval $ H.defaultEval { handleAction = handleAction } } render :: forall m. State -> H.ComponentHTML Action () m render state = HH.p_ [ HH.text "Hello" ] handleAction :: forall output m. Action -> H.HalogenM State Action () output m Unit handleAction = case _ of Dummy -> pure unit Halogen Guide 02の2段落目によると、Componentとは、 「入力を受け付けて、HTMLを生成するもの」のこと。さらにComponentは内部状態を持っている。そして何かイベントを定義することができ、それに応じて内部状態を変えたり、副作用を起こしたりできる。 ...

2021-07-09 · (updated 2021-07-09) · 12 min · 2530 words

PureScriptで作るBrainfuckインタプリタ 3/4 CUIでの可視化

動作の可視化 インタプリタ動作中における内部状態を可視化できると面白い。 そこで、インタプリタ動作中のログを出力できるような仕組みを作る。 ログは以下のタイミングで起こるようにする。 onStart: インタプリタが動作したときに起こる。 onState state: 各ステップで状態を取得したときに起こる。 onCmd cmd: 各ステップで命令を取得できたときに起こる。 onEnd: インタプリタが終了するときに起こる。 これらはイベントリスナのように、関数の形で指定する。 Logの作成 src/Brainfuck/Interp/Log.pursを作成。 以下のimport文を書く。 1 2 3 4 5 6 7 8 9 module Brainfuck.Interp.Log where import Prelude import Brainfuck.Interp (Interp) import Brainfuck.State (State) import Brainfuck.Command (Command) import Effect.Class (class MonadEffect, liftEffect) import Effect.Console (log) Logを定義。 1 2 3 4 5 6 newtype Log m = Log { onStart :: Interp m Unit , onState :: State -> Interp m Unit , onCmd :: Command -> Interp m Unit , onEnd :: Interp m Unit } 関連する関数を定義。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 logStart :: forall m. Log m -> Interp m Unit logStart (Log { onStart }) = onStart logState :: forall m. Log m -> State -> Interp m Unit logState (Log { onState }) = onState logCmd :: forall m. Log m -> Command -> Interp m Unit logCmd (Log { onCmd }) = onCmd logEnd :: forall m. Log m -> Interp m Unit logEnd (Log { onEnd }) = onEnd いくつかのLog mを作っておく。 ...

2021-07-06 · (updated 2021-07-09) · 12 min · 2395 words

PureScriptで作るBrainfuckインタプリタ 2/4 CUIでの入出力

入出力用のストリーム作成 例えば出力だけ考えてみると、まず考えられるのは単純に、 logで出力することである。しかしlog以外の選択肢も考えられる。 logでコンソール出力するだけでなく、Webページのテキスト上で出力したり、テキストファイルに吐き出したりできるような汎用性が持たせられると良い。 そこで今回は、いわゆる「ストリームオブジェクト」のようなものを作って、そこから入出力を行うような設計にしてみる。 Streamの作成 src/Brainfuck/Interp/Stream.pursを作成。この後使うモジュールをインポート。 1 2 3 4 5 module Brainfuck.Interp.Stream where import Prelude import Brainfuck.Interp (Interp) Stream型を作成する。これは入出力を束ねた型になっている。 inputは、外部からの入力を1文字受け取る。 outputは、Charの値を外部に出力する。 1 2 3 4 newtype Stream = Stream { input :: Interp Char , output ::Char -> Interp Unit } Streamを通じてデータを読み書きする関数を作成。 1 2 3 4 5 6 7 read :: Stream -> Interp Char read (Stream { input }) = input write :: Char -> Stream -> Interp Unit write c (Stream { output }) = output c 1 2 3 4 5 6 defaultStream :: Stream defaultStream = Stream { input, output } where input = pure 'N' -- Not Implemented output _ = pure unit -- Not Implemented ‘.‘と’,’ src/Brainfuck/Interp/Command.pursを修正する。まず以下のインポート文を追加。 1 2 3 import Brainfuck.Interp.Util (readCharOrFail) import Brainfuck.Interp.Stream (write, read, Stream) import Data.Char (toCharCode) as Char StreamはEnvのレコードのフィールドとして扱いたいところだが、 それをやるとBrainfuck.Interp.Stream、Brainfuck.Env、Brainfuck.Interpとでcircular importとなってしまう。 仕方ないのでinterpCommandの引数で扱うことにする。 ...

2021-07-05 · (updated 2021-07-06) · 7 min · 1343 words

PureScriptで作るBrainfuckインタプリタ 1/4 基礎部分の作成

Brainfuckの記事ではあるが、実はモナド変換子を使ってみたかっただけだったりする。 以下の3部の記事で構成されている。 インタプリタと基本的な命令の実装 (この記事) CUIでの入出力処理の実装 CUIでのインタプリタ可視化 Halogenを用いた入出力処理の実装 この記事でインタプリタの基本的な部分を実装し、 残りの3記事はインタプリタとはあまり関係ない話となる (とはいえ出力ができないと Hello, World すら書けないので、必要な記事ではある)。 Brainfuckインタプリタの構造 Brainfuckインタプリタは以下の情報を内部に持っているものとする。 program: 命令の列。 iptr: インストラクションポインタ。実行する命令の位置を示す。プログラムカウンタみたいなもの。 dptr: データポインタ。メモリ上のある位置を示す。 memory: メモリ。 インタプリタは以下の手順を踏む。 iptr番目の命令をprogramから読み取る。 読み取れなかったらプログラムを終了する。 命令に応じてmemory、dptrの書き換えだったり、入出力を行う。 iptrを1進め、手順1に戻る。 どんな命令があるのかについてはWikipedia参照。 準備 適当なディレクトリを作って、プロジェクトの初期化を行う。 % spago init 命令列の作成 src/Brainfuck/Command.pursを作成する。 Commandを定義。Showクラスのインスタンスにして、Charからの変換をする関数を作る。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 module Brainfuck.Command where import Prelude data Command = IncPtr -- "+" | DecPtr -- "-" | IncDat -- ">" | DecDat -- "<" | LBrace -- "[" | RBrace -- "]" | Output -- "." | Input -- "," | Nop -- otherwise instance Show Command where show = case _ of IncPtr -> ">" DecPtr -> "<" IncDat -> "+" DecDat -> "-" LBrace -> "[" RBrace -> "]" Output -> "." Input -> "," Nop -> "nop" fromChar :: Char -> Command fromChar = case _ of '>' -> IncPtr '<' -> DecPtr '+' -> IncDat '-' -> DecDat '[' -> LBrace ']' -> RBrace '.' -> Output ',' -> Input _ -> Nop 続いて、src/Brainfuck/Program.pursを作成。この後使う関数をまとめて読み込んでおく。 ...

2021-07-04 · (updated 2021-07-06) · 10 min · 2017 words