プログラミング言語 Standard ML 入門 (問題の解答例)
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 の中で使われている toString は token 型データを文字列に 変換する関数である. 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