プログラミング言語 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