PureScriptでパーサーコンビネータを触る (2) テキストファイル

前回の記事 と合わせて1つの記事にする予定だったが、前回があまりに長くなってしまったので分割した。 ある書式に従ったテキストファイルをパースすることを考える。パースしたデータを整形し、HTML文書として出力するところまでやる。 前回インポートした関数で今回使うものは、(漏れが無ければ)以下の通り。 1 2 3 4 import Control.Alt ((<|>)) import Control.Lazy (defer) import Text.Parsing.Parser (Parser, fail) import Text.Parsing.Parser.String (char) テキストの仕様 テキストファイルは、複数のentryで構成される。 1つのentryはタイトルtitleと中身bodyで構成される。 titleは[と]でくくられる。 titleとbodyの間、bodyと次のentryのtitleの間には、1つの改行と、0個以上の空行があり得る。それらはbodyには含まない。 空行とは、0個以上のスペースだけから構成される行のことである。 BNF風に書くと次のようになるだろう。 1 2 3 4 5 6 <entries> = (0個以上の<entry>) <entry> = <title> "\n" <empty lines> <body> "\n" <empty lines> <title> = "[" (文字列) "]" <body> = (先頭、末尾が<empty lines>でないような文字列) <empty lines> = (0個以上の<empty line>) <empty line> = (0個以上のスペース) "\n" 例えば、以下のファイルがあったとする。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 [Title1] line1 line2 line3 line4 [Title2] line1 line2 [Title3] line1 line2 line3 これは次のようにパースされる。...

2021-12-31 · (updated 2021-12-31) · 9 min · 1750 words

PureScriptでパーサーコンビネータを触る (1) 四則演算のパース

PureScriptのパーサーコンビネータにpurescript-parsingがある。これはHaskellのParsecが基になっているので、使い方はParsecとほとんど同じだと思われる(とはいえ、Parsecを使ったことはあまりない)。これを用いて四則演算のパーサーを実装してみたが、うまく動かず詰まる点がいくつかあった。その備忘録。 パーサーコンビネータの準備 % spago install parsing 後々使うので以下のパッケージもインストール。 % spago install either integers maybe strings arrays lists src/Main.pursに以下の記述を追加。 1 2 3 4 import Text.Parsing.Parser (Parser) parser :: Parser String String parser = pure "Hello" REPLを起動して、動くか確認する。どんな文字列を食わせても"Hello"としか結果を返さないパーサーの完成。 > import Main > import Text.Parsing.Parser (runParser) > runParser "hoge" parser (Right "Hello") REPLでMain.pursをリロードする場合は:rをREPLで実行する。 数字のパース 1文字取得 1文字の数字を読み取りたいなら、Text.Parsing.Parser.Tokenにdigitがあるのでそれを使う。 1 2 3 4 5 import Text.Parsing.Parser.Token (digit) parser :: Parser String Char parser = digit > runParser "12345" parser (Right '1') 1文字以上取得 1文字以上を取得したいなら、Data....

2021-12-30 · (updated 2021-12-30) · 11 min · 2146 words

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....

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 } 関連する関数を定義。...

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 ‘....

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....

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

PureScriptとCanvasで作る『弾むボール』

英語だと “bouncing ball programming” とか検索すると出てくるやつ。 単にボールが弾むだけなのだが、思ったより勉強になったので書き残す。 実際のプログラムの動作はGitHub Pagesを参照。 プロジェクトの初期化 適当なディレクトリを作って、その中でプロジェクトを作成。 % spago init % spago build 今回は、追加のパッケージは必要なときにspago installコマンドで入れることにする。 Canvas入門 src/Main.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 41 42 module Main where import Prelude import Data.Maybe (Maybe(..)) import Effect (Effect) import Effect....

2021-06-17 · (updated 2021-06-17) · 12 min · 2530 words

Purescriptメモ - 配列のシャッフルを様々な方法で実装する

PureScriptで配列のシャッフルをしたい。型はこんな感じ。乱数は副作用を伴うため、返り値の型はEffectで包まれる。 1 shuffle :: forall a. Array a -> Effect (Array a) アルゴリズムはFisher-Yates ShuffleのModern Algorithmの項の2つ目を利用する。これをさまざまな方法で作成したところ、Functor, Applicative, Monadなどに関連する事項だったり、STモナドの使い方、FFIの使い方だったりが学べたので、備忘のために書く。 準備 適当なディレクトリでプロジェクトを作成する。今回使うパッケージをインストールする。 $ spago init $ spago install arrays $ spago install random $ spago install foldable-traversable 方法1: 素直(?)な書き方 ここでは、src/Shuffle.pursに記述する。 天下り的ではあるが、これから使う関数、型をimportしておく。 1 2 3 4 5 6 7 8 9 10 module Shuffle where import Prelude import Effect (Effect) import Data.Array (range, (!!), updateAt, length) import Data.Traversable (for) import Effect.Random (randomInt) import Data.Maybe (maybe) import Data....

2021-01-04 · (updated 2021-01-05) · 7 min · 1285 words

PureScriptメモ - Generics

purescript-generics-repパッケージを使ってGenericなSerializer型クラスを作った。以下はそのメモ。 準備 プロジェクトを作成。 $ spago init Arrayを使うので、パッケージをインストールしてsrc/Main.pursにimport文を書き込んでいく。 本記事の本命であるgenerics-rep入れる。 $ spago install arrays $ spago install generics-rep 1 import Data.Array ((:)) REPLで色々実験するので、あらかじめ起動しておく。 $ spago repl > import Main 以降はsrc/Main.pursに色々書き足していく。REPLで:r(もしくは:reload)とコマンドを打てばモジュールが再読み込みされるので、src/Main.pursを書き換える度にこのコマンドを打つと良い。 Serializer そもそもSerializerとは何か。ここでは単に「データをビット列に変換するもの」程度の意味で捉えれば良い。 厳密にはJSONなどの階層を持つデータを,文字列などの平坦なデータに変換するという意味合いとしてシリアライズ(直列化)という言葉を使う。実際、本記事では最終的に木構造をシリアライズする。 まずビットは次のように定義する。 1 2 3 4 5 data Bit = O | I instance showBit :: Show Bit where show O = "O" show I = "I" Serializer型クラスを以下のように定義する。 1 2 class Serializer a where serialize :: a -> Array Bit 試しに適当な型をつくり、それをSerializer型クラスのインスタンスにしてみる。...

2020-11-03 · (updated 2020-11-06) · 6 min · 1114 words

PureScriptでじゃんけんゲーム(CUI)を作る

プログラミングの初歩で作りそうなじゃんけんゲームを作る。ただし、PureScriptで作る。 方針 Jankenというモジュールを作る グー・チョキ・パーをHandとして定義する じゃんけんの勝負の結果をJudgementとして定義する コンピュータが出す手はランダムに出したいので、ランダムな手を出す関数randomを作っておく 入力は文字列にしたいので、文字列から手に変換する関数fromStringを作っておく 入出力はMainに書く。Node.ReadLineモジュールの力で入力を受け付ける。 準備 適当なプロジェクトディレクトリを作っておいて、 $ spago init /src/Main.pursと/src/Janken.pursを作っておく。 /src/Main.pursはとりあえず以下のようにしておく。 1 2 3 4 5 6 7 8 9 10 module Main where import Prelude import Effect (Effect) import Effect.Console (log) main :: Effect Unit main = do log "Hello" 次のコマンドでHelloが出力されることを確認する。 $ spago run Jankenモジュールの定義 この節では/src/Janken.pursを編集していく。 1 2 3 module Janken where import Prelude Handの定義 じゃんけんの手を表す型Handを定義する。 1 data Hand = Rock | Scissors | Paper 余談。これは公式ではタグ付き共用体と呼ばれているもの。Haskellでは代数的データ型と呼ばれているが、正直名前はどうでもいい。データをこのように表現すれば、「データはこの値しかとりえない」という制限が得られる。制限があれば、プログラムのバグも減らせる。たとえば、「グーを0、チョキを1、パーを2」として表現すると、万が一それ以外の値が来た場合に困る。上のようなHandの定義では、「それ以外の値」が入る余地すら与えない。…この話は、Elm Guideの受け売り。...

2020-06-25 · (updated 2020-06-25) · 3 min · 591 words