-
Notifications
You must be signed in to change notification settings - Fork 4
Expand file tree
/
Copy path12_language.html
More file actions
504 lines (358 loc) · 72.6 KB
/
12_language.html
File metadata and controls
504 lines (358 loc) · 72.6 KB
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
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
<!doctype html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>پروژه: نوشتن یک زبان برنامهنویسی :: Eloquent JavaScript</title>
<link rel=stylesheet href="js/node_modules/codemirror/lib/codemirror.css">
<script src="js/acorn_codemirror.js"></script>
<link rel=stylesheet href="css/ejs.css">
<script src="js/sandbox.js"></script>
<script src="js/ejs.js"></script><script>var chapNum = 12;var sandboxLoadFiles = ["code/chapter/12_language.js"];</script><script>var clicky_site_ids = clicky_site_ids || []; clicky_site_ids.push(101171577);</script>
<script async src="//static.getclicky.com/js"></script>
</head>
<article>
<nav><a href="11_async.html" title="previous chapter">◀</a> <a href="index.html" title="cover">◆</a> <a href="13_browser.html" title="next chapter">▶</a></nav>
<h1><span class=chap_num>فصل 12</span>پروژه: نوشتن یک زبان برنامهنویسی</h1>
<blockquote>
<p><a class="p_ident" id="p_2jmj7l5rSw" href="#p_2jmj7l5rSw" tabindex="-1" role="presentation"></a>ارزیاب، که معنای عبارتها در یک زبان برنامهنویسی را مشخص میکند، خود نیز یک برنامه است.</p>
<footer>Hal Abelson and Gerald Sussman, <cite>Structure and Interpretation of Computer Programs</cite></footer>
</blockquote><figure class="chapter framed"><img src="img/chapter_picture_12.jpg" alt="Picture of an egg with smaller eggs inside"></figure>
<p>نوشتن زبان برنامهنویسی خودتان به طرز شگفتآوری آسان و آموزنده است (البته تا زمانی که سطح انتظارتان زیاد بالا نباشد).</p>
<p>هدف اصلی من در این فصل این است که به شما نشان دهم نوشتن یک زبان برنامهنویسی، چیزی ماوارایی و جادویی نیست. خیلی وقتها احساس میکردم که بعضی از اختراعات انسانها بسیار هوشمندانه و پیچیده است و من هرگز نمیتوانم آن ها را درک کنم. اما با کمی مطالعه و آزمایش، اغلب متوجه می شدم که اتفاقا قابل درک و ساده هستند.</p>
<p><a class="p_ident" id="p_Msm8sQztf+" href="#p_Msm8sQztf+" tabindex="-1" role="presentation"></a>در این فصل یک زبان برنامهنویسی به نام Egg خواهیم ساخت. این زبان بسیار ساده و کوچک خواهد بود – اما به اندازهی کافی قدرتمند میباشد تا بتواند هر محاسبهای که تصور میکنید را انجام دهد. در این زبان میتوان با استفاده از توابع، تجریدهای ساده را به وجود آورد.</p>
<h2 id="parsing"><a class="h_ident" id="h_8S7jf7RGIb" href="#h_8S7jf7RGIb" tabindex="-1" role="presentation"></a>تجزیه (Parsing)</h2>
<p><a class="p_ident" id="p_mIhNmG84f4" href="#p_mIhNmG84f4" tabindex="-1" role="presentation"></a>در چشم ترین قسمت یک زبان برنامهنویسی، گرامر (<em>syntax</em>) یا شیوهی نشانگذاری آن است. یک تجزیهگر (<em>parser</em>) برنامهای است که متنی را خوانده و ساختار دادهای تولید میکند که نمایانگر ساختار برنامهای است که در آن متن قرار دارد. اگر آن متن یک برنامهی معتبر را شکل نداده باشد، تجزیهگر باید خطایی تولید کند.</p>
<p><a class="p_ident" id="p_mLfbhCTbWv" href="#p_mLfbhCTbWv" tabindex="-1" role="presentation"></a>زبان برنامهنویسی ما گرامری ساده و یکپارچه خواهد داشت. همه چیز در زبان Egg از جنس عبارت خواهند بود. یک عبارت میتواند نام یک متغیر، یک عدد، یک رشته، یا یک کاربرد (application) باشد. کاربردها برای فراخوانی توابع استفاده میشوند؛ همچنین برای ساختارهایی مثل <code>if</code> و <code>while</code> نیز از آنها بهره میبریم.</p>
<p><a class="p_ident" id="p_e9CghsBnOW" href="#p_e9CghsBnOW" tabindex="-1" role="presentation"></a>برای این که تجزیهگر را ساده نگه داریم، رشتهها در زبان Egg چیزهایی مثل گریز (Escape) با بکاسلش را پشتیبانی نمیکنند. یک رشته دنبالهای از کاراکترها به جز نقل قول جفتی میباشد که خود توسط نقل قول جفتی محصور میشود. یک عدد برابر با دنبالهای از ارقام است. در نام متغیرها میتوان از هر کاراکتری به جز فضای خالی و کاراکترهایی که معنای خاصی در گرامر زبان دارند استفاده نمود.</p>
<p>کاربردها به همان سبکی که در جاوااسکریپت هستند نوشته میشوند: بعد از یک عبارت پرانتز قرار میگیرد که درون آن هر تعداد آرگومان مجاز میباشد و به وسیلهی ویرگول از هم جدا میشوند.</p>
<pre class="snippet cm-s-default" data-language="null" ><a class="c_ident" id="c_WUuISgykcX" href="#c_WUuISgykcX" tabindex="-1" role="presentation"></a>do(define(x, 10),
if(>(x, 5),
print("large"),
print("small")))</pre>
<p>یکپارچگی موجود در زبان Egg به این معنا است که چیزهایی که در جاوااسکریپت به عنوان عملگر محسوب می شدند (مثل <bdo><code>></code></bdo>)، در این زبان متغیرهایی معمولی میباشند که مثل دیگر قابلیتها به کار گرفته میشوند. و به دلیل اینکه در گرامر این زبان مفهومی به نام بلاک وجود ندارد، به ساختاری به نام <code>do</code> نیاز داریم تا بتوانیم انجام متوالی چند عمل را نمایش دهیم.</p>
<p>ساختار دادهای که تجزیهگر از آن برای توصیف یک برنامه استفاده خواهد کرد از اشیاء عبارت تشکیل شده است که هر کدام از آن ها یک خاصیت <code>type</code> دارد که نمایانگر نوع آن عبارت است و نیز خاصیتهای دیگری برای توصیف محتوای آن دارد.</p>
<p>عبارتهای نوع <code>"value"</code> نمایانگر رشتهها و اعداد خام میباشند. خاصیت <code>value</code> آن ها حاوی مقدار رشته یا عددی است که نماینده آن میباشند. عبارتهای نوع <code>"word"</code> برای شناسهها (نامها) استفاده میشوند. این گونه اشیاء دارای خاصیتی به نام <code>name</code> میباشند که نام شناسه را به عنوان یک رشته نگهداری میکند. در آخر، عبارت <code>"apply"</code> نمایانگر یک کاربرد است. این عبارتها دارای یک خاصیت به نام <code>operator</code> میباشند که به عبارتی اشاره میکند که مورد استعمال قرار گرفته است و خاصیتی به نام <code>args</code> دارند که آرایهای از عبارتهای آرگومان را نگهداری میکند.</p>
<p>بخش <bdo><code>>(x, 5)</code></bdo> از برنامهی قبلی به شکل زیر نمایش داده میشود:</p>
<pre class="snippet cm-s-default" data-language="application/json" ><a class="c_ident" id="c_YRUVy1WdLZ" href="#c_YRUVy1WdLZ" tabindex="-1" role="presentation"></a>{
<span class="cm-property">type</span>: <span class="cm-string">"apply"</span>,
<span class="cm-property">operator</span>: {<span class="cm-property">type</span>: <span class="cm-string">"word"</span>, <span class="cm-property">name</span>: <span class="cm-string">">"</span>},
<span class="cm-property">args</span>: [
{<span class="cm-property">type</span>: <span class="cm-string">"word"</span>, <span class="cm-property">name</span>: <span class="cm-string">"x"</span>},
{<span class="cm-property">type</span>: <span class="cm-string">"value"</span>, <span class="cm-property">value</span>: <span class="cm-number">5</span>}
]
}</pre>
<p>این گونه از ساختار داده را <em>درخت گرامر</em> می گویند. اگر اشیاء را به عنوان نقطه در نظر بگیرید و پیوندهای بینشان را به عنوان خطوط بین نقطهها، نمای آن شبیه به درخت خواهد بود. این واقعیت که عبارتها خود از عبارتهای دیگری تشکیل میشوند ، که آن ها هم ممکن است شامل عبارتهای دیگری باشند ، شبیه به شاخههای درخت است که خود دارای شاخههای دیگری میباشند.</p><figure><img src="img/syntax_tree.svg" alt="The structure of a syntax tree"></figure>
<p>این را با تجزیهگری که برای فایل تنظیمات در <a href="09_regexp.html#ini">فصل 9</a> نوشتیم مقایسه کنید، که ساختاری ساده داشت: ورودی را به خطوطی تقسیم می کرد و همهی آن خطوط را یکی یکی رسیدگی مینمود. خطوط فقط میتوانستند شکلهای محدودی داشته باشند.</p>
<p><a class="p_ident" id="p_0gBcwgbMv9" href="#p_0gBcwgbMv9" tabindex="-1" role="presentation"></a>در اینجا باید راه حل دیگری پیدا کنیم. عبارتها توسط خطوط جدا نمیشوند و ساختاری بازگشتی دارند. عبارتهای کاربردی (application) حاوی عبارتهای دیگر میباشند.</p>
<p>خوشبختانه، این مشکل را میتوان به خوبی با نوشتن یک تابع بازگشتی تجزیهگر حل نمود به صورتی که نمایانگر ماهیت بازگشتی زبان باشد.</p>
<p>تابعی به نام <code>parseExpression</code> را تعریف میکنیم که رشتهای را به عنوان ورودی دریافت میکند و یک شیء را باز می گرداند که حاوی ساختار دادهی مورد نظر برای عبارتی است که در ابتدای رشته آمده است، به همراه بخشی از رشته که بعد از تجزیه آن عبارت باقی می ماند. در زمان تجزیه زیر عبارتها (مثلا آرگومانهای ورودی یک کاربرد)، این تابع را میتوان دوباره فراخوانی کرد تا عبارت آرگومان را به همراه متن باقی مانده تولید کند. ممکن است که این متن، حاوی آرگومانهای بیشتر یا پرانتز آخر لیست ورودیها باشد.</p>
<p>اولین بخش تجزیهگر به این صورت خواهد بود:</p>
<pre class="snippet cm-s-default" data-language="javascript" ><a class="c_ident" id="c_Wq0wHUqay5" href="#c_Wq0wHUqay5" tabindex="-1" role="presentation"></a><span class="cm-keyword">function</span> <span class="cm-def">parseExpression</span>(<span class="cm-def">program</span>) {
<span class="cm-variable-2">program</span> <span class="cm-operator">=</span> <span class="cm-variable">skipSpace</span>(<span class="cm-variable-2">program</span>);
<span class="cm-keyword">let</span> <span class="cm-def">match</span>, <span class="cm-def">expr</span>;
<span class="cm-keyword">if</span> (<span class="cm-variable-2">match</span> <span class="cm-operator">=</span> <span class="cm-string-2">/^"([^"]*)"/</span>.<span class="cm-property">exec</span>(<span class="cm-variable-2">program</span>)) {
<span class="cm-variable-2">expr</span> <span class="cm-operator">=</span> {<span class="cm-property">type</span>: <span class="cm-string">"value"</span>, <span class="cm-property">value</span>: <span class="cm-variable-2">match</span>[<span class="cm-number">1</span>]};
} <span class="cm-keyword">else</span> <span class="cm-keyword">if</span> (<span class="cm-variable-2">match</span> <span class="cm-operator">=</span> <span class="cm-string-2">/^\d+\b/</span>.<span class="cm-property">exec</span>(<span class="cm-variable-2">program</span>)) {
<span class="cm-variable-2">expr</span> <span class="cm-operator">=</span> {<span class="cm-property">type</span>: <span class="cm-string">"value"</span>, <span class="cm-property">value</span>: <span class="cm-variable">Number</span>(<span class="cm-variable-2">match</span>[<span class="cm-number">0</span>])};
} <span class="cm-keyword">else</span> <span class="cm-keyword">if</span> (<span class="cm-variable-2">match</span> <span class="cm-operator">=</span> <span class="cm-string-2">/^[^\s(),#"]+/</span>.<span class="cm-property">exec</span>(<span class="cm-variable-2">program</span>)) {
<span class="cm-variable-2">expr</span> <span class="cm-operator">=</span> {<span class="cm-property">type</span>: <span class="cm-string">"word"</span>, <span class="cm-property">name</span>: <span class="cm-variable-2">match</span>[<span class="cm-number">0</span>]};
} <span class="cm-keyword">else</span> {
<span class="cm-keyword">throw</span> <span class="cm-keyword">new</span> <span class="cm-variable">SyntaxError</span>(<span class="cm-string">"Unexpected syntax: "</span> <span class="cm-operator">+</span> <span class="cm-variable-2">program</span>);
}
<span class="cm-keyword">return</span> <span class="cm-variable">parseApply</span>(<span class="cm-variable-2">expr</span>, <span class="cm-variable-2">program</span>.<span class="cm-property">slice</span>(<span class="cm-variable-2">match</span>[<span class="cm-number">0</span>].<span class="cm-property">length</span>));
}
<span class="cm-keyword">function</span> <span class="cm-def">skipSpace</span>(<span class="cm-def">string</span>) {
<span class="cm-keyword">let</span> <span class="cm-def">first</span> <span class="cm-operator">=</span> <span class="cm-variable-2">string</span>.<span class="cm-property">search</span>(<span class="cm-string-2">/\S/</span>);
<span class="cm-keyword">if</span> (<span class="cm-variable-2">first</span> <span class="cm-operator">==</span> <span class="cm-operator">-</span><span class="cm-number">1</span>) <span class="cm-keyword">return</span> <span class="cm-string">""</span>;
<span class="cm-keyword">return</span> <span class="cm-variable-2">string</span>.<span class="cm-property">slice</span>(<span class="cm-variable-2">first</span>);
}</pre>
<p>به دلیل این که زبان Egg مانند جاوااسکریپت امکان استفاده از فضاهای خالی بین عناصر را مجاز می شمرد، می بایست مکررا فضاهای خالی را از ابتدای یک رشتهی برنامه حذف کنیم. این کار توسط تابع <code>skipSpace</code> صورت میگیرد.</p>
<p>بعد از چشمپوشی از فضاهای خالی ابتدایی، تابع <code>parseExpression</code> از سه عبارت باقاعده برای شناسایی سه عنصر اساسی که زبان Egg از آنها پشتیبانی میکند، استفاده میکند؛ شامل: رشتهها، اعداد و کلمهها. تجزیهگر با توجه به اینکه کدام یک از آن عناصر تطبیق بخورد ساختاردادهی متفاوتی را تولید میکند. اگر ورودی با هیچ کدام از آن سه شکل تطبیق نخورد، آن عبارت معتبر نخواهد بود و تجزیهگر یک خطا تولید میکند. ما از <code>SyntaxError</code> به جای <code>Error</code> به عنوان تابع سازنده استثنا استفاده میکنیم که یک نوع خطای استاندارد دیگر است، به این دلیل که این نوع کمی اختصاصی تر است – همچنین این نوع خطا زمانی تولید میشود که تلاشی برای اجرای یک برنامه نامعتبر جاوااسکریپت صورت گرفته باشد.</p>
<p>سپس آن قسمت که تطبیق خورده است را از رشتهی برنامه حذف میکنیم و رشته را به همراه شیء متعلق به عبارت، به <code>parseApply</code> ارسال میکنیم که بررسی کند عبارت از نوع کاربرد (application) باشد. اگر بود، تابع قسمت داخل پرانتز را – لیست آرگومانها – تجزیه میکند.</p>
<pre class="snippet cm-s-default" data-language="javascript" ><a class="c_ident" id="c_tCV23NW6UI" href="#c_tCV23NW6UI" tabindex="-1" role="presentation"></a><span class="cm-keyword">function</span> <span class="cm-def">parseApply</span>(<span class="cm-def">expr</span>, <span class="cm-def">program</span>) {
<span class="cm-variable-2">program</span> <span class="cm-operator">=</span> <span class="cm-variable">skipSpace</span>(<span class="cm-variable-2">program</span>);
<span class="cm-keyword">if</span> (<span class="cm-variable-2">program</span>[<span class="cm-number">0</span>] <span class="cm-operator">!=</span> <span class="cm-string">"("</span>) {
<span class="cm-keyword">return</span> {<span class="cm-property">expr</span>: <span class="cm-variable-2">expr</span>, <span class="cm-property">rest</span>: <span class="cm-variable-2">program</span>};
}
<span class="cm-variable-2">program</span> <span class="cm-operator">=</span> <span class="cm-variable">skipSpace</span>(<span class="cm-variable-2">program</span>.<span class="cm-property">slice</span>(<span class="cm-number">1</span>));
<span class="cm-variable-2">expr</span> <span class="cm-operator">=</span> {<span class="cm-property">type</span>: <span class="cm-string">"apply"</span>, <span class="cm-property">operator</span>: <span class="cm-variable-2">expr</span>, <span class="cm-property">args</span>: []};
<span class="cm-keyword">while</span> (<span class="cm-variable-2">program</span>[<span class="cm-number">0</span>] <span class="cm-operator">!=</span> <span class="cm-string">")"</span>) {
<span class="cm-keyword">let</span> <span class="cm-def">arg</span> <span class="cm-operator">=</span> <span class="cm-variable">parseExpression</span>(<span class="cm-variable-2">program</span>);
<span class="cm-variable-2">expr</span>.<span class="cm-property">args</span>.<span class="cm-property">push</span>(<span class="cm-variable-2">arg</span>.<span class="cm-property">expr</span>);
<span class="cm-variable-2">program</span> <span class="cm-operator">=</span> <span class="cm-variable">skipSpace</span>(<span class="cm-variable-2">arg</span>.<span class="cm-property">rest</span>);
<span class="cm-keyword">if</span> (<span class="cm-variable-2">program</span>[<span class="cm-number">0</span>] <span class="cm-operator">==</span> <span class="cm-string">","</span>) {
<span class="cm-variable-2">program</span> <span class="cm-operator">=</span> <span class="cm-variable">skipSpace</span>(<span class="cm-variable-2">program</span>.<span class="cm-property">slice</span>(<span class="cm-number">1</span>));
} <span class="cm-keyword">else</span> <span class="cm-keyword">if</span> (<span class="cm-variable-2">program</span>[<span class="cm-number">0</span>] <span class="cm-operator">!=</span> <span class="cm-string">")"</span>) {
<span class="cm-keyword">throw</span> <span class="cm-keyword">new</span> <span class="cm-variable">SyntaxError</span>(<span class="cm-string">"Expected ',' or ')'"</span>);
}
}
<span class="cm-keyword">return</span> <span class="cm-variable">parseApply</span>(<span class="cm-variable-2">expr</span>, <span class="cm-variable-2">program</span>.<span class="cm-property">slice</span>(<span class="cm-number">1</span>));
}</pre>
<p>اگر کاراکتر بعدی در برنامه یک پرانتز آغاز نباشد، پس ورودی یک کاربرد نیست و تابع <code>parseApply</code> عبارتی که دریافت کرده بود را برمیگرداند.</p>
<p>در غیر این صورت، از پرانتز آغاز عبور کرده و شیء درخت گرامر را برای این عبارت کاربرد میسازد. سپس به صورت بازگشتی تابع <code>parseExpression</code> را فراخوانی میکند تا هر یک از آرگومانها را تا زمانیکه به یک پرانتز پایان برسد تجزیه کند. عمل بازگشتی به صورت غیر مستقیم است، و با فراخوانی یکدیگر <code>parseApply</code> و <code>parseExpression</code> صورت می پذیرد.</p>
<p>به دلیل اینکه میتوان یک عبارت کاربرد را اجرا کرد (مثل عبارت <bdo><code>multiplier(2)(1)</code></bdo>)، تابع <code>parseApply</code> باید بعد از آن که یک کاربرد را تجزیه کرد خودش را دوباره فراخوانی کند تا اگر جفت پرانتز دیگری در ادامه آمده است متوجه آن بشود.</p>
<p><a class="p_ident" id="p_+cdP7Sgw90" href="#p_+cdP7Sgw90" tabindex="-1" role="presentation"></a>این تمام چیزی است که برای قسمت تجزیهی Egg نیاز داریم. آن را در تابعی سرراست به نام <code>parse</code> قرار می دهیم که بررسی میکند که بعد از تجزیهی عبارت (یک برنامهی Egg یک عبارت واحد است) به انتهای رشتهی ورودی رسیده باشد و به ما ساختار دادهی برنامه را تحویل دهد.</p>
<pre class="snippet cm-s-default" data-language="javascript" ><a class="c_ident" id="c_P0tq+UdJy1" href="#c_P0tq+UdJy1" tabindex="-1" role="presentation"></a><span class="cm-keyword">function</span> <span class="cm-def">parse</span>(<span class="cm-def">program</span>) {
<span class="cm-keyword">let</span> {<span class="cm-def">expr</span>, <span class="cm-def">rest</span>} <span class="cm-operator">=</span> <span class="cm-variable">parseExpression</span>(<span class="cm-variable-2">program</span>);
<span class="cm-keyword">if</span> (<span class="cm-variable">skipSpace</span>(<span class="cm-variable-2">rest</span>).<span class="cm-property">length</span> <span class="cm-operator">></span> <span class="cm-number">0</span>) {
<span class="cm-keyword">throw</span> <span class="cm-keyword">new</span> <span class="cm-variable">SyntaxError</span>(<span class="cm-string">"Unexpected text after program"</span>);
}
<span class="cm-keyword">return</span> <span class="cm-variable-2">expr</span>;
}
<span class="cm-variable">console</span>.<span class="cm-property">log</span>(<span class="cm-variable">parse</span>(<span class="cm-string">"+(a, 10)"</span>));
<span class="cm-comment">// → {type: "apply",</span>
<span class="cm-comment">// operator: {type: "word", name: "+"},</span>
<span class="cm-comment">// args: [{type: "word", name: "a"},</span>
<span class="cm-comment">// {type: "value", value: 10}]}</span></pre>
<p>کار میکند! این تابع اطلاعات خیلی مفیدی در زمان بروز شکست به ما نمیدهد و خط و ستونی که در آن عبارت شروع میشود را ذخیره نمیکند، که اگر بود، در زمان گزارش خطاها در آینده کاربرد داشت، اما به هر حال برای هدف فعلی ما به اندازه کافی خوب است.</p>
<h2><a class="h_ident" id="h_DqiZ/jdYeG" href="#h_DqiZ/jdYeG" tabindex="-1" role="presentation"></a>ارزیاب</h2>
<p>با داشتن درخت گرامر یک برنامه، چه میتوان کرد؟ البته که اجرای آن! و این چیزی است که ارزیاب انجام میدهد. ارزیاب، یک درخت گرامر و یک قلمروی شیء که مقادیر را به نامها اختصاص میدهند دریافت میکند و عبارتی را که درخت نمایش میدهد، ارزیابی میکند و مقداری که این عبارت تولید میکند را برمیگرداند.</p>
<pre class="snippet cm-s-default" data-language="javascript" ><a class="c_ident" id="c_v23JbR3fAL" href="#c_v23JbR3fAL" tabindex="-1" role="presentation"></a><span class="cm-keyword">const</span> <span class="cm-def">specialForms</span> <span class="cm-operator">=</span> <span class="cm-variable">Object</span>.<span class="cm-property">create</span>(<span class="cm-atom">null</span>);
<span class="cm-keyword">function</span> <span class="cm-def">evaluate</span>(<span class="cm-def">expr</span>, <span class="cm-def">scope</span>) {
<span class="cm-keyword">if</span> (<span class="cm-variable-2">expr</span>.<span class="cm-property">type</span> <span class="cm-operator">==</span> <span class="cm-string">"value"</span>) {
<span class="cm-keyword">return</span> <span class="cm-variable-2">expr</span>.<span class="cm-property">value</span>;
} <span class="cm-keyword">else</span> <span class="cm-keyword">if</span> (<span class="cm-variable-2">expr</span>.<span class="cm-property">type</span> <span class="cm-operator">==</span> <span class="cm-string">"word"</span>) {
<span class="cm-keyword">if</span> (<span class="cm-variable-2">expr</span>.<span class="cm-property">name</span> <span class="cm-keyword">in</span> <span class="cm-variable-2">scope</span>) {
<span class="cm-keyword">return</span> <span class="cm-variable-2">scope</span>[<span class="cm-variable-2">expr</span>.<span class="cm-property">name</span>];
} <span class="cm-keyword">else</span> {
<span class="cm-keyword">throw</span> <span class="cm-keyword">new</span> <span class="cm-variable">ReferenceError</span>(
<span class="cm-string-2">`Undefined binding: ${</span><span class="cm-variable-2">expr</span>.<span class="cm-property">name</span><span class="cm-string-2">}</span><span class="cm-string-2">`</span>);
}
} <span class="cm-keyword">else</span> <span class="cm-keyword">if</span> (<span class="cm-variable-2">expr</span>.<span class="cm-property">type</span> <span class="cm-operator">==</span> <span class="cm-string">"apply"</span>) {
<span class="cm-keyword">let</span> {<span class="cm-def">operator</span>, <span class="cm-def">args</span>} <span class="cm-operator">=</span> <span class="cm-variable-2">expr</span>;
<span class="cm-keyword">if</span> (<span class="cm-variable-2">operator</span>.<span class="cm-property">type</span> <span class="cm-operator">==</span> <span class="cm-string">"word"</span> <span class="cm-operator">&</span><span class="cm-operator">&</span>
<span class="cm-variable-2">operator</span>.<span class="cm-property">name</span> <span class="cm-keyword">in</span> <span class="cm-variable">specialForms</span>) {
<span class="cm-keyword">return</span> <span class="cm-variable">specialForms</span>[<span class="cm-variable-2">operator</span>.<span class="cm-property">name</span>](<span class="cm-variable-2">expr</span>.<span class="cm-property">args</span>, <span class="cm-variable-2">scope</span>);
} <span class="cm-keyword">else</span> {
<span class="cm-keyword">let</span> <span class="cm-def">op</span> <span class="cm-operator">=</span> <span class="cm-variable">evaluate</span>(<span class="cm-variable-2">operator</span>, <span class="cm-variable-2">scope</span>);
<span class="cm-keyword">if</span> (<span class="cm-keyword">typeof</span> <span class="cm-variable-2">op</span> <span class="cm-operator">==</span> <span class="cm-string">"function"</span>) {
<span class="cm-keyword">return</span> <span class="cm-variable-2">op</span>(<span class="cm-meta">...</span><span class="cm-variable-2">args</span>.<span class="cm-property">map</span>(<span class="cm-def">arg</span> <span class="cm-operator">=></span> <span class="cm-variable">evaluate</span>(<span class="cm-variable-2">arg</span>, <span class="cm-variable-2">scope</span>)));
} <span class="cm-keyword">else</span> {
<span class="cm-keyword">throw</span> <span class="cm-keyword">new</span> <span class="cm-variable">TypeError</span>(<span class="cm-string">"Applying a non-function."</span>);
}
}
}
}</pre>
<p>ارزیاب برای هر نوعی از عبارتها، کد به خصوصی دارد. عبارتی که شامل یک مقدار ساده باشد، معادل خود مقدار خواهد بود. (به عنوان مثال، عبارت <code>100</code> فقط به عنوان عدد <code>100</code> ارزیابی میشود) برای یک متغیر، باید بررسی کنیم که در قلمروی مورد نظر تعریف شده باشد و در این صورت مقدار آن متغیر را بدست بیاوریم.</p>
<p><a class="p_ident" id="p_dsdL0HHdc/" href="#p_dsdL0HHdc/" tabindex="-1" role="presentation"></a>کاربردها (Applications) کمی پیچیدهتر هستند. اگر در شکل خاصی باشند، مانند <code>if</code>، چیزی را ارزیابی نمیکنیم و عبارتهای آرگومان را به همراه قلمرو به تابعی که این شکل را رسیدگی میکند ارسال میکنیم. اگر یک فراخوانی معمولی باشد، عملگر را ارزیابی کرده، اطمینان حاصل میکنیم که یک تابع باشد، و آن را با آرگومانهای ارزیابی شده فراخوانی میکنیم.</p>
<p>برای نمایش مقدارهای تابع Egg از مقدارهای تابع جاوااسکریپت استفاده خواهیم کرد. در <a href="12_language.html#egg_fun">ادامه</a> بعد از اینکه شکل خاصی که <code>fun</code> نامیده میشود تعریف شده باشد، به این بخش باز خواهیم گشت.</p>
<p>ساختار بازگشتی <code>evaluate</code> به ساختاری مشابه تجزیهگر نزدیک است و هر دو بازتاب ساختار خود زبان هستند. همچنین میتوانستیم ارزیاب و تجزیهگر را یکپارچه کنیم و در حین تجزیه، ارزیابی را نیز انجام دهیم، اما جدا کردن آن ها به این سبک باعث شفافیت بیشتر برنامه میشود.</p>
<p>این تمام چیزی است که برای تفسیر Egg مورد نیاز است. به همین سادگی. اما هنوز بدون تعریف چندین شکل خاص و افزودن چند مقدار مفید به محیط، نمیتوان کار زیادی با این زبان انجام داد.</p>
<h2><a class="h_ident" id="h_dIqUwwOD36" href="#h_dIqUwwOD36" tabindex="-1" role="presentation"></a>شکلهای خاص</h2>
<p>شیء <code>specialForm</code> برای تعریف گرامر ویژه در Egg استفاده میشود. این شیء کلمهها را به توابعی که این شکلها را ارزیابی میکنند انتساب میدهد. فعلا این شیء تهی است. بیایید <code>if</code> را به آن اضافه کنیم.</p>
<pre class="snippet cm-s-default" data-language="javascript" ><a class="c_ident" id="c_4kMBuxSNgt" href="#c_4kMBuxSNgt" tabindex="-1" role="presentation"></a><span class="cm-variable">specialForms</span>.<span class="cm-property">if</span> <span class="cm-operator">=</span> (<span class="cm-def">args</span>, <span class="cm-def">scope</span>) <span class="cm-operator">=></span> {
<span class="cm-keyword">if</span> (<span class="cm-variable-2">args</span>.<span class="cm-property">length</span> <span class="cm-operator">!=</span> <span class="cm-number">3</span>) {
<span class="cm-keyword">throw</span> <span class="cm-keyword">new</span> <span class="cm-variable">SyntaxError</span>(<span class="cm-string">"Wrong number of args to if"</span>);
} <span class="cm-keyword">else</span> <span class="cm-keyword">if</span> (<span class="cm-variable">evaluate</span>(<span class="cm-variable-2">args</span>[<span class="cm-number">0</span>], <span class="cm-variable-2">scope</span>) <span class="cm-operator">!==</span> <span class="cm-atom">false</span>) {
<span class="cm-keyword">return</span> <span class="cm-variable">evaluate</span>(<span class="cm-variable-2">args</span>[<span class="cm-number">1</span>], <span class="cm-variable-2">scope</span>);
} <span class="cm-keyword">else</span> {
<span class="cm-keyword">return</span> <span class="cm-variable">evaluate</span>(<span class="cm-variable-2">args</span>[<span class="cm-number">2</span>], <span class="cm-variable-2">scope</span>);
}
};</pre>
<p>ساختار <code>if</code> در Egg دقیقا به سه آرگومان نیاز دارد. اولین آرگومان را ارزیابی می کند، و اگر نتیجهی آن برابر با مقدار <code>false</code> نبود، به سراغ ارزیابی دومی می رود. در غیر این صورت، سومین آرگومان ارزیابی میشود. این شکل <code>if</code> بیشتر شبیه به عملگر سهتایی <bdo><code>?:</code></bdo> در جاوااسکریپت است تا دستور <code>if</code> در آن. این یک عبارت است، نه یک دستور و مقداری را تولید میکند که همان نتیجهی آرگومان دوم و سوم میباشد.</p>
<p>Egg همچنین در چگونگی رسیدگی به مقدار شرط در عبارت <code>if</code> با جاواسکریپت تفاوت دارد. این عبارت چیزهایی مثل صفر یا رشتهی خالی را <code>false</code> در نظر نمیگیرد، فقط مقدار دقیق <code>false</code> را در نظر میگیرد.</p>
<p>علت نمایش <code>if</code> به عنوان یک شکل خاص به جای یک تابع معمولی، این است که تمامی آرگومانها در توابع قبل از این که تابع فراخوانی بشود ارزیابی میشوند در حالیکه <code>if</code> باید فقط بعد از یکی از آرگومانهای دوم یا سوم بسته به مقدار آرگومان اول ارزیابی شود.</p>
<p>شکل خاص <code>while</code> به همین صورت است.</p>
<pre class="snippet cm-s-default" data-language="javascript" ><a class="c_ident" id="c_wZb+EB+hgA" href="#c_wZb+EB+hgA" tabindex="-1" role="presentation"></a><span class="cm-variable">specialForms</span>.<span class="cm-property">while</span> <span class="cm-operator">=</span> (<span class="cm-def">args</span>, <span class="cm-def">scope</span>) <span class="cm-operator">=></span> {
<span class="cm-keyword">if</span> (<span class="cm-variable-2">args</span>.<span class="cm-property">length</span> <span class="cm-operator">!=</span> <span class="cm-number">2</span>) {
<span class="cm-keyword">throw</span> <span class="cm-keyword">new</span> <span class="cm-variable">SyntaxError</span>(<span class="cm-string">"Wrong number of args to while"</span>);
}
<span class="cm-keyword">while</span> (<span class="cm-variable">evaluate</span>(<span class="cm-variable-2">args</span>[<span class="cm-number">0</span>], <span class="cm-variable-2">scope</span>) <span class="cm-operator">!==</span> <span class="cm-atom">false</span>) {
<span class="cm-variable">evaluate</span>(<span class="cm-variable-2">args</span>[<span class="cm-number">1</span>], <span class="cm-variable-2">scope</span>);
}
<span class="cm-comment">// Since undefined does not exist in Egg, we return false,</span>
<span class="cm-comment">// for lack of a meaningful result.</span>
<span class="cm-keyword">return</span> <span class="cm-atom">false</span>;
};</pre>
<p>یکی دیگر از بلاکها ساختاری پایه <code>do</code> است که تمامی آرگومانهایش را از بالا به پایین اجرا میکند. مقدار آن برابر با مقداری است که توسط آرگومان آخر تولید می شود.</p>
<pre class="snippet cm-s-default" data-language="javascript" ><a class="c_ident" id="c_QkaRUun3ao" href="#c_QkaRUun3ao" tabindex="-1" role="presentation"></a><span class="cm-variable">specialForms</span>.<span class="cm-property">do</span> <span class="cm-operator">=</span> (<span class="cm-def">args</span>, <span class="cm-def">scope</span>) <span class="cm-operator">=></span> {
<span class="cm-keyword">let</span> <span class="cm-def">value</span> <span class="cm-operator">=</span> <span class="cm-atom">false</span>;
<span class="cm-keyword">for</span> (<span class="cm-keyword">let</span> <span class="cm-def">arg</span> <span class="cm-keyword">of</span> <span class="cm-variable-2">args</span>) {
<span class="cm-variable-2">value</span> <span class="cm-operator">=</span> <span class="cm-variable">evaluate</span>(<span class="cm-variable-2">arg</span>, <span class="cm-variable-2">scope</span>);
}
<span class="cm-keyword">return</span> <span class="cm-variable-2">value</span>;
};</pre>
<p>برای این که قادر باشیم تا متغیرهایی ایجاد کنیم و مقادیر جدیدی را به آن ها اختصاص دهیم، همچنین نیاز به تعریف شکلی به نام <code>define</code> داریم. این شکل به عنوان آرگومان اول یک واژه را دریافت میکند و به عنوان آرگومان دوم، عبارتی را که منجر به تولید مقداری میشود که قرار است به آن واژه منتسب شود. به دلیل این که <code>define</code> مثل هر چیز دیگر، یک عبارت است باید مقداری را برگرداند. ما طوری آن را می سازیم که مقداری که به آن انتساب یافته را برگرداند (درست شبیه عملگر <code>=</code> در جاوااسکریپت).</p>
<pre class="snippet cm-s-default" data-language="javascript" ><a class="c_ident" id="c_/TYE9JhkNk" href="#c_/TYE9JhkNk" tabindex="-1" role="presentation"></a><span class="cm-variable">specialForms</span>.<span class="cm-property">define</span> <span class="cm-operator">=</span> (<span class="cm-def">args</span>, <span class="cm-def">scope</span>) <span class="cm-operator">=></span> {
<span class="cm-keyword">if</span> (<span class="cm-variable-2">args</span>.<span class="cm-property">length</span> <span class="cm-operator">!=</span> <span class="cm-number">2</span> <span class="cm-operator">|</span><span class="cm-operator">|</span> <span class="cm-variable-2">args</span>[<span class="cm-number">0</span>].<span class="cm-property">type</span> <span class="cm-operator">!=</span> <span class="cm-string">"word"</span>) {
<span class="cm-keyword">throw</span> <span class="cm-keyword">new</span> <span class="cm-variable">SyntaxError</span>(<span class="cm-string">"Incorrect use of define"</span>);
}
<span class="cm-keyword">let</span> <span class="cm-def">value</span> <span class="cm-operator">=</span> <span class="cm-variable">evaluate</span>(<span class="cm-variable-2">args</span>[<span class="cm-number">1</span>], <span class="cm-variable-2">scope</span>);
<span class="cm-variable-2">scope</span>[<span class="cm-variable-2">args</span>[<span class="cm-number">0</span>].<span class="cm-property">name</span>] <span class="cm-operator">=</span> <span class="cm-variable-2">value</span>;
<span class="cm-keyword">return</span> <span class="cm-variable-2">value</span>;
};</pre>
<h2><a class="h_ident" id="h_A1AQlkbi9c" href="#h_A1AQlkbi9c" tabindex="-1" role="presentation"></a>محیط</h2>
<p>قلمرویی که توسط <code>evaluate</code> قبول میشود یک شیء است که خاصیتهایی دارد که نام آنها متناظر با نام متغیرها میباشد و مقادیر آن برابر مقدار آن متغیرها خواهد بود. بیایید شیئی را تعریف کنیم که نماینده قلمروی سراسری باشد.</p>
<p>برای این که بتوان از ساختار <code>if</code> که پیشتر تعریف کرده ایم استفاده کنیم باید به مقادیر بولی دسترسی داشته باشیم. به دلیل این که فقط دو مقدار بولی وجوددارد، نیاز به گرامر خاصی برای آن ها نداریم. کافی است تا دو مقدار <code>true</code> و <code>false</code> را به دو نام منتسب کنیم و از آن ها استفاده کنیم.</p>
<pre class="snippet cm-s-default" data-language="javascript" ><a class="c_ident" id="c_vJ45zHlK0v" href="#c_vJ45zHlK0v" tabindex="-1" role="presentation"></a><span class="cm-keyword">const</span> <span class="cm-def">topScope</span> <span class="cm-operator">=</span> <span class="cm-variable">Object</span>.<span class="cm-property">create</span>(<span class="cm-atom">null</span>);
<span class="cm-variable">topScope</span>.<span class="cm-property">true</span> <span class="cm-operator">=</span> <span class="cm-atom">true</span>;
<span class="cm-variable">topScope</span>.<span class="cm-property">false</span> <span class="cm-operator">=</span> <span class="cm-atom">false</span>;</pre>
<p>اکنون میتوانیم یک عبارت ساده را که یک مقدار بولی را معکوس میکند ارزیابی کنیم.</p>
<pre class="snippet cm-s-default" data-language="javascript" ><a class="c_ident" id="c_ynBEgrK+/h" href="#c_ynBEgrK+/h" tabindex="-1" role="presentation"></a><span class="cm-keyword">let</span> <span class="cm-def">prog</span> <span class="cm-operator">=</span> <span class="cm-variable">parse</span>(<span class="cm-string-2">`if(true, false, true)`</span>);
<span class="cm-variable">console</span>.<span class="cm-property">log</span>(<span class="cm-variable">evaluate</span>(<span class="cm-variable">prog</span>, <span class="cm-variable">topScope</span>));
<span class="cm-comment">// → false</span></pre>
<p>برای فراهم ساختن عملگرهای اصلی حسابی و مقایسه، چند مقدار تابع نیز به قلمرو اضافه خواهیم کرد. برای رعایت اختصار در کدنویسی، به جای تعریف جداگانهی هر کدام، از سازندهی <code>Function</code> برای ترکیب چند تابع عملگر در یک حلقه استفاده میکنیم.</p>
<pre class="snippet cm-s-default" data-language="javascript" ><a class="c_ident" id="c_OTgmEw/s8v" href="#c_OTgmEw/s8v" tabindex="-1" role="presentation"></a><span class="cm-keyword">for</span> (<span class="cm-keyword">let</span> <span class="cm-def">op</span> <span class="cm-keyword">of</span> [<span class="cm-string">"+"</span>, <span class="cm-string">"-"</span>, <span class="cm-string">"*"</span>, <span class="cm-string">"/"</span>, <span class="cm-string">"=="</span>, <span class="cm-string">"<"</span>, <span class="cm-string">">"</span>]) {
<span class="cm-variable">topScope</span>[<span class="cm-variable">op</span>] <span class="cm-operator">=</span> <span class="cm-variable">Function</span>(<span class="cm-string">"a, b"</span>, <span class="cm-string-2">`return a ${</span><span class="cm-variable">op</span><span class="cm-string-2">}</span> <span class="cm-string-2">b;`</span>);
}</pre>
<p>داشتن راهی برای چاپ مقادیر نیز بسیار کاربردی خواهد بود، بنابراین <bdo><code>console.log</code></bdo> را در یک تابع قرار می دهیم و نام ان را <code>print</code> می گذاریم:</p>
<pre class="snippet cm-s-default" data-language="javascript" ><a class="c_ident" id="c_XFrq8jIuQC" href="#c_XFrq8jIuQC" tabindex="-1" role="presentation"></a><span class="cm-variable">topScope</span>.<span class="cm-property">print</span> <span class="cm-operator">=</span> <span class="cm-def">value</span> <span class="cm-operator">=></span> {
<span class="cm-variable">console</span>.<span class="cm-property">log</span>(<span class="cm-variable-2">value</span>);
<span class="cm-keyword">return</span> <span class="cm-variable-2">value</span>;
};</pre>
<p>با این کار به اندازه کافی ابزارهای مقدماتی برای نوشتن برنامههای ساده در اختیار خواهیم داشت. توابع پیش رو راهی مناسب برای تجزیهی یک برنامه و اجرای آن در یک قلمروی جدید را فراهم می سازند.</p>
<pre class="snippet cm-s-default" data-language="javascript" ><a class="c_ident" id="c_aPeJgSZPEO" href="#c_aPeJgSZPEO" tabindex="-1" role="presentation"></a><span class="cm-keyword">function</span> <span class="cm-def">run</span>(<span class="cm-def">program</span>) {
<span class="cm-keyword">return</span> <span class="cm-variable">evaluate</span>(<span class="cm-variable">parse</span>(<span class="cm-variable-2">program</span>), <span class="cm-variable">Object</span>.<span class="cm-property">create</span>(<span class="cm-variable">topScope</span>));
}</pre>
<p>ما از زنجیرهی پروتوتایپ شیء برای نمایش قلمروهای تودرتو استفاده خواهیم کرد، تا برنامه بتواند متغیرهای خودش را به قلمروی محلی بدون ایجاد تغییر در قلمروی بالادست اضافه کند.</p>
<pre class="snippet cm-s-default" data-language="javascript" ><a class="c_ident" id="c_uW/XtfVXMZ" href="#c_uW/XtfVXMZ" tabindex="-1" role="presentation"></a><span class="cm-variable">run</span>(<span class="cm-string-2">`</span>
<span class="cm-string-2">do(define(total, 0),</span>
<span class="cm-string-2">define(count, 1),</span>
<span class="cm-string-2">while(<(count, 11),</span>
<span class="cm-string-2">do(define(total, +(total, count)),</span>
<span class="cm-string-2">define(count, +(count, 1)))),</span>
<span class="cm-string-2">print(total))</span>
<span class="cm-string-2">`</span>);
<span class="cm-comment">// → 55</span></pre>
<p><a class="p_ident" id="p_0KQ+esLp48" href="#p_0KQ+esLp48" tabindex="-1" role="presentation"></a>این برنامهای است که تاکنون چندین بار دیدهایم، که مجموع اعداد 1 تا 10 را محاسبه میکند و به زبان Egg نوشته شده است. قطعا ظاهر این برنامه از برنامهی معادل جاوااسکریپتش زشتتر است – اما برای زبان برنامهنویسیای که با کمتر از 150 خط کدنویسی پیادهسازی شده است بد نیست.</p>
<h2 id="egg_fun"><a class="h_ident" id="h_vAoppXbLUs" href="#h_vAoppXbLUs" tabindex="-1" role="presentation"></a>توابع</h2>
<p>یک زبان برنامهنویسی بدون داشتن توابع، زبانی فقیر محسوب میشود.</p>
<p>خوشبختانه، به آسانی میتوان یک ساختار <code>fun</code> به زبان افزود، که آرگومان آخرش را به عنوان بدنهی تابع در نظر بگیرد و از آرگومان های قبلی به عنوان نام پارامترهای تابع استفاده کند.</p>
<pre class="snippet cm-s-default" data-language="javascript" ><a class="c_ident" id="c_cnvuH0fWH0" href="#c_cnvuH0fWH0" tabindex="-1" role="presentation"></a><span class="cm-variable">specialForms</span>.<span class="cm-property">fun</span> <span class="cm-operator">=</span> (<span class="cm-def">args</span>, <span class="cm-def">scope</span>) <span class="cm-operator">=></span> {
<span class="cm-keyword">if</span> (<span class="cm-operator">!</span><span class="cm-variable-2">args</span>.<span class="cm-property">length</span>) {
<span class="cm-keyword">throw</span> <span class="cm-keyword">new</span> <span class="cm-variable">SyntaxError</span>(<span class="cm-string">"Functions need a body"</span>);
}
<span class="cm-keyword">let</span> <span class="cm-def">body</span> <span class="cm-operator">=</span> <span class="cm-variable-2">args</span>[<span class="cm-variable-2">args</span>.<span class="cm-property">length</span> <span class="cm-operator">-</span> <span class="cm-number">1</span>];
<span class="cm-keyword">let</span> <span class="cm-def">params</span> <span class="cm-operator">=</span> <span class="cm-variable-2">args</span>.<span class="cm-property">slice</span>(<span class="cm-number">0</span>, <span class="cm-variable-2">args</span>.<span class="cm-property">length</span> <span class="cm-operator">-</span> <span class="cm-number">1</span>).<span class="cm-property">map</span>(<span class="cm-def">expr</span> <span class="cm-operator">=></span> {
<span class="cm-keyword">if</span> (<span class="cm-variable-2">expr</span>.<span class="cm-property">type</span> <span class="cm-operator">!=</span> <span class="cm-string">"word"</span>) {
<span class="cm-keyword">throw</span> <span class="cm-keyword">new</span> <span class="cm-variable">SyntaxError</span>(<span class="cm-string">"Parameter names must be words"</span>);
}
<span class="cm-keyword">return</span> <span class="cm-variable-2">expr</span>.<span class="cm-property">name</span>;
});
<span class="cm-keyword">return</span> <span class="cm-keyword">function</span>() {
<span class="cm-keyword">if</span> (<span class="cm-variable-2">arguments</span>.<span class="cm-property">length</span> <span class="cm-operator">!=</span> <span class="cm-variable-2">params</span>.<span class="cm-property">length</span>) {
<span class="cm-keyword">throw</span> <span class="cm-keyword">new</span> <span class="cm-variable">TypeError</span>(<span class="cm-string">"Wrong number of arguments"</span>);
}
<span class="cm-keyword">let</span> <span class="cm-def">localScope</span> <span class="cm-operator">=</span> <span class="cm-variable">Object</span>.<span class="cm-property">create</span>(<span class="cm-variable-2">scope</span>);
<span class="cm-keyword">for</span> (<span class="cm-keyword">let</span> <span class="cm-def">i</span> <span class="cm-operator">=</span> <span class="cm-number">0</span>; <span class="cm-variable-2">i</span> <span class="cm-operator"><</span> <span class="cm-variable-2">arguments</span>.<span class="cm-property">length</span>; <span class="cm-variable-2">i</span><span class="cm-operator">++</span>) {
<span class="cm-variable-2">localScope</span>[<span class="cm-variable-2">params</span>[<span class="cm-variable-2">i</span>]] <span class="cm-operator">=</span> <span class="cm-variable-2">arguments</span>[<span class="cm-variable-2">i</span>];
}
<span class="cm-keyword">return</span> <span class="cm-variable">evaluate</span>(<span class="cm-variable-2">body</span>, <span class="cm-variable-2">localScope</span>);
};
};</pre>
<p>توابع در Egg، قلمروی محلی خودشان را دریافت میکنند. تابعی که با <code>fun</code> تولید میشود قلمروی محلی خودش را ایجاد کرده و آرگومانهایش را به آن مقید میکند. سپس بدنهی تابع را در این قلمرو ارزیابی کرده و نتیجه را باز می گرداند.</p>
<pre class="snippet cm-s-default" data-language="javascript" ><a class="c_ident" id="c_tn5DChGAkA" href="#c_tn5DChGAkA" tabindex="-1" role="presentation"></a><span class="cm-variable">run</span>(<span class="cm-string-2">`</span>
<span class="cm-string-2">do(define(plusOne, fun(a, +(a, 1))),</span>
<span class="cm-string-2">print(plusOne(10)))</span>
<span class="cm-string-2">`</span>);
<span class="cm-comment">// → 11</span>
<span class="cm-variable">run</span>(<span class="cm-string-2">`</span>
<span class="cm-string-2">do(define(pow, fun(base, exp,</span>
<span class="cm-string-2">if(==(exp, 0),</span>
<span class="cm-string-2">1,</span>
<span class="cm-string-2">*(base, pow(base, -(exp, 1)))))),</span>
<span class="cm-string-2">print(pow(2, 10)))</span>
<span class="cm-string-2">`</span>);
<span class="cm-comment">// → 1024</span></pre>
<h2><a class="h_ident" id="h_5SgRFiRmCf" href="#h_5SgRFiRmCf" tabindex="-1" role="presentation"></a>کامپایل کردن</h2>
<p>چیزی که تاکنون ساختهایم یک مفسر است. در طول ارزیابی، این مفسر به طور مستقیم روی چیزی که توسط تجزیهگر تولید شده عمل می نماید.</p>
<p>کامپایل کردن روندی است که در آن گامی دیگر بین تفسیر و اجرای برنامه اضافه می شود، که کد برنامه را به چیزی که بتوان با کارایی بیشتری ارزیابی کرد تبدیل میکند و این کار با انجام حداکثر کار ممکن قبل از مرحلهی ارزیابی میسر میشود. به عنوان مثال، در زبانهایی که خوب طراحی شده اند، در زمان استفاده از یک متغیر، به روشنی می توان فهمید که کدام متغیر مورد اشاره است، بدون اینکه نیاز باشد برنامه واقعا اجرا شود. این کار باعث میشود که از جستجوی متغیر بر اساس نام در هر بار استفاده از آن جلوگیری شود، و به جای آن مستقیما آن را از قسمتهای مشخصی از حافظه بهدست بیاوریم.</p>
<p>به طور سنتی، کامپایل شامل تبدیل برنامه به کد ماشین میشود، فرمت خامی که یک پردازندهی کامپیوتر میتواند اجرا کند. اما هر روندی که برنامه را به نمایش متفاوتی تبدیل کند را میتوان به عنوان کامپایل در نظر گرفت.</p>
<p>میتوان یک استراتژی ارزیابی جایگزین برای Egg، نوشت که در آن ابتدا برنامه را به یک برنامهی جاوااسکریپت تبدیل کند، از <code>Function</code> برای فراخوانی جاوااسکریپت برای کامپایل آن استفاده کند و سپس نتیجه را اجرا نماید. اگر این کار به درستی انجام شود باعث می شود که Egg خیلی سریع تر اجرا شود در حالیکه سادگی پیادهسازی را هنوز با خود دارد.</p>
<p>اگر به این موضوع علاقه دارید و قصد دارید مقداری زمان صرف آن کنید، پیشنهاد می کنم این کامپایلری که ذکر شد را به عنوان یک تمرین پیاده سازی کنید.</p>
<h2><a class="h_ident" id="h_nA1o6NN6Pb" href="#h_nA1o6NN6Pb" tabindex="-1" role="presentation"></a>تقلب</h2>
<p>زمانی که <code>if</code> و <code>while</code> را تعریف کردیم، احتمالا متوجه شدید که این دو، پوششی کم و بیش ساده برای <code>if</code> و <code>while</code> خود جاواسکریپت بودند. به طور مشابه، مقدارها در Egg همان مقدارهای معمولی جاوااسکریپت هستند.</p>
<p>اگر پیادهسازی Egg را که بر اساس جاوااسکریپت ساخته شده است با میزان کار و پیچیدگی لازم برای ساخت یک زبان برنامهنویسی که مستقیما برپایهی قابلیتهای خام ماشین است، مقایسه کنید، تفاوت خیلی قابل توجه است. صرف نظر از آن، امیدوارم این مثال درکی از روش کارکرد زبانهای برنامهنویسی را به شما منتقل کرده باشد.</p>
<p>و زمانیکه لازم است کاری انجام شود، تقلب کردن موثرتر از این است که همه چیز را خودتان انجام دهید. البته زبانی که برای آموزش در این فصل ایجاد شد کاری را انجام نمیدهد که از معادلش در جاوااسکریپت بهتر عمل کند اما موقعیتهایی وجود دارد که نوشتن زبانهای کوچک برای انجام کارهای واقعی کاربرد دارد.</p>
<p>نیازی نیست این گونه زبانها شبیه به یک زبان برنامهنویسی متداول باشند. مثلا اگر جاوااسکریپت از عبارتهای باقاعده به صورت درونی پشتیبانی نمی کرد، میتوانستید مفسر و ارزیاب خودتان را برای عبارات باقاعده بنویسید.</p>
<p>تصور کنید که در حال ساخت یک دایناسور روباتیک غول پیکر هستید و لازم است تا رفتار آن را برنامهنویسی کنید. جاوااسکریپت ممکن است موثر ترین روش این کار نباشد، ممکن است در عوض به دنبال زبانی شبیه به زیر باشید.</p>
<pre class="snippet cm-s-default" data-language="null" ><a class="c_ident" id="c_831P/I2TjC" href="#c_831P/I2TjC" tabindex="-1" role="presentation"></a>behavior walk
perform when
destination ahead
actions
move left-foot
move right-foot
behavior attack
perform when
Godzilla in-view
actions
fire laser-eyes
launch arm-rockets</pre>
<p>به این گونه زبانها، معمولا <em>زبانی با دامنهی خاص</em> گفته میشود؛ زبانی که برای بیان دامنهی کوچکی از اطلاعات تدارک دیده میشود. این گونه زبانها میتوانند نسبت به یک زبان متداول عام رساتر باشند زیرا طراحی آنها دقیقا برای توصیف چیزهایی بوده است که در دامنهشان وجود دارد نه چیز دیگر.</p>
<h2><a class="h_ident" id="h_ggOFdVwDCk" href="#h_ggOFdVwDCk" tabindex="-1" role="presentation"></a>تمرینها</h2>
<h3><a class="i_ident" id="i_wt7dc1PMKA" href="#i_wt7dc1PMKA" tabindex="-1" role="presentation"></a>آرایهها</h3>
<p><a class="p_ident" id="p_yENkOHhEKC" href="#p_yENkOHhEKC" tabindex="-1" role="presentation"></a>پشتیبانی از آرایهها را به Egg اضافه کنید و این کار را با افزودن سه تابع پیش رو به قلمروی بالایی انجام دهید: تابع <bdo><code>array(...values)</code></bdo> برای ساختن آرایهای که حاوی مقدارهای آرگومان است، <bdo><code>length(array)</code></bdo> برای گرفتن طول یک آرایه و <bdo><code>element(array, n)</code></bdo> برای به دست آوردن n<sup>th</sup> عنصر آرایه.</p>
<pre class="snippet cm-s-default" data-language="javascript" ><a class="c_ident" id="c_eB3J9xBy0h" href="#c_eB3J9xBy0h" tabindex="-1" role="presentation"></a><span class="cm-comment">// Modify these definitions...</span>
<span class="cm-variable">topScope</span>.<span class="cm-property">array</span> <span class="cm-operator">=</span> <span class="cm-string">"..."</span>;
<span class="cm-variable">topScope</span>.<span class="cm-property">length</span> <span class="cm-operator">=</span> <span class="cm-string">"..."</span>;
<span class="cm-variable">topScope</span>.<span class="cm-property">element</span> <span class="cm-operator">=</span> <span class="cm-string">"..."</span>;
<span class="cm-variable">run</span>(<span class="cm-string-2">`</span>
<span class="cm-string-2">do(define(sum, fun(array,</span>
<span class="cm-string-2">do(define(i, 0),</span>
<span class="cm-string-2">define(sum, 0),</span>
<span class="cm-string-2">while(<(i, length(array)),</span>
<span class="cm-string-2">do(define(sum, +(sum, element(array, i))),</span>
<span class="cm-string-2">define(i, +(i, 1)))),</span>
<span class="cm-string-2">sum))),</span>
<span class="cm-string-2">print(sum(array(1, 2, 3))))</span>
<span class="cm-string-2">`</span>);
<span class="cm-comment">// → 6</span></pre>
<div class="solution"><div class="solution-text">
<p>سادهترین روش انجام آن این است که از آرایههای خود جاوااسکریپت برای نمایش آرایههای Egg بهره ببرید.</p>
<p><a class="p_ident" id="p_J60WctjJfX" href="#p_J60WctjJfX" tabindex="-1" role="presentation"></a>مقادیری که به قلمروی بالایی اضافه می شوند باید تابع باشند. با استفاده از یک آرگومان rest (که با سه نقطه نوشته میشود)، تعریف <code>array</code> بسیار ساده خواهد شد.</p>
</div></div>
<h3><a class="i_ident" id="i_TMER9FuDfN" href="#i_TMER9FuDfN" tabindex="-1" role="presentation"></a>بستار (Closure)</h3>
<p>روشی که برای تعریف <code>fun</code> استفاده کردیم به توابع در Egg این امکان را میدهد که به قلمروی پیرامونشان بتوانند ارجاع دهند، به این صورت که به بدنهی تابع این امکان را میدهد تا از مقدارهای محلی که در زمان تعریف تابع قابل مشاهده بوده اند استفاده کند درست شبیه کاری که توابع در جاوااسکریپت انجام میدهند.</p>
<p>برنامهی پیش رو این مفهوم را نشان میدهد: تابع <code>f</code> یک یک تابع دیگر برمیگرداند؛ تابعی که آرگومانهایش را با آرگومانهای <code>f</code> جمع می نماید، به این معنا که برای انجام این کار باید بتواند به قلمروی محلی درون <code>f</code> دسترسی داشته باشد تا از مقدار متغیر <code>a</code> استفاده کند.</p>
<pre class="snippet cm-s-default" data-language="javascript" ><a class="c_ident" id="c_zJ2x7sbWRv" href="#c_zJ2x7sbWRv" tabindex="-1" role="presentation"></a><span class="cm-variable">run</span>(<span class="cm-string-2">`</span>
<span class="cm-string-2">do(define(f, fun(a, fun(b, +(a, b)))),</span>
<span class="cm-string-2">print(f(4)(5)))</span>
<span class="cm-string-2">`</span>);
<span class="cm-comment">// → 9</span></pre>
<p>به قسمت تعریف <code>fun</code> برگردید و توضیح دهید چه مکانیزمی باعث این رفتار شده است.</p>
<div class="solution"><div class="solution-text">
<p>میدانیم که ما از مکانیزم جاوااسکریپت برای ساخت یک قابلیت مشابه در Egg بهره میبریم. قلمروی محلی به صورتی که ارزیابی میشوند به شکلهای خاص داده میشوند در نتیجه شکلهای خاص میتوانند زیرشکلهای خودشان را در همان قلمرو ارزیابی کنند. تابعی که توسط <code>fun</code> برگردانده میشود به آرگومان <code>scope</code>ای (قلمرویی) که به تابع محصورش داده میشود دسترسی دارد و از آن برای ایجاد قلمروی محلیاش در هنگام فراخوانی استفاده میکند.</p>
<p>معنای آن این است که خمیرمایهی قلمروی محلی برابر با قلمرویی خواهد بود که در آن تابع ایجاد شده است، که موجب میشود بتوان به متغیرهای آن از درون تابع دسترسی داشت. این تمام چیزی است که در پیادهسازی بستار وجود دارد (اگرچه برای کامپایل آن به صورتی که بهینه عمل کند لازم است تا کارهای بیشتری انجام شود).</p>
</div></div>
<h3><a class="i_ident" id="i_VbvUuahfUk" href="#i_VbvUuahfUk" tabindex="-1" role="presentation"></a>توضیحات</h3>
<p>خوب می شد اگر میتوانستیم در Egg توضیحات بنویسیم. مثلا، هر بار که به علامت (<code>#</code>) برسیم، بقیهی خط را به عنوان یک توضیح در نظر بگیریم شبیه به <code>//</code> در جاوااسکریپت.</p>
<p>نیازی به ایجاد تغییرات بزرگی در تجزیهگر برای پشتیبانی از توضیحات نیست. می توانیم <code>skipSpace</code> را تغییر داده تا توضیحات را همان طور که از فضاهای خالی صرف نظر میشود، پردازش نکند؛ بنابراین در تمامی نقاطی که تابع <code>skipSpace</code> فراخوانی می شود اکنون توضیحات نیز شناسایی و صرف نظر میشوند. این تغییر را اعمال کنید.</p>
<pre class="snippet cm-s-default" data-language="javascript" ><a class="c_ident" id="c_HlvQkY82YH" href="#c_HlvQkY82YH" tabindex="-1" role="presentation"></a><span class="cm-comment">// This is the old skipSpace. Modify it...</span>
<span class="cm-keyword">function</span> <span class="cm-def">skipSpace</span>(<span class="cm-def">string</span>) {
<span class="cm-keyword">let</span> <span class="cm-def">first</span> <span class="cm-operator">=</span> <span class="cm-variable-2">string</span>.<span class="cm-property">search</span>(<span class="cm-string-2">/\S/</span>);
<span class="cm-keyword">if</span> (<span class="cm-variable-2">first</span> <span class="cm-operator">==</span> <span class="cm-operator">-</span><span class="cm-number">1</span>) <span class="cm-keyword">return</span> <span class="cm-string">""</span>;
<span class="cm-keyword">return</span> <span class="cm-variable-2">string</span>.<span class="cm-property">slice</span>(<span class="cm-variable-2">first</span>);
}
<span class="cm-variable">console</span>.<span class="cm-property">log</span>(<span class="cm-variable">parse</span>(<span class="cm-string">"# hello\nx"</span>));
<span class="cm-comment">// → {type: "word", name: "x"}</span>
<span class="cm-variable">console</span>.<span class="cm-property">log</span>(<span class="cm-variable">parse</span>(<span class="cm-string">"a # one\n # two\n()"</span>));
<span class="cm-comment">// → {type: "apply",</span>
<span class="cm-comment">// operator: {type: "word", name: "a"},</span>
<span class="cm-comment">// args: []}</span></pre>
<div class="solution"><div class="solution-text">
<p>اطمینان حاصل کنید که راه حل شما چند توضیح در یک خط را مدیریت میکند و در صورت وجود فضای خالی قبل و بعد هر توضیح با مشکل روبرو نمیشود.</p>
<p>احتمالا سادهترین روش حل این مسئله استفاده از عبارات باقاعده است. چیزی بنویسید که "فضای خالی یا توضیح، با صفر یا بیشتر تکرار" را تطبیق بزند. از متد <code>exec</code> یا <code>match</code> استفاده کنید و به طول عنصر اول آرایهی برگشتی (تطبیق کامل) توجه کنید تا متوجه شوید چه تعداد کاراکتر نیاز است تا جدا شود.</p>
</div></div>
<h3><a class="i_ident" id="i_HdzBG6Qs+q" href="#i_HdzBG6Qs+q" tabindex="-1" role="presentation"></a>رفع مشکل قلمرو</h3>
<p>در حال حاضر، تنها راهی که میتوان یک متغیر و مقدار را به هم منتسب کرد استفاده از <code>define</code> است. این ساختار برای هر دو کار تعریف متغیر جدید و تغییر مقدار یک متغیر موجود استفاده میشود.</p>
<p>این ابهام مشکلی را ایجاد میکند. زمانی که به یک متغیر غیرمحلی یک مقدار جدید را منتسب میکنید، باعث میشود که به جای آن، متغیری محلی با همان نام را تعریف کنید. برخی زبانها به همین صورت طراحی شده اند، اما من همیشه این روش مدیریت قلمرو را نامناسب دیدهام.</p>
<p>یک شکل خاصی به نام <code>set</code> را ،شبیه به <code>define</code>، اضافه کنید که به یک متغیر یک مقدار جدید منتسب میکند، و متغیر را در صورت عدم وجود در قلمروی درونی، در قلمروی بالاتر (بیرونیتر) به روز رسانی میکند. اگر آن متغیر درکل تعریف نشده بود، یک خطای <code>ReferenceError</code> (یک یک نوع خطای استاندارد است) را تولید کند.</p>
<p>تکنیکی که برای نمایش قلمروها از اشیاء ساده استفاده می کرد که تا الان کارها را خیلی راحت کرده است، در این جا کمی مانع ایجاد خواهد کرد. ممکن است بخواهید از <bdo><code>Object.<wbr>getPrototypeOf</code></bdo> استفاده کنید، که پروتوتایپ یک شیء را بر می گرداند. همچنین به خاطر داشته باشید که قلمروها از <bdo><code>Object.prototype</code></bdo> مشتق نمیشوند، بنابراین اگر می خواهید تا <code>hasOwnProperty</code> را روی آن ها فراخوانی کنید، باید از این عبارت بدترکیب استفاده کنید.</p>
<pre class="snippet cm-s-default" data-language="javascript" ><a class="c_ident" id="c_uQ8XmGT/j0" href="#c_uQ8XmGT/j0" tabindex="-1" role="presentation"></a><span class="cm-variable">Object</span>.<span class="cm-property">prototype</span>.<span class="cm-property">hasOwnProperty</span>.<span class="cm-property">call</span>(<span class="cm-variable">scope</span>, <span class="cm-variable">name</span>);</pre>
<pre class="snippet cm-s-default" data-language="javascript" ><a class="c_ident" id="c_A8iULt4bk6" href="#c_A8iULt4bk6" tabindex="-1" role="presentation"></a><span class="cm-variable">specialForms</span>.<span class="cm-property">set</span> <span class="cm-operator">=</span> (<span class="cm-def">args</span>, <span class="cm-def">scope</span>) <span class="cm-operator">=></span> {
<span class="cm-comment">// Your code here.</span>
};
<span class="cm-variable">run</span>(<span class="cm-string-2">`</span>
<span class="cm-string-2">do(define(x, 4),</span>
<span class="cm-string-2">define(setx, fun(val, set(x, val))),</span>
<span class="cm-string-2">setx(50),</span>
<span class="cm-string-2">print(x))</span>
<span class="cm-string-2">`</span>);
<span class="cm-comment">// → 50</span>
<span class="cm-variable">run</span>(<span class="cm-string-2">`set(quux, true)`</span>);
<span class="cm-comment">// → Some kind of ReferenceError</span></pre>
<div class="solution"><div class="solution-text">
<p>باید هر بار با استفاده از <bdo><code>Object.<wbr>getPrototypeOf</code></bdo> یک قلمرو را پیمایش نمایید تا به قلمروی بیرونیتر برسید. برای هر قلمرو، از متد <code>hasOwnProperty</code> برای بررسی وجود متغیر، که با خاصیت <code>name</code> در اولین آرگومان <code>set</code> مشخص شده است، در قلمرو استفاده کنید. اگر وجود داشت، آن را برابر نتیجهی ارزیابی آرگومان دوم <code>set</code> قرار دهید و آن مقدار را برگردانید.</p>
<p><a class="p_ident" id="p_K+iMpCQsdu" href="#p_K+iMpCQsdu" tabindex="-1" role="presentation"></a>اگر به بیرونیترین قلمرو برسید ( که در این صورت <bdo><code>Object.<wbr>getPrototypeOf</code></bdo> مقدار null را برمیگرداند) و متغیر هنوز پیدا نشده باشد، آن متغیر وجود ندارد و باید یک خطا تولید شود.</p>
</div></div><nav><a href="11_async.html" title="previous chapter">◀</a> <a href="index.html" title="cover">◆</a> <a href="13_browser.html" title="next chapter">▶</a></nav>
</article>