|
<プログラム> | ::= | <式> ;; |
<式> | ::= | <識別子> |
| | <整数リテラル> | |
| | <真偽値リテラル> | |
| | <式1> <二項演算子> <式n> | |
| | if <式1> then <式2> else <式3> | |
| | (<式>) | |
<二項演算子> | ::= | + | * | < |
3;; true;; x;; 3 + x';; (3 + x1) * false;;また,+, * は左結合,結合の強さは,強い方から,*, +, <, if式 とする.
|
id は変数の識別情報を示すための型で,ここでは変数の名前を表す文字列 としている.(より現実的なインタプリタ・コンパイラでは,変数の型などの 情報も加わることが多い.) binOp,exp,program 型に関しては 上の文法構造を(括弧式を除いて)そのまま写した形の宣言になっていることが わかるだろう.
|
%{ <ヘッダ> %} <宣言> %% <文法規則> %% <トレイラ><ヘッダ>, <トレイラ> は Objective Caml のプログラムを 書く部分で,ocamlyacc が生成する parser.ml の,それぞれ先頭・ 末尾にそのまま埋め込まれる.<宣言>はトークン(終端記号)や, 開始記号,優先度などの宣言を行う.parser.mly では演習を通して, 開始記号とトークンの宣言のみを使用する.<文法規則>には 文法記述と還元時のアクションを記述する.ヘッダ・トレイラでは, コメントは Objective Caml と同様 (* ... *) であり,宣言・文法規則部分では C 言語と同様な記法 /* ... */ で記述する.
この文法定義ファイルではトレイラは空になっていて,その前の%% は省略されている.
%{ open Syntax %} %token LPAREN RPAREN SEMISEMI %token PLUS MULT LT EQ COLONCOLON %token IF THEN ELSE TRUE FALSE %token <int> INTV %token <Syntax.id> ID %start toplevel %type <Syntax.program> toplevel %% toplevel : Expr SEMISEMI { Exp $1 } Expr : IfExpr { $1 } | LTExpr { $1 } LTExpr : PExpr LT PExpr { BinOp (Lt, $1, $3) } | PExpr { $1 } PExpr : PExpr PLUS MExpr { BinOp (Plus, $1, $3) } | MExpr { $1 } MExpr : MExpr MULT AExpr { BinOp (Mult, $1, $3) } | AExpr { $1 } AExpr : INTV { ILit $1 } | TRUE { BLit true } | FALSE { BLit false } | ID { Var $1 } | LPAREN Expr RPAREN { $2 } IfExpr : IF Expr THEN Expr ELSE Expr { IfExp ($2, $4, $6) }
Figure 2: ML1 インタプリタ: parser.mly
<非終端記号名> : <記号名11> ... <記号名1n1> { <還元時アクション1> } | <記号名21> ... <記号名2n2> { <還元時アクション2> } ...のように記述する.<還元時アクション>には Objective Caml の式を記述する. $i で,i番目の記号の属性を参照することができる.式全体の評価結果がこ の非終端記号の属性となるので,式の型は全て一致している必要がある.例え ば Exp は Syntax.exp 型の値(つまり ML1 の式の抽象構文木)を属 性として持ち,各アクションでは,その生成規則に対応する抽象構文木を生成 するような式が書かれている.
{ <ヘッダ> } let <名前> = <正則表現> ... rule <エントリポイント名> = parse <正則表現> { <アクション> } | <正則表現> { <アクション> } | ... and <エントリポイント名> = parse ... and ... { <トレイラ> }という構成になっている.ヘッダ・トレイラ部には,Objective Caml のプログラム を書くことができ,ocamllex が生成する lexer.ml ファイルの先頭・末尾 に埋め 込まれる.次の let を使った定義部は,よく使う正則表現に名前を つけるための部分で,lexer.mll では何も定義されていない.続く部分がエ ントリポイント,つまり字句解析の規則の定義で,同名の関数が ocamllex に よって生成される.規則としては正則表現とそれにマッチした際のアクション を(Objective Caml 式で)記述する.アクションは,基本的には(parser.mly で宣 言された)トークン(Parser.token 型)を返すような式を記述する.また,字 句解析に使用する文字列バッファが lexbuf という名前で使えるが,通常は 以下の使用法でしか使われない.
ヘッダ部では,予約語の文字列と,それに対応するトークンの連想リストであ る,reservedWords を定義している.後でみるように,List.assoc関数を 使って,文字列からトークンを取り出すことができる.
{ let reservedWords = [ (* Keywords in the alphabetical order *) ("else", Parser.ELSE); ("false", Parser.FALSE); ("if", Parser.IF); ("then", Parser.THEN); ("true", Parser.TRUE); ] } rule main = parse (* ignore spacing and newline characters *) [' ' '\009' '\012' '\n']+ { main lexbuf } | "-"? ['0'-'9']+ { Parser.INTV (int_of_string (Lexing.lexeme lexbuf)) } | "(" { Parser.LPAREN } | ")" { Parser.RPAREN } | ";;" { Parser.SEMISEMI } | "+" { Parser.PLUS } | "*" { Parser.MULT } | "<" { Parser.LT } | ['a'-'z'] ['a'-'z' '0'-'9' '_' '']* { let id = Lexing.lexeme lexbuf in try List.assoc id reservedWords with _ -> Parser.ID id } | eof { exit 0 }
Figure 3: ML1 インタプリタ: lexer.mll
|
Expressed Value | = | 整数 (…, -2, -1, 0, 1, 2, 3, …) + 真偽値 |
Denoted Value | = | Expressed Value |
(* Expressed values *) type exval = IntV of int | BoolV of bool and dnval = exval
type 'a t exception Not_bound val empty : 'a t val extend : Syntax.id -> 'a -> 'a t -> 'a t val lookup : Syntax.id -> 'a t -> 'a val map : ('a -> 'b) -> 'a t -> 'b t val fold_right : ('a -> 'b -> 'b) -> 'a t -> 'b -> 'b最初の値 empty_env は,何の変数も束縛されていない,空の環境である. 次の extend_env は,環境に新しい束縛をひとつ付け加えるための関数で, extend_env id dnval envで,環境 env に対して,変数 id を denoted value dnval に束縛したような新しい環境を表す.関数 lookup は,環 境から変数が束縛された値を取り出すもので,lookup id env で,環境 env の中を,新しく加わった束縛から順に変数 id を探し,束縛されてい る値を返す.変数が環境中に無い場合は,例外 Not_bound が発生する.
以下は後述する main.ml に記述されている, プログラム実行開始時の環境(大域環境)の定義である.
type 'a t = (Syntax.id * 'a) list exception Not_bound let empty = [] let extend x v env = (x,v)::env let rec lookup x env = try List.assoc x env with Not_found -> raise Not_bound let rec map f = function [] -> [] | (id, v)::rest -> (id, f v) :: map f rest let rec fold_right f env a = match env with [] -> a | (_, v)::rest -> f v (fold_right f rest a)
Figure 4: ML1 インタプリタ: 環境の実装 (environment.ml)
let initial_env = Environment.extend "i" (IntV 1) (Environment.extend "v" (IntV 5) (Environment.extend "x" (IntV 10) Environment.empty))i,v,x が,それぞれ 1,5,10 に束縛されている ことを表している.この大域環境は主に変数参照のテスト用で,(空でなければ)何 でもよい.
let rec apply_prim op arg1 arg2 = match op, arg1, arg2 with Plus, IntV i1, IntV i2 -> IntV (i1 + i2) | Plus, _, _ -> err ("Both arguments must be integer: +") | Mult, IntV i1, IntV i2 -> IntV (i1 * i2) | Mult, _, _ -> err ("Both arguments must be integer: *") | Lt, IntV i1, IntV i2 -> BoolV (i1 < i2) | Lt, _, _ -> err ("Both arguments must be integer: <") let rec eval_exp env = function Var x -> (try Environment.lookup x env with Environment.Not_bound -> err ("Variable not bound: " ^ x)) | ILit i -> IntV i | BLit b -> BoolV b | BinOp (op, exp1, exp2) -> let arg1 = eval_exp env exp1 in let arg2 = eval_exp env exp2 in apply_prim op arg1 arg2 | IfExp (exp1, exp2, exp3) -> let test = eval_exp env exp1 in (match test with BoolV true -> eval_exp env exp2 | BoolV false -> eval_exp env exp3 | _ -> err ("Test expression must be boolean: if")) let eval_decl env = function Exp e -> let v = eval_exp env e in ("-", env, v)
Figure 5: ML1 インタプリタ: 評価部の実装(eval.ml)の抜粋
|
open Syntax open Eval let rec read_eval_print env = print_string "# "; flush stdout; let decl = Parser.toplevel Lexer.main (Lexing.from_channel stdin) in let (id, newenv, v) = eval_decl env decl in Printf.printf "val %s = " id; pp_val v; print_newline(); read_eval_print newenv let initial_env = Environment.extend "i" (IntV 1) (Environment.extend "v" (IntV 5) (Environment.extend "x" (IntV 10) Environment.empty)) let _ = read_eval_print initial_env
Figure 6: mini Scheme1 インタプリタ: main.ml
iv + iii * iiなどを試してみよ.