プログラミング言語 Standard ML 入門 (問題の解答例)
16 データのフォーマッティング

16.2 文字列からのデータの読み込み

問 16.1

getc(char, substring) reader 型を持つことを確かめよ.

解答例  Substring.getcの型は substring -> (char * substring) option である。 一方 (’a, ’b) reader’b -> (’a * ’b) option であるから、 substring -> (char * substring) optionは、 (char, substring) readerである。

問 16.2

上記の関数を使用して,空白で区切られた数字データの文字列を受け取り, 呼び出されるごとに,そのデータから数字を1つずつ読み出す関数を返す関数

   readInt : string -> unit -> int option

を定義せよ. たとえば
val f = readInt "123 345 abc"; val f = fn : unit -> int option - f (); val it = SOME 123 : int option - f(); val it = SOME 345 : int option - f(); val it = NONE : int option  
のような振る舞いをする.

解答例  教科書にあるdecScanintScanの定義も含めた コード例を以下に示す。

   fun decScan x = Int.scan StringCvt.DEC x;
   val intScan = decScan Substring.getc
   fun readInt string =
     let
       val stream = ref (Substring.full string)
     in
       fn () =>
         case intScan (!stream) of
           SOME (i, substring) =>
           SOME i before stream := substring
         | NONE => NONE
    end
問 16.3

real 型および bool 型に対してもそれぞれ以下の型を持つ関数が定義 されている.

   Real.scan : (char,’a) reader -> (real,’a) reader
   Bool.scan : (char,’a) reader -> (bool,’a) reader

これらを用いて,ReadInt と同様の読み込み関数 readReal および readBool を定義せよ.

解答例  コード例を以下にしめす。 これら関数はすべて同型であるため、以下の例では、 高階の関数を定義し、それを各型のscan関数に適用している。

   fun makeRead scan string =
     let
       val reader = scan Substring.getc
       val stream = ref (Substring.full string)
     in
       fn () =>
         case reader (!stream) of
           SOME (i, substring) =>
           SOME i before stream := substring
         | NONE => NONE
    end
   val readReal = makeRead Real.scan
   val readBool = makeRead Bool.scan

以下はテスト実行例である。

   # val f = readReal "1.2 3E10 abc";
   val f = fn : unit -> real option
   # f ();
   val it = SOME 1.2 : real option
   # f ();
   val it = SOME 30000000000.0 : real option
   # f ();
   val it = NONE : real option
   # val f = readBool "true false abc";
   val f = fn : unit -> bool option
   # f ();
   val it = SOME true : bool option
   # f ();
   val it = SOME false : bool option
   # f ();
   val it = NONE : bool option
問 16.4

上記の各種の scan 関数は,対応するリーダに適用すればsubstring型以外のストリームデータに対してもそのまま使用することができる. これら種々のストリームに使用することができる以下の型を持つ汎用の 読み込み関数

   genericReadInt : (char,’a) reader -> ’a -> unit -> int option

を書け.

解答例 

   fun genericReadInt baseReader baseData =
     let
       val stream = ref baseData
       val reader = Int.scan StringCvt.DEC baseReader
     in
       fn () =>
          case reader (!stream) of
            SOME (i, data) =>
            SOME i before stream := data
          | NONE => NONE
     end

例えば、以下のように使用できる。

   # fun readIntFromString string = genericReadInt Substring.getc (Substring.full string);
   val readIntFromString = fn : string -> unit -> int option
   # val f = readIntFromString  "123 456 abc";
   val f = fn : unit -> int option
   # f ();
   val it = SOME 123 : int option
   # f ();
   val it = SOME 456 : int option
   # f ();
   val it = NONE : int option
問 16.5

ファイルの入力ストリームを受け取り, 入力ストリームから数字データを1つずつ読み出す関数を返す関数

   readIntFromStream : TextIO.instream -> unit -> int option

genericReadInt を使って定義せよ. genericReadInt を使い,空白で区切られた数字列を内容とする ファイル名を受け取り,ファイル内の各数字列を int 型データに変換し, そのリストを返す関数

   readIntFromFile : string -> int list

を書け.

解答例  TextIO.StreamIO.input1が、 (char, TextIO.StreamIO.instream) reader を持つので、この関数を使って、以下のように定義できる。

   fun readIntFromStream ins =
     let
       val stream = TextIO.getInstream ins
     in
       genericReadInt TextIO.StreamIO.input1 stream
     end

readIntFromFileの定義例を以下に示す。

   fun readIntFromFile file =
     let
       val ins = TextIO.openIn file
       val reader = readIntFromStream ins
       fun loop L =
           case  reader () of
             SOME i => loop (L @ [i])
           | NONE =>  L
     in
       loop nil before TextIO.closeIn ins
     end
問 16.6

parseHttp を参考にして, ローカルファイルシステムのファイルパスを表すURLの解析処理関数 parseFile,およびスキーマ指定のない相対アドレス解析処理関数 parseRelative を書き ParseUrl モジュールを完成せよ.

解答例 

   structure ParseURL =
   struct
   local
     structure SS = Substring
   in
     exception urlFormat
     datatype url
       = HTTP of {host : string list, path : string list option,
                  anchor : string option}
       | FILE of {path : string list, anchor : string option}
       | RELATIVE of {path : string list, anchor : string option}
     fun parseHttp s =
         let val s = if SS.isPrefix "://" s then
                       SS.triml 3 s
                     else raise urlFormat
             fun neq c x = not (x = c)
             fun eq c x = c = x
             val (host,body) = SS.splitl (neq #"/") s
             val domain = map SS.string (SS.tokens (eq #".") host)
             val (path,anchor) =
                 if SS.isEmpty body then (NONE,NONE)
                 else
                   let val (p,a) = SS.splitl (neq #"#") body
                   in (SOME (map SS.string (SS.tokens (eq #"/") p)),
                       if SS.isEmpty a then NONE
                       else SOME (SS.string (SS.triml 1 a)))
                   end
         in {host=domain, path=path, anchor=anchor}
         end
     fun parseRelative s =
         let val (path,anchor) =
                 let val (p,a) = SS.splitl (fn #"#" => false | _ => true) s
                 in (map SS.string (SS.fields (fn c => c = #"/") p),
                  if SS.isEmpty a then NONE else SOME (SS.string (SS.triml 1 a)))
              end
         in {path=path, anchor=anchor}
         end
     fun parseFile s =
         let val s = if SS.isPrefix ":/" s then SS.triml 2 s else raise urlFormat
             val (path,anchor) =
                 let val (p,a) = SS.splitl (fn #"#" => false | _ => true) s
                 in (map SS.string (SS.tokens (fn c => c = #"/") p),
                    if SS.isEmpty a then NONE else SOME (SS.string (SS.triml 1 a)))
                 end
         in  {path=path,anchor=anchor}
         end
     fun parseUrl s =
         let val s = SS.full s
             val (scheme,body) = SS.splitl (fn c => not (c = #":")) s
         in
           if SS.isEmpty body then
              RELATIVE (parseRelative scheme)
           else case SS.string scheme of
                  "http" => HTTP (parseHttp body)
                | "file" => FILE (parseFile body)
                | _  => raise urlFormat
         end
   end
   end