プログラミング言語 Standard ML 入門 (問題の解答例)
15 入出力処理

15.4 簡単な字句解析処理

問 15.5

getID を参考にして,数字列を読む込み関数

   getNum : TextIO.instream -> string

を定義せよ. ただし数字か否かの判定には,ライブラリ関数 Char.isDigit を使 用せよ.

解答例 

   fun getNum ins =
     let
       fun getRest s =
           case T.lookahead ins of
             NONE => s
           | SOME c =>
             if Char.isDigit c then
               getRest (s ^ T.inputN(ins,1))
             else s
     in
       DIGITS (getRest "")
     end
問 15.6

testLex の中で使われている toStringtoken 型データを文字列に 変換する関数である. toString を定義せよ.

解答例 

   fun toString tok =
       case tok of
           EOF => "EOF"
         | ID s => "ID(" ^ s ^ ")"
         | DIGITS s => "DIGITS(" ^ s ^ ")"
         | SPECIAL c => "SPECIAL" ^ Char.toString c ^ ")"
         | BANG => "BANG"
         | DOUBLEQUOTE => "DOUBLEQUOTE"
         | HASH => "HASH"
         | DOLLAR => "DOLLAR"
         | PERCENT => "PERCENT"
         | AMPERSAND => "AMPERSAND"
         | QUOTE => "QUOTE"
         | LPAREN => "LPAREN"
         | RPAREN => "RPAREN"
         | TILDE => "TILDE"
         | EQUALSYM => "EQUALSYM"
         | HYPHEN => "HYPHEN"
         | HAT => "HAT"
         | UNDERBAR => "UNDERBAR"
         | SLASH => "SLASH"
         | BAR => "BAR"
         | AT => "AT"
         | BACKQUOTE => "BACKQUOTE"
         | LBRACKET => "LBRACKET"
         | LBRACE => "LBRACE"
         | SEMICOLON => "SEMICOLON"
         | PLUS => "PLUS"
         | COLON => "COLON"
         | ASTERISK => "ASTERISK"
         | RBRACKET => "RBRACKET"
         | RBRACE => "RBRACE"
         | COMMA => "COMMA"
         | LANGLE => "LANGLE"
         | PERIOD => "PERIOD"
         | RANGLE => "RANGLE"
         | BACKSLASH => "BACKSLASH"
         | QUESTION => "QUESTION"
問 15.7

lex 関数の特殊文字の処理を補い,lex を完成せよ.

