top page > computer > haskell > web_lecture > for_programmer > nsc_integer.html
更新日:
文責: 重城良国

Nano Scheme: 整数値の表示

(工事中 30%)

はじめに

整数値の入力と表示を実装する。

型Value

Environment.hs

モジュールEnvironmentではNano Schemeで使う値や環境について定義する。Env型の値は変数と値を対応づける辞書だ。

module Environment (Env, M.fromList, Value(..), showValue, Symbol) where

import qualified Data.Map as M

type Env = M.Map Symbol Value

型Valueを定義する。いまは整数値のみだ。

data Value =
Int Integer

専用の表示関数も用意する。

showValue :: Value -> String
showValue (Int i) = show i

型Symbolを型Stringの別名として定義する。

type Symbol = String

% ghci Environment.hs
*Environment> showValue $ Int 8
"8"

モジュールParse

Parse.hs

モジュールParseでは字句解析器と構文解析器を定義する。公開する名前のリストを設定し、必要なモジュールを導入しておく。

module Parse (Token) where

import Data.Char

import Environment
import Maybe

型Token

字句解析器から構文解析器にわたされる値だ。整数値のトークンを定義する。

data Token
= TkInt Integer
deriving Show

関数tokens

関数tokensは文字列をトークン列にわける。

tokens :: String -> Maybe [Token]
tokens (c : s)
| isDigit c = let (t, r) = span isDigit s in
(TkInt (read $ c : t) :) `mapply` tokens r
| isSpace c = tokens s
tokens "" = Just []
tokens _ = Nothing

1文字目が数字ならばspan isDigitで数字だけの部分tと残りrとにわける。read $ c : tで整数値に変換してTkIntでValue型の値とする。その値を残りの文字列rから作られたトークン列に追加すれば良い。演算子:によって追加できるがtokens rはMaybe値を返すのでmapplyを使ってJustのなかみに追加するようにする。

isSpace cの節は空白文字を読みとばしている。

% ghci Parse.hs
*Parse> tokens "135 459 231"
[TkInt 135,TkInt 459,TkInt 231]
*Parse> tokens "135 can't tokenize it"
Nothing

数字の並びをトークン列に変換した。数字と空白文字以外があると字句解析は失敗しNothingとなる。公開する名前のリストにtokensを追加する。

module Parse (Token, tokens) where

関数parse

関数parseは複数の式をパースして値のリストをかえす。ひとつの式をパースする関数parse1を作る。

parse1 :: [Token] -> Maybe (Value, [Token])
parse1 (TkInt i : ts) = Just (Int i, ts)
parse1 _ = Nothing

整数値しかないので整数値のトークンを整数値の値とするだけだ。関数parseは関数parse1をくりかえし適用する。

parse :: [Token] -> Maybe [Value]
parse [] = Just []
parse ts = case parse1 ts of
Just (v, ts') -> (v :) `mapply` parse ts'
_ -> Nothing

parse1 tsがJust値を返したとき(Just (v, ts') ->)、残りのトークンであるts'をさらにパースした結果(リスト)の先頭に結果vを追加する。Just値のなかに追加するのでmapplyを使う。

*Parse> :reload
*Parse> map showValue `mapply` (parse `mbind` tokens "123 45 678 9")
Just ["123","45","678","9"]

parseやtokensはMaybe値を、map showValueはふつうの値を返す。mapplyとmbindを使いわける。公開する名前のリストにparseを追加する。

module Parse (Token, tokens, parse) where

初期環境

Primitive.hs

初期環境にはじめから定義されている変数をプリミティブと呼ぶ。いまのところは空だ。

module Primitive (env0) where

import Environment

env0 :: Env
env0 = fromList [
]

関数scheme

nsc.hs

拡張機能TupleSectionsを利用する。

{-# LANGUAGE TupleSections #-}

とする。必要なモジュールを導入する。

import Primitive
import Parse
import Environment
import Maybe

Nano Scheme(以下nscとする)は複数の式を評価しその結果を1行ずつ出力する。ソースコードの文字列と環境をとって複数の値と新しい環境を返す関数schemeを作る。構文解析や評価中にエラーが生じる可能性がある。返り値はMaybe値だ。

scheme :: String -> Env -> Maybe ([Value], Env)
scheme src e = (, e) `mapply` (parse `mbind` tokens src)

まだ「評価」はない。(, e) `mapply`で引数の環境をそのまま結果に追加する。parse `mbind` tokens srcでソースコードを字句解析し構文解析している。

main :: IO ()
main = interact $ maybe "err" (unlines . map showValue . fst)
. (`scheme` env0)

関数mainではinteractでString -> String型の関数をIO値に変換する。変換される関数の中身は(`scheme` env0)によってソースコードからMaybe ([Value], Env)型の値を作り、Nothingなら文字列"err"を返しそうでなければ、タプルのひとつめ(fst)であるValue型のリストの中身のすべてを文字列に変換(map showValue)してそれをunlinesで一行ずつの文字列にする。

試してみる

数値を構文解析して表示するコマンドが作れた。

% echo '33 45 3 921' | runghc -Wall -fno-warn-tabs nsc.hs
33
45
3
921

-Wallオプションは警告の表示を増やす。-fno-warn-tabsオプションはタブ文字使用の警告を抑制する。

Hubot

Hubot用にmain関数に手を加える。

nscという文字列より後ろだけを取り出す関数rmvPrfxを作る。

rmvPrfx :: String -> String
rmvPrfx ('n' : 's' : 'c' : s) = s
rmvPrfx (_ : s) = rmvPrfx s
rmvPrfx _ = ""

Hubotで「入力される文字列」すべてを対象にすると処理が終わらなくなる。最初の1行だけを取り出す。head . linesを追加する。

main = interact $ maybe "err" (unlines . map showValue . fst)
. (`scheme` env0) . rmvPrfx . head . lines

試してみる

Hubotで試してみよう。hellobot/haskell以下にnano_schemeディレクトリを作成しそこにモジュールファイルをコピーする。必要なcoffeeスクリプトコードをscriptsディレクトリ下に置く。

nano_scheme.coffee

spawn = require('child_process').spawn

module.exports = (robot) -> robot.hear /nsc/i, (msg) ->
echo = spawn 'echo', [msg.message]
nsc = spawn 'runghc', ['-ihaskell/nano_scheme', 'haskell/nano_scheme/nsc.hs']
echo.stdout.on 'data', (data) -> nsc.stdin.write(data)
nsc.stdout.on 'data', (data) -> msg.send data.toString()

-ihaskell/nano_schemeオプションでモジュールをさがすディレクトリを教える。Hubotを再起動して試す。

nsc 123 4 56

のようにするとHubotは

123
4
56

と答える。

まとめ

整数値の表示までできた。字句解析や構文解析を含む枠組のスタブ的な実装を作った。

「Nano Scheme: Map型」へもどる 「Nano Scheme: 変数の評価」へ

正当なCSSです! HTML5 Powered with CSS3 / styling, and Semantics