-
Notifications
You must be signed in to change notification settings - Fork 0
/
2012.txt
205 lines (151 loc) · 7.2 KB
/
2012.txt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
From: Takuo Watanabe <[email protected]>
Date: Tue, 17 Apr 2012 15:04:41 +0900
Subject: Programming Project for Newcomers (2012)
--
Dear All
Here is the description of programming project for newcomers.
(written in Japanese, sorry)
新4年生むけに毎年行っているプログラミング課題の案内です.
言語処理系・関数プログラミング・記号処理に慣れ親しむのが目的です.
(M1以上の人が参加してもかまいません)
問題:Scheme(サブセット)のインタプリタを作成してください.
作成する Scheme の文法と,最低限用意する関数を以下(*)に挙げます.
他の機能を付け加えてもかまいません.
記述言語によって,以下のA, Bいずれかのコースを選んで下さい.
コースAは若干チャレンジングですが,やってみる価値はあると思います.
コースA: CあるいはC++で実装する.
・S式のパーザ等も含めて実装する.
・ガベージコレクションはBoehm GCライブラリでよいが,自作してもよい.
・末尾呼び出しの最適化は省略してもよい.
・C/C++の関数を呼び出せるようにできるとなお良い.
コースB: Schemeで実装する.
・実装したSchemeサブセット上で自身を動作させることができること.
つまりload関数で自身のソースコードを読みこみ,インタプリタ上でインタプリタが動作
できるようにする.
・call/ccを実装すること.
いずれのコースとも,以下の要件をみたすこと.
・構文に合致しない入力はエラーとすること.
・実行時エラーによってインタプリタが停止してしまわないようにすること(できる範囲で).
以上です.期間は約1ヶ月とします.完成したものはゼミの時間にデモをしてもらいます.
(*) 実装するSchemeサブセットについて
A. 構文
非終端記号:
大文字[A-Z]で始まる名前
終端記号:
開き括弧 (
閉じ括弧 )
ピリオド .
小文字[a-z]で構成される名前
シングルクオート ' で囲まれた文字列
X* 要素Xの0個以上のくりかえし
X+ 要素Xの1個以上のくりかえし
[X] 要素Xは高々1個
Toplevel ::= Exp
| Define
| (load String) ファイルの内容を読み込む
Define ::= (define Id Exp)
| (define (Id Id* [. Id]) Body)
Exp ::= Const 定数
| Id 変数
| (lambda Arg Body) λ抽象
| (Exp Exp*) 関数適用
| (quote S-Exp) クオート (注1)
| (set! Id Exp) 代入
| (let [Id] Bindings Body) let
| (let* Bindings Body) let* (注4)
| (letrec Bindings Body) letrec
| (if Exp Exp [Exp]) 条件式(if)
| (cond (Exp Exp+)* [(else Exp+)]) 条件式(cond) (注5)
| (and Exp*) 条件式(and)
| (or Exp*) 条件式(or)
| (begin Exp*) 逐次実行
| (do ((Id Exp Exp)*) (Exp Exp*) Body) 繰り返し
Body ::= Define* Exp+
Arg ::= Id
| (Id* [Id . Id])
Bindings ::= ((Id Exp)*)
S-Exp ::= Const
| Id
| (S-Exp* [S-Exp . S-Exp])
Const ::= Num
| Bool
| String
| ()
Num: 最低限10進整数はサポートすること
Bool ::= '#t'
| '#f'
String: ダブルクオートで囲まれた文字列 (注1)
Id ::= [0-9A-Za-z!$%&*+-./<=>?@^_]+ (注3)
(注1) (quote X) だけでなく 'X という記法もサポートすること.
(注2) バックスラッシュ \ によるエスケープをサポートすること.
(注3) 数値となるものをのぞくこと.
(注4) let* は let の0個以上の繰り返しではなく let* という名前である.
(注5) 節は(else節を含めて)最低1個はあること.else節は高々1個とすること.
B. 関数(最低限)
整数
number?, +, -, *, /, =, <, <=, >, >=
リスト
null?, pair?, list?, symbol?,
car, cdr, cons, list, length, memq, last, append,
set-car!, set-cdr!
ブール値
boolean?, not
文字列
string?, string-append,
symbol->string, string->symbol, string->number, number->string
関数
procedure?
比較
eq?, neq?, equal?
その他
load
C. 末尾呼び出しの最適化
末尾呼び出しの最適化(Tail Call Optimization, TCO)とは,関数本体の
末尾の位置(tail position)に関数呼び出しがある場合,コールスタックを
消費せずにその呼び出された関数に制御を移すことです.例えば
(define (even? x)
(if (= x 0) #t (odd? (- x 1))))
(define (odd? x)
(if (= x 1) #t (even? (- x 1))))
のような関数 even?, odd? の場合,0以上の引数に対しスタックオーバー
フローを起こさずに計算できなくてはなりません.
D. マクロ
マクロは以下のように定義します.
(define-macro (positive x)
(list '> x 0))
プログラム中に (positive e) という式が出てきた場合,これが
(list '> e 0) の実行結果で置き換えられ(これを「マクロが展開される」
といいます),その後で式の評価が行われます.例えば
(if (positive (+ (g x) 1)) (foo) (bar))
という式の場合,まずマクロが展開されて
(if (> (+ (g x) 1) 0) (foo) (bar))
という形の式が得られて,それからこの式が評価されます.
次に,以下のような関数定義との違いについて説明しておきます.
(define (positive x)
(> x 0))
関数定義の場合は,(positive e) という式を評価する場合は,まず
eを評価して,その結果の値が関数 positive の引数になります.一方
もしこれがマクロなら,e は評価されずに (> e 0) という形の式に
展開され,それから展開後の式が評価されます.
引数を評価しなくてすむので,関数定義と違って以下のような定義も
可能です.
(define (let*-expander vars body)
(if (null? vars)
(cons 'begin body)
(list 'let (list (car vars)) (let*-expander (cdr vars) body))))
(define-macro (let* vars . body)
(let*-expander vars body))
これは let を使って let* を定義した例です.このようにマクロを
使うことで,さまざまな構文をユーザが自分で定義することができます.
Schemeには define-syntax (let-syntax) というマクロ定義機構がありますが,
これは展開の前後で変数名がぶつからないようにする hygienic マクロと呼ばれる
もので,ちょっと実装は難しいと思います.今回実装してもらうのは Common-Lisp
風のマクロです.ちなみにCommon-Lispだと上の例は以下のようになります.
(defun let*-expander (vars body)
(if (null vars)
(cons 'begin body)
(list 'let (list (car vars)) (let*-expander (cdr vars) body))))
(defmacro let* (vars &rest body)
(let*-expander vars body))
以上.