解答例 

   signature LEX = sig
     datatype token
       =  EOF                         | ID of string
       | DIGITS of string             | SPECIAL of char
       | BANG            (* ! *)      | DOUBLEQUOTE     (* " *)
       | HASH            (* # *)      | DOLLAR          (* $ *)
       | PERCENT         (* % *)      | AMPERSAND       (* & *)
       | QUOTE           (* ’ *)      | LPAREN          (* ( *)
       | RPAREN          (* ) *)      | TILDE           (* ~ *)
       | EQUALSYM        (* = *)      | HYPHEN          (* - *)
       | HAT             (* ^ *)      | UNDERBAR        (* _ *)
       | SLASH           (* \ *)      | BAR             (* | *)
       | AT              (* @ *)      | BACKQUOTE       (* ‘ *)
       | LBRACKET        (* [ *)      | LBRACE          (* { *)
       | SEMICOLON       (* ; *)      | PLUS            (* + *)
       | COLON           (* : *)      | ASTERISK        (* * *)
       | RBRACKET        (* ] *)      | RBRACE          (* } *)
       | COMMA           (* , *)      | LANGLE          (* < *)
       | PERIOD          (* . *)      | RANGLE          (* > *)
       | BACKSLASH       (* / *)      | QUESTION        (* ? *)
     val lex : TextIO.instream -> token
     val toString : token -> string
     val testLex : unit -> unit
   end

   structure Lex : LEX =
   struct
     structure T = TextIO
         datatype token
           = EOF                          | ID of string
           | DIGITS of string             | SPECIAL of char
           | BANG            (* ! *)      | DOUBLEQUOTE     (* " *)
           | HASH            (* # *)      | DOLLAR          (* $ *)
           | PERCENT         (* % *)      | AMPERSAND       (* & *)
           | QUOTE           (* ’ *)      | LPAREN          (* ( *)
           | RPAREN          (* ) *)      | TILDE           (* ~ *)
           | EQUALSYM        (* = *)      | HYPHEN          (* - *)
           | HAT             (* ^ *)      | UNDERBAR        (* _ *)
           | SLASH           (* \ *)      | BAR             (* | *)
           | AT              (* @ *)      | BACKQUOTE       (* ‘ *)
           | LBRACKET        (* [ *)      | LBRACE          (* { *)
           | SEMICOLON       (* ; *)      | PLUS            (* + *)
           | COLON           (* : *)      | ASTERISK        (* * *)
           | RBRACKET        (* ] *)      | RBRACE          (* } *)
           | COMMA           (* , *)      | LANGLE          (* < *)
           | PERIOD          (* . *)      | RANGLE          (* > *)
           | BACKSLASH       (* / *)      | QUESTION        (* ? *)
     fun skipSpaces ins =
         case T.lookahead ins of
           SOME c => if Char.isSpace c
                     then (T.input1 ins;skipSpaces ins)
                     else ()
         | _ => ()
     fun getID ins =
         let fun getRest s =
             case T.lookahead ins of
               SOME c => if Char.isAlphaNum c then
                            getRest (s ^ T.inputN(ins,1))
                         else s
             | _ => s
         in ID(getRest "")
         end
     fun getNum ins =
       let
         fun getRest s =
             case T.lookahead ins of
               NONE => s
             | SOME c =>
               if Char.isDigit c then
                 getRest (s ^ T.inputN(ins,1))
               else s
       in
         DIGITS (getRest "")
       end
     fun lex ins =
         (skipSpaces ins;
          if T.endOfStream ins then EOF
          else
           let
             val c = valOf (T.lookahead ins)
           in
             if Char.isDigit c then getNum ins
             else if Char.isAlpha c then getID ins
             else case valOf (T.input1 ins) of
               #"!" => BANG
             | #"\"" => DOUBLEQUOTE
             | #"#" => HASH
             | #"$" => DOLLAR
             | #"%" => PERCENT
             | #"&" => AMPERSAND
             | #"’" => QUOTE
             | #"(" => LPAREN
             | #")" => RPAREN
             | #"~" => TILDE
             | #"=" => EQUALSYM
             | #"-" => HYPHEN
             | #"^" => HAT
             | #"_" => UNDERBAR
             | #"\\" => SLASH
             | #"|" => BAR
             | #"@" => AT
             | #"‘" =>  BACKQUOTE
             | #"[" => LBRACKET
             | #"{" => LBRACE
             | #";" => SEMICOLON
             | #"+" => PLUS
             | #":" => COLON
             | #"*" => ASTERISK
             | #"]" => RBRACKET
             | #"}" => RBRACE
             | #"," => COMMA
             | #"<" => LANGLE
             | #"." => PERIOD
             | #">" => RANGLE
             | #"/" => BACKSLASH
             | #"?" => QUESTION
             | _ => SPECIAL c
           end)

     fun toString tok =
         case tok of
         EOF => "EOF"
           | ID s => "ID(" ^ s ^ ")"
           | DIGITS s => "DIGITS(" ^ s ^ ")"
           | SPECIAL c => "SPECIAL" ^ Char.toString c ^ ")"
           | BANG => "BANG"
           | DOUBLEQUOTE => "DOUBLEQUOTE"
           | HASH => "HASH"
           | DOLLAR => "DOLLAR"
           | PERCENT => "PERCENT"
           | AMPERSAND => "AMPERSAND"
           | QUOTE => "QUOTE"
           | LPAREN => "LPAREN"
           | RPAREN => "RPAREN"
           | TILDE => "TILDE"
           | EQUALSYM => "EQUALSYM"
           | HYPHEN => "HYPHEN"
           | HAT => "HAT"
           | UNDERBAR => "UNDERBAR"
           | SLASH => "SLASH"
           | BAR => "BAR"
           | AT => "AT"
           | BACKQUOTE => "BACKQUOTE"
           | LBRACKET => "LBRACKET"
           | LBRACE => "LBRACE"
           | SEMICOLON => "SEMICOLON"
           | PLUS => "PLUS"
           | COLON => "COLON"
           | ASTERISK => "ASTERISK"
           | RBRACKET => "RBRACKET"
           | RBRACE => "RBRACE"
           | COMMA => "COMMA"
           | LANGLE => "LANGLE"
           | PERIOD => "PERIOD"
           | RANGLE => "RANGLE"
           | BACKSLASH => "BACKSLASH"
           | QUESTION => "QUESTION"
     fun testLex () =
         let
           val token = lex (TextIO.stdIn)
         in
           case token of
             EOF => ()
           | _ => (print (toString token ^ "\n");
                   testLex ())
         end
   end
問 15.8

字句解析プログラムに,以下のコマンドを解釈しファイルから読み込む処理を 加えよ.
use fileName  
字句解析処理は,この行を入力すると,fileName で指定された ファイルをオープンし,ファイルから入力を続行する. たとえばtemp.txtファイルの中身が InFile であれば,以下のよ うな動作をする.
1 DIGITS(1) use temp.txt [opening file ”temp.txt”] ID(InFile) [closing file ”temp.txt”] 2 DIGITS(1)  
さらにこの use 構文はネストして使用してもよいものとする.

解答例  この対応には、lex関数ではなく、lexを使用するメイン関数、 すなわちtestLexの変更が必要である。 以下に変更例を示す。

   fun getFileName ins =
       let fun getRest s =
               case (T.lookahead ins) of
                 SOME c =>
                 if Char.isSpace c then s
                 else getRest (s ^ T.inputN(ins,1))
               | NONE => s
       in getRest "" end
    fun testMain ins =
        let
          val token = lex ins
        in
          case token of
             EOF => ()
           | ID "use" =>
               let
                 val fileName = (skipSpaces ins; getFileName ins)
                 val newIns = TextIO.openIn fileName
               in
                 (testMain newIns; TextIO.closeIn newIns; testMain ins)
               end
            | _ => (print (toString token ^ "\n");
                    testMain ins)
        end
    fun testLex () = testMain TextIO.stdIn