tag:blogger.com,1999:blog-1186685524082520522024-03-13T17:26:56.895+04:00armenbadalarmenbadalhttp://www.blogger.com/profile/01200997467308938068noreply@blogger.comBlogger81125tag:blogger.com,1999:blog-118668552408252052.post-68699117174254948952022-01-20T11:07:00.002+04:002022-01-20T11:07:36.953+04:00<p style="text-align: center;">
<span style="color: #20124d; font-size: x-large;">ԽԱՆՈՒԹԸ ՓԱԿ Է</span>
</p>
<p style="text-align: center;">
<em>Գրառումները</em> շարունակում եմ <a href="https://github.com/armenbadal/notebook" target="_blank">Օրագրում</a>։
</p><p style="text-align: center;">Տե՛ս նաև իմ <a href="https://github.com/armenbadal?tab=repositories" target="_blank">GitHub</a> էջը։</p>armenbadalhttp://www.blogger.com/profile/01200997467308938068noreply@blogger.com0tag:blogger.com,1999:blog-118668552408252052.post-40442375012722289232020-07-12T22:40:00.002+04:002020-07-12T22:42:43.042+04:00Haskell: Օր առաջին. Ֆակտորիալ<p>Այս կիրակի ես վերջապես որոշեցի սկսել ծանոթությունը <a href="https://www.haskell.org/">Haskell</a> լեզվի հետ։ Haskel-ը <em>ֆունկցիանալ</em> լեզու է. երբեմն ասում են, որ այն ֆունկցիոնալների մեջ ամենաֆունկցիոնալն է։ Ես, ինչ-որ տարրական պատկերացում ունենալով <em>ֆունկցիոնալ ծրագրավորման</em> մասին, ինձ համար սահմանեցի հետևյալ առաջին խնդիրը.</p>
<p><em>Ֆակտորիալ։</em> Գրել ծրագիր, որ հրամանային տողից ստանում է որևէ դրական ամբողջ թիվ, ապա հաշվարկում և արտածում է այդ թվի ֆակտորիալը։</p>
<p>Բայց, մինչև խնդրի լուծմանն անցնելը, ես պիտի պատրաստեմ Haskell լեզվի միջավայրը, որում աշխատեցնելու եմ իմ գրած ծրագրերը։ Կարելի է, իհարկե, օգտագործել որևէ առցանց ծառայություն, ինչպիսիք են, օրինակ, <a href="https://www.tutorialspoint.com/compile_haskell_online.php">www.tutorialspoint.com</a>-ը կամ <a href="https://repl.it/languages/haskell">https://repl.it</a>-ը, բայց ես նախընտրում եմ ամեն ինչ ունենալ ձեռքի տակ՝ իմ մեքենայի վրա։</p>
<p>Իսկ իմ մեքենան <em>Raspberry Pi</em> է` Debian-ի հիման վրա կառուցված օպերացիոն համակարգով։ <em>Haskell Platform</em>-ի <a href="https://www.haskell.org/platform/linux.html#linux-debian">էջից</a> գտա, թե ինչպես է պետք տեղադրել Haskell-ի կոմպիլյատորն ու ինտերպրետատորը.</p>
<div class="sourceCode" id="cb1"><pre class="sourceCode bash"><code class="sourceCode bash"><span id="cb1-1"><a aria-hidden="true" href="#cb1-1"></a>$ <span class="fu">sudo</span> apt-get install haskell-platform</span></code></pre></div>
<p>Haskel Platform-ի կոմպիլյատորի և ինտերպրետատորի հաջող տեղադրված լինելը ստուգելու համար նախ հրամանային տողից աշխատեցեմ <code>ghci</code> ինտերպրետատորը.</p>
<div class="sourceCode" id="cb2"><pre class="sourceCode bash"><code class="sourceCode bash"><span id="cb2-1"><a aria-hidden="true" href="#cb2-1"></a>$ <span class="ex">ghci</span></span>
<span id="cb2-2"><a aria-hidden="true" href="#cb2-2"></a><span class="ex">GHCi</span>, version 8.4.4: http://www.haskell.org/ghc/ :? for help</span>
<span id="cb2-3"><a aria-hidden="true" href="#cb2-3"></a><span class="ex">Prelude</span><span class="op">></span></span></code></pre></div>
<p>Հրավերքի տողում <code>Prelude</code> ցույց է տալիս, որ ինտերպրետատորը գործարկվել է և ակտիվ է <em>Prelude</em> փաթեթը։ Խնդրեմ Հասկելին ցույց տալ π թվի արժեքը.</p>
<div class="sourceCode" id="cb3"><pre class="sourceCode bash"><code class="sourceCode bash"><span id="cb3-1"><a aria-hidden="true" href="#cb3-1"></a><span class="ex">Prelude</span><span class="op">></span> pi</span>
<span id="cb3-2"><a aria-hidden="true" href="#cb3-2"></a><span class="ex">3.141592653589793</span></span></code></pre></div>
<p>Կարծես թե աշխատում է։ Փորձեմ հենց այստեղ սահմանել ֆակտորիալը հաշվող ֆունկցիան՝ ամենապարզ մոտեցմամբ.</p>
<pre><code>Prelude> factorial n = if n == 1 then 1 else n * factorial (n - 1)</code></pre>
<p>Մի քանի օրինակներով համոզվեմ, որ սահմանած ֆունկցիան աշխատում է.</p>
<pre><code>Prelude> factorial 1
1
Prelude> factorial 5
120
Prelude> factorial 10
3628800
Prelude> factorial 100
93326215443944152681699238856266700490715968264381621468592963895217599993229915608941463976156518286253697920827223758251185210916864000000000000000000000000</code></pre>
<p>Հիմա այս ֆունկցիան գրեմ մի ֆայլի մեջ, օրինակ, <code>ex0.hs</code> անունով, ու փորձեմ այդ ֆայլը թարգմանել Հասկելի կոմպիլյատորով։</p>
<div class="sourceCode" id="cb6"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb6-1"><a aria-hidden="true" href="#cb6-1"></a><span class="co">-- Իմ առաջին ծրագիրը</span></span>
<span id="cb6-2"><a aria-hidden="true" href="#cb6-2"></a></span>
<span id="cb6-3"><a aria-hidden="true" href="#cb6-3"></a><span class="ot">factorial ::</span> <span class="dt">Integer</span> <span class="ot">-></span> <span class="dt">Integer</span></span>
<span id="cb6-4"><a aria-hidden="true" href="#cb6-4"></a>factorial n <span class="ot">=</span> <span class="kw">if</span> n <span class="op">==</span> <span class="dv">1</span> <span class="kw">then</span> <span class="dv">1</span> <span class="kw">else</span> n <span class="op">*</span> factorial (n <span class="op">-</span> <span class="dv">1</span>)</span></code></pre></div>
<p>Այստեղ ֆունկցիայի սահմանումից առաջ ավելացրել եմ նաև դրա <em>վերնագիրը</em> (կամ <em>նկարագրությունը</em>)։ Այդ նկարագրությամբ տրվում է ֆունկցիայի տիպը. <code>::</code> սիմվոլից ձախ գրված է ֆունկցիայի անունը՝ <code>factorial</code>, իսկ աջ կողմում՝ արգումենտի ու վերադարձվող արժեքի տիպերը։ Այսինքն՝ ֆունկցիան ստանում է <code>Integer</code> տիպի արգումենտ և վերադարձնում է <code>Integer</code> տիպի արժեք։ Երկրորդ տողում հենց ֆունկցիայի սահմանումն է. <code>=</code> սիմվոլից ձախ ֆունկցիայի անունն ու արգումենտն է, իսկ աջ կողմում՝ մարմինը, որը տվյալ դեպքում պարզ ճյուղավորման արտահայտություն է։</p>
<p>Haskell Platform-ում կոմպիլյատորը <code>ghc</code>—ն է։ Աշխատեցնում եմ՝ մուտքին տալով <code>ex0.hs</code> ֆայլը.</p>
<div class="sourceCode" id="cb7"><pre class="sourceCode bash"><code class="sourceCode bash"><span id="cb7-1"><a aria-hidden="true" href="#cb7-1"></a>$ <span class="ex">ghc</span> ex0.hs</span>
<span id="cb7-2"><a aria-hidden="true" href="#cb7-2"></a>[<span class="ex">1</span> of 1] Compiling Main ( ex0.hs, ex0.o )</span>
<span id="cb7-3"><a aria-hidden="true" href="#cb7-3"></a></span>
<span id="cb7-4"><a aria-hidden="true" href="#cb7-4"></a><span class="ex">ex0.hs</span>:1:1: error:</span>
<span id="cb7-5"><a aria-hidden="true" href="#cb7-5"></a> <span class="ex">The</span> IO action ‘main’ is not defined in module ‘Main’</span>
<span id="cb7-6"><a aria-hidden="true" href="#cb7-6"></a> <span class="kw">|</span></span>
<span id="cb7-7"><a aria-hidden="true" href="#cb7-7"></a><span class="ex">1</span> <span class="kw">|</span></span>
<span id="cb7-8"><a aria-hidden="true" href="#cb7-8"></a> <span class="kw">|</span> ^</span></code></pre></div>
<p>Սխալի հաղորդագրությունն ասում է, որ <code>Main</code> մոդուլում սահմանված չէ <code>main</code> գործողությունը։ Բանից պարզվում է, որ Հասկելի կոմպիլյատորը նույնպես (ինչպես, օրինակ, Սի լեզվի կոմպիլյատորը) որպես մուտքի կետ է համարում <code>main</code> գործողությունը։ Հիմա <code>ex0.hs</code> ֆայլում ավելացնում եմ <code>main</code> գործողությունն այնպես, որ այն արտածի <code>12</code>-ի ֆակտորիալը.</p>
<div class="sourceCode" id="cb8"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb8-1"><a aria-hidden="true" href="#cb8-1"></a><span class="co">-- Իմ առաջին ծրագիրը</span></span>
<span id="cb8-2"><a aria-hidden="true" href="#cb8-2"></a>factorial n <span class="ot">=</span> <span class="kw">if</span> n <span class="op">==</span> <span class="dv">1</span> <span class="kw">then</span> <span class="dv">1</span> <span class="kw">else</span> n <span class="op">*</span> factorial (n <span class="op">-</span> <span class="dv">1</span>)</span>
<span id="cb8-3"><a aria-hidden="true" href="#cb8-3"></a></span>
<span id="cb8-4"><a aria-hidden="true" href="#cb8-4"></a><span class="co">-- Մուտքի կետը</span></span>
<span id="cb8-5"><a aria-hidden="true" href="#cb8-5"></a>main <span class="ot">=</span></span>
<span id="cb8-6"><a aria-hidden="true" href="#cb8-6"></a> <span class="fu">print</span> (factorial <span class="dv">12</span>)</span></code></pre></div>
<p>Նորից փորձեմ թարգմանել։ Ի դեպ, Հասկել լզվում <code>--</code> սիմվոլով սկսվում են մեկնաբանությունները։</p>
<div class="sourceCode" id="cb9"><pre class="sourceCode bash"><code class="sourceCode bash"><span id="cb9-1"><a aria-hidden="true" href="#cb9-1"></a>$ <span class="ex">ghc</span> ex0.hs</span>
<span id="cb9-2"><a aria-hidden="true" href="#cb9-2"></a>[<span class="ex">1</span> of 1] Compiling Main ( ex0.hs, ex0.o )</span>
<span id="cb9-3"><a aria-hidden="true" href="#cb9-3"></a><span class="ex">Linking</span> ex0 ...</span></code></pre></div>
<p>Արդեն ամեն ինչ լավ է։ Իմ գրած ծրագիրը թարգմանվեց (compile), կապակցվեց (link), և հիմա կարող եմ աշխատեցնել ու տեսնել արդյունքը.</p>
<div class="sourceCode" id="cb10"><pre class="sourceCode bash"><code class="sourceCode bash"><span id="cb10-1"><a aria-hidden="true" href="#cb10-1"></a>$ <span class="ex">./ex0</span></span>
<span id="cb10-2"><a aria-hidden="true" href="#cb10-2"></a><span class="ex">479001600</span></span></code></pre></div>
<p>Բայց այս ծրագիրը կարողանում է հաշվել ու տպել միայն <code>12</code>-ի ֆակտորիալը։ Իսկ ես ուզում եմ, որ այն կարողանա հաշվել հրամանային տողում տրված թվի ֆակտորիալը։ ՄԻ քիչ քչփորելուց հետո պարզեցի, որ Հասկել ծրագրում գրամանային տողի պարամետրերը կարելի է վերցնել <code>System.Environment</code> մոդուլի <code>getArgs</code> գործողությամբ։ Օրինակ, հետևյալ ծրագիրը (գրառված <code>ex1.hs</code> ֆայլում) արտածում է հրամանային տողում տրված պարամետրերի ցուցակը.</p>
<div class="sourceCode" id="cb11"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb11-1"><a aria-hidden="true" href="#cb11-1"></a><span class="co">-- Հրամանային տողի պարամետրերի ցուցադրություն</span></span>
<span id="cb11-2"><a aria-hidden="true" href="#cb11-2"></a></span>
<span id="cb11-3"><a aria-hidden="true" href="#cb11-3"></a><span class="kw">import</span> <span class="dt">System.Environment</span></span>
<span id="cb11-4"><a aria-hidden="true" href="#cb11-4"></a></span>
<span id="cb11-5"><a aria-hidden="true" href="#cb11-5"></a>main <span class="ot">=</span> <span class="kw">do</span></span>
<span id="cb11-6"><a aria-hidden="true" href="#cb11-6"></a> args <span class="ot"><-</span> getArgs</span>
<span id="cb11-7"><a aria-hidden="true" href="#cb11-7"></a> <span class="fu">print</span> args</span></code></pre></div>
<p>Ահա թարգմանության ու կատարման մի քանի օրինակ.</p>
<div class="sourceCode" id="cb12"><pre class="sourceCode bash"><code class="sourceCode bash"><span id="cb12-1"><a aria-hidden="true" href="#cb12-1"></a>$ <span class="ex">./ex1</span></span>
<span id="cb12-2"><a aria-hidden="true" href="#cb12-2"></a>[]</span>
<span id="cb12-3"><a aria-hidden="true" href="#cb12-3"></a>$ <span class="ex">./ex1</span> a</span>
<span id="cb12-4"><a aria-hidden="true" href="#cb12-4"></a>[<span class="st">"a"</span>]</span>
<span id="cb12-5"><a aria-hidden="true" href="#cb12-5"></a>$ <span class="ex">./ex1</span> a bb</span>
<span id="cb12-6"><a aria-hidden="true" href="#cb12-6"></a>[<span class="st">"a"</span>,<span class="st">"bb"</span>]</span>
<span id="cb12-7"><a aria-hidden="true" href="#cb12-7"></a>$ <span class="ex">./ex1</span> a bb ccc</span>
<span id="cb12-8"><a aria-hidden="true" href="#cb12-8"></a>[<span class="st">"a"</span>,<span class="st">"bb"</span>,<span class="st">"ccc"</span>]</span>
<span id="cb12-9"><a aria-hidden="true" href="#cb12-9"></a>$ <span class="ex">./ex1</span> 1 22 333</span>
<span id="cb12-10"><a aria-hidden="true" href="#cb12-10"></a>[<span class="st">"1"</span>,<span class="st">"22"</span>,<span class="st">"333"</span>]</span></code></pre></div>
<p>Այստեղից երևում է, որ հրամանային տողի պարամետրերը ծրագրում երևում են <em>տեքստային</em> արժեքների ցուցակի տեսքով։ Ես պետք է թվի տեքստային ներկայացումից ստանամ դրա <em>թվային արժեքը</em>, ապա այդ արժեքի նկատմամբ կիրառեմ <code>factorial</code> ֆունկցիան:</p>
<p>Հասկելի <code>read</code> ֆուկցիան տեքսից «կարդում» է որևէ տիպի արժեք։ Այդ տիպը տրվում է ֆունկցիայի կանչի հետ՝ <code>::</code> սիմվոլոլից հետո։ Օրինակ, «<code>read "12" :: Int</code>» արտահայտությունը <code>"12"</code> տողից կարդում է <code>Int</code> տիպի <code>12</code> արժեքը։ «<code>read "12" :: Float</code>» արտահայտությունը նույն տողից կարդում է <code>12.0</code> արժեքը՝ <code>Float</code> տիպի։</p>
<p>Այսպիսով, ես պետք է վերցնեմ հրամանային տողի պարամետրերի ցուցակի առաջին տարրը (<code>head</code> ֆունկցիայիով), դրա նկատմամբ կիրառեմ <code>read</code> ֆունկցիան՝ <code>Integer</code> տիպի համար, ստացված արժեքի նկատմամբ կիրառեմ <code>factorial</code>-ը ու տպեմ ստացված արժեքը։ Ահա այսպիսի մի արտահայտություն <code>main</code> ֆունկցիայում.</p>
<div class="sourceCode" id="cb13"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb13-1"><a aria-hidden="true" href="#cb13-1"></a><span class="fu">print</span> (factorial (<span class="fu">read</span> (<span class="fu">head</span> args)<span class="ot"> ::</span> <span class="dt">Integer</span>))</span></code></pre></div>
<p>Ձևափոխված <code>ex0.hs</code> ծրագիրը կունենա հետևյալ վերջնական տեսքը.</p>
<div class="sourceCode" id="cb14"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb14-1"><a aria-hidden="true" href="#cb14-1"></a><span class="kw">import</span> <span class="dt">System.Environment</span></span>
<span id="cb14-2"><a aria-hidden="true" href="#cb14-2"></a></span>
<span id="cb14-3"><a aria-hidden="true" href="#cb14-3"></a><span class="co">-- Ֆակտորիալի հաշվարկը</span></span>
<span id="cb14-4"><a aria-hidden="true" href="#cb14-4"></a><span class="ot">factorial ::</span> <span class="dt">Integer</span> <span class="ot">-></span> <span class="dt">Integer</span></span>
<span id="cb14-5"><a aria-hidden="true" href="#cb14-5"></a>factorial n <span class="ot">=</span> </span>
<span id="cb14-6"><a aria-hidden="true" href="#cb14-6"></a> <span class="kw">if</span> n <span class="op">==</span> <span class="dv">1</span> </span>
<span id="cb14-7"><a aria-hidden="true" href="#cb14-7"></a> <span class="kw">then</span> <span class="dv">1</span></span>
<span id="cb14-8"><a aria-hidden="true" href="#cb14-8"></a> <span class="kw">else</span> n <span class="op">*</span> factorial (n <span class="op">-</span> <span class="dv">1</span>)</span>
<span id="cb14-9"><a aria-hidden="true" href="#cb14-9"></a></span>
<span id="cb14-10"><a aria-hidden="true" href="#cb14-10"></a><span class="co">-- Մուտքի կետ</span></span>
<span id="cb14-11"><a aria-hidden="true" href="#cb14-11"></a><span class="ot">main ::</span> <span class="dt">IO</span> ()</span>
<span id="cb14-12"><a aria-hidden="true" href="#cb14-12"></a>main <span class="ot">=</span> <span class="kw">do</span></span>
<span id="cb14-13"><a aria-hidden="true" href="#cb14-13"></a> args <span class="ot"><-</span> getArgs</span>
<span id="cb14-14"><a aria-hidden="true" href="#cb14-14"></a> <span class="fu">print</span> (factorial (<span class="fu">read</span> (<span class="fu">head</span> args)<span class="ot"> ::</span> <span class="dt">Integer</span>))</span></code></pre></div>
<p>Լավ. տեսնենք, թե սա ինչպես է աշխատում։</p>
<div class="sourceCode" id="cb15"><pre class="sourceCode bash"><code class="sourceCode bash"><span id="cb15-1"><a aria-hidden="true" href="#cb15-1"></a>$ <span class="ex">ghc</span> ex0.hs</span>
<span id="cb15-2"><a aria-hidden="true" href="#cb15-2"></a>[<span class="ex">1</span> of 1] Compiling Main ( ex0.hs, ex0.o )</span>
<span id="cb15-3"><a aria-hidden="true" href="#cb15-3"></a><span class="ex">Linking</span> ex0 ...</span>
<span id="cb15-4"><a aria-hidden="true" href="#cb15-4"></a>$ <span class="ex">./ex0</span> 2</span>
<span id="cb15-5"><a aria-hidden="true" href="#cb15-5"></a><span class="ex">2</span></span>
<span id="cb15-6"><a aria-hidden="true" href="#cb15-6"></a>$ <span class="ex">./ex0</span> 12</span>
<span id="cb15-7"><a aria-hidden="true" href="#cb15-7"></a><span class="ex">479001600</span></span>
<span id="cb15-8"><a aria-hidden="true" href="#cb15-8"></a>$ <span class="ex">./ex0</span> 20</span>
<span id="cb15-9"><a aria-hidden="true" href="#cb15-9"></a><span class="ex">2432902008176640000</span></span>
<span id="cb15-10"><a aria-hidden="true" href="#cb15-10"></a>$ <span class="ex">./ex0</span> 40</span>
<span id="cb15-11"><a aria-hidden="true" href="#cb15-11"></a><span class="ex">815915283247897734345611269596115894272000000000</span></span></code></pre></div>
<p>Լավ էլ աշխատում է։ Բայց, իհարկե, թերություններ կան։ Առաջին թերությունը տեխնիկական է. դիտարկված չէ այն դեպքը, երբ հրամանային տողում ոչինչ տրված չէ։ Օրինակ, եթե աշխատեցնեմ ծրագիրը՝ հրամանային տողում ոչինչ չտալով, ապա կստանամ հաղորդագրություն այն մասին, որ <code>head</code> ֆունկցիային տրված է դատարկ ցուցակ.</p>
<div class="sourceCode" id="cb16"><pre class="sourceCode bash"><code class="sourceCode bash"><span id="cb16-1"><a aria-hidden="true" href="#cb16-1"></a>$ <span class="ex">./ex0</span></span>
<span id="cb16-2"><a aria-hidden="true" href="#cb16-2"></a><span class="ex">ex0</span>: Prelude.head: empty list</span></code></pre></div>
<p>Սա պետք է ուղղել՝ <code>main</code> գործողության մեջ պայման գրելով։ Այսպես.</p>
<div class="sourceCode" id="cb17"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb17-1"><a aria-hidden="true" href="#cb17-1"></a>main <span class="ot">=</span> <span class="kw">do</span></span>
<span id="cb17-2"><a aria-hidden="true" href="#cb17-2"></a> args <span class="ot"><-</span> getArgs</span>
<span id="cb17-3"><a aria-hidden="true" href="#cb17-3"></a> <span class="kw">if</span> <span class="fu">not</span> (<span class="fu">null</span> args)</span>
<span id="cb17-4"><a aria-hidden="true" href="#cb17-4"></a> <span class="kw">then</span> <span class="fu">print</span> (factorial (<span class="fu">read</span> (<span class="fu">head</span> args)<span class="ot"> ::</span> <span class="dt">Integer</span>))</span>
<span id="cb17-5"><a aria-hidden="true" href="#cb17-5"></a> <span class="kw">else</span> <span class="fu">putStrLn</span> <span class="st">"Ոչինչ տրված չէ։"</span></span></code></pre></div>
<p>Հիմա եթե ծրագիրն աշխատեցնեմ դատարկ հրամանային տողով, ապա որպես պատասխան կստանամ «<code>Ոչինչ տրված չէ։</code>»։</p>
<p>Հաջորդիվ. թերևս Հասկել լեզվով գրող ոչ մի ծրագրավորող թվի ֆակտորիալը հաշվող ֆունկցիան չի գրի այնպես, ինչպես ես գրել եմ։ Վարպետ Հասկել-ծրագրավորողը պարզապես կգրի.</p>
<div class="sourceCode" id="cb18"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb18-1"><a aria-hidden="true" href="#cb18-1"></a><span class="ot">factorial ::</span> <span class="dt">Integer</span> <span class="ot">-></span> <span class="dt">Integer</span></span>
<span id="cb18-2"><a aria-hidden="true" href="#cb18-2"></a>factorial n <span class="ot">=</span> <span class="fu">product</span> [<span class="dv">1</span> <span class="op">..</span> n]</span></code></pre></div>
<p>Եվ վերջ։ Այստեղ գրված է ֆակտորիալի բառացի սահմանումը՝ այն <code>1</code>-ից <code>n</code> թվերի (<code>[1 .. n]</code>) արտադրյալն է (<code>product</code>):</p>
armenbadalhttp://www.blogger.com/profile/01200997467308938068noreply@blogger.com0tag:blogger.com,1999:blog-118668552408252052.post-87622196831088309202020-04-06T15:21:00.003+04:002020-05-08T17:01:03.786+04:00Գրաֆի գագաթների տոպոլոգիական թվարկման մասինՆախ նկարագրեմ <em>ուղղորդված գրաֆի</em> (directed graph, digraph) մոդելը որպես
<code>digraph</code> դասի կաղապար։ Այս կաղապարի պարամետրն այն տիպն է, որ նմուշը գրառվելու
է գրավի գագաթում։ Այդ տիպի վրա դրված մակ պահանջն այն է, որ նրա համար սահմանված
լինի հավասարության ստուգման <code>==</code> գործողությունը։ Այդ գործողությունն օգտագործելու
եմ տրված տվյալը պարունակող գագաթի ինդեքսը որոնելու համար։
<pre class='prettyprint lang-cpp'>
template<typename Y>
class digraph {
/// ...
};
</pre>
Գրաֆի ներքին ներկայացման համար ընտրել եմ գագաթների հարևանության ցուցակների եղանակը։
Գագաթները հերթականությամբ ավելացնում եմ միաչափ զանգվածում (vector): Իսկ ամեն մի
գագաթի (vertex) համապատասխանեցնում եմ նրան հարևան գագաթների ինդեքսների ցուցակը։
Քանի որ որոշել եմ գագաթում գրառել կաղապարի պարամետրով տրված ցանկացած տիպի օբյեկտ,
մի գագաթի նկարագրությունը սահմանել եմ <code>vertex</code> տիպով.
<pre class='prettyprint lang-cpp'>
using index = int;
using index_list = std::vector<index>;
using vertex = std::pair<Y,index_list>;
</pre>
Դե իսկ գրաֆի գագաթների ցուցակն էլ արդեն կսահմանեմ հետևյալ կերպ.
<pre class='prettyprint lang-cpp'>
std::vector<vertex> _vertices;
</pre>
Գրաֆը կառուցելու եմ կողերը հաջորդաբար ավելացնելով։ <code>add_edge</code> մեթոդը ստանում է
կաղապարի <code>Y</code> տիպի երկու արժեք և գրաֆում կոդ է ավելացնում այդ արժեքները պարունակող
գագաթների միջև։ Եթե այդպիսի գագաթներ դեռ չկան, ապա գրաֆում ավելացնում է նաև
գագաթները։
<pre class='prettyprint lang-cpp'>
void add_vertex(const Y& u, const Y& v)
{
auto ui = add_vertex(u);
auto vi = add_vertex(v);
_vertices[ui].second.push_back(vi);
}
</pre>
Այստեղ օգտագործված <code>add_vertex</code> մեթոդը գրաֆում ավելացնում է տրված արժեքը պարունակող
գագաթ։ Եթե այդպիսին արդեն կա, ապա վերադարձնում է դրա ինդեքսը։
<pre class='prettyprint lang-cpp'>
index add_vertex(const Y& v)
{
// որոնել
for( index ix = 0; ix < _vertices.size(); ++ix )
if( _vertices[ix].first == v )
return ix;
// ավելացնել
_vertices.push_back(v, {});
return _vertices.size() - 1;
}
</pre>
Այսքանը լրիվ հերիք է գրաֆի կառուցման համար։ Թերևս կարելի է ավելացնել <code>to_string</code>
մեթոդը, որը կվերադարձնի գրաֆի ինչ-որ տեքստային ներկայացում. պարզապես շտկումների
համար։
<pre class='prettyprint lang-cpp'>
std::string to_string()
{
std::ostringstream oss;
for( auto& e : _vertices ) {
oss << e.first << " -> { ";
for( auto& i : e.second )
oss << _vertices[i].first << ' ';
oss << '}' << std::endl;
}
return oss.str();
}
</pre>
Այս մեթոդը պահանջում է, որ կաղապարի <code>Y</code> տիպի համար սահմանված լինի արտածման հոսքի մեջ
ուղարկելու գործողությունը՝ <code>operator<<</code>։
Անցնեմ գրաֆի գագաթների տոպոլոգիական թվարկման իրականացմանը։ Տոպոլոգիական կարգավորման
մասին բան չեմ գրի. կարելի է կարդալ, օրինակ, Ուիքիփեդիայի
<a href="https://en.wikipedia.org/wiki/Topological_sorting">Topological Sorting</a> էջը։ Միայն
ասեմ, որ իրականացման համար ընտրել եմ Kahn-ի ալգորիթմը։ Մի քանի բառով դրա էությունը
հետևյալն է. ամենասկզբում ստեկի մեջ են ավելացնում (push) բոլոր այն գագաթները, որոնք
նախորդող չունեն (աղեղները դրանցից միայն դուրս են գալիս)։ Այնուհետև, քանի դեռ ստեկը
դատարկ չէ, ստեկից հանում ենք <em>u</em> գագաթը, ապա գրաֆից հեռացնում ենք (pop) <em>u</em> գագաթից
դուրս եկող բոլոր կողերը։ Եթե այս գործողության արդյունքում հայտնվում է գագաթ, որը դեպի
իրեն եկող կող չունի, ապա այդ գագաթն ավելացնում ենք (push) ստեկում։ Քանի որ գրաֆից
կող հեռացնելը կվնասեր մեր սկզբնական գրաֆը, այդ «կող հեռացնել» գործողությունը
մոդելավորվում է մի վեկտորով, որում ամեն մի գագաթի համար պահվում է դեպի իրեն եկող
կողերի քանակը։
Գրաֆի գագաթների՝ տոպոլոգիական կարգով թվարկումը իրականացրել եմ <code>enumerate</code> մեթոդում,
որի պարամետրը ֆունկցիա է։ Այս ֆունկցիայով տրվում է այն գործողությունը, որը պետք է
կատարվի գրաֆի թվարկվող հերթական գագաթի հետ։ Ավելի մանրամասն՝ մեկնաբանություններում.
<pre class='prettyprint lang-cpp'>
void enumerate(std::function<void(const Y&)> f)
{
// գրաֆի գագաթների քանակը
const std::size_t sz = _vertices.size();
// degrees զանգվածում գրաֆի ամեն մի գագաթի համար
// հաշվում ենք դեպի այն եկող կողերի քանակը
std::vector<std::size_t> degrees(sz);
for( auto& e : _vertices )
for( auto& n : e.second )
degrees[n] += 1;
// նախապես S ստեկում ավելացնում ենք այն գագաթները,
// որոնք նախորդող չունեն
std::stack<index> S;
for( std::size_t i = 0; i < degrees.size(); ++i )
if( degrees[i] == 0 )
S.push(i);
// իտերացիա գագաթներով
while( !S.empty() ) { // քանի դեռ ստեկը դատարկ չէ
// վերցնել գագաթի տարրը
index ix = S.top();
S.pop();
// տրված գործողությունը կատարել գագաթում գրառված
// տվյալների հետ
f(_vertices[ix].first);
// դիտարկվող գագաթի բոլոր հարևանների համար...
for( auto& vi : _vertices[ix].second ) {
// ... եթե այդ հարևանը նախորդոնղեր չունի, ապա
// այն արդեն մասնակցել է թվարկմանը, ...
if( degrees[vi] == 0 )
continue;
// «կողը հեռացնելու» գործողության մոդելը.
// պակասեցնում ենք հաշվիչը, ...
degrees[vi] -= 1;
// ... եթե հաշվիչը դառնում է զրո, ապա այդ գագաթը
// ավելացնում ենք ստեկում
if( degrees[vi] == 0 )
S.push(vi);
}
}
}
</pre>
Փորձարկենք ալգորիթմը ստորև բերված գրաֆի վրա՝ որպես թվարկման գործողություն
տալով պարզապես գագաթի պարունակությունը տպող որևէ ֆունկցիա։
<center>
<a href="https://1.bp.blogspot.com/-QdddGAKKnus/XosQAff2OLI/AAAAAAAAEfM/s4iUZLFvy_sf9n1O4W1xCEpe2dTEBVsZQCNcBGAsYHQ/s1600/graph-0.jpg" imageanchor="1" ><img border="0" src="https://1.bp.blogspot.com/-QdddGAKKnus/XosQAff2OLI/AAAAAAAAEfM/s4iUZLFvy_sf9n1O4W1xCEpe2dTEBVsZQCNcBGAsYHQ/s320/graph-0.jpg" width="257" height="320" data-original-width="1286" data-original-height="1600" /></a>
</center>
Սահմանում եմ գրաֆ, դրանում ավելացնում եմ նկարի գրաֆի կողերը, հետո <code>to_string</code>
մեթոդով տպում եմ գրաֆի տեքստային ներկայացումը։ Թեսթավորման համար պարզապես
ուզում եմ տպել գագաթները տոպոլոգիական կարգով։ Դրա համար <code>enumerate</code> մեթոդի
արգումենտում տալիս եմ լամբդա-ֆունկցիա, որը պարզապես արտածում է իր արգումենտը։
<pre class='prettyprint lang-cpp'>
int main()
{
graphs::digraph<std::string> g0;
g0.add_edge("a", "q0");
g0.add_edge("b", "q0");
g0.add_edge("b", "q1");
g0.add_edge("c", "q1");
g0.add_edge("q0", "x");
g0.add_edge("q1", "x");
g0.add_edge("q0", "y");
g0.add_edge("q1", "y");
std::cout << "Graph: " << std::endl << g0.to_string() << std::endl;
std::cout << "Topological order of vertices: " << std::endl;
g0.enumerate([&](auto& s) {std::cout << s << ' '; });
}
</pre>
Կատարման արդյունքում արտածվում է հետևյալը.
<pre class='prettyprint lang-text'>
Graph:
a -> { q0 }
q0 -> { x y }
b -> { q0 q1 }
q1 -> { x y }
c -> { q1 }
x -> { }
y -> { }
Topological order of vertices:
c b q1 a q0 y x
</pre>
Լավ է։ Բայց ես ուզում եմ գրաֆի համար իրականացնել նրա գանաթերը տոպոլոգիական
կարգով թվարկող <em>իտերատոր</em>. այնպիսին, ինչպիսիք սահմանված են STL-ում։ Այդ տիպի
իտերատորի առկայության դեպքում կարող եմ գրել այսպիսի կոդ.
<pre class='prettyprint lang-cpp'>
for( auto& v : g0 )
std::cout << v << ' ';
</pre>
Պարզ է, որ այդ իտերատորի մեթոդների իրականացումը պետք է արտացոլի նույն
<code>enumerate</code> մեթոդի վարքը։ <code>digraph</code> դասում սահմանում եմ ներդրված <code>iterator</code>
դասը։ range-for-ի աշխատանքն ապահովելու համար իտերատորում պետք է իրականացնել
<code>operator*</code>, <code>operator++</code> և <code>operator!=</code> գործողությունները։ Իտերատորի
կոնստրուկտորում հաշվարկվում են բոլոր գագաթների <em>մուտքային կիսաաստիճանները</em>՝
տվյալ գագաթին ուղղված կողերի քանակը, ապա ստեկի մեջ են ավելացվում այն գագաթների
ինդեքսները, դեպի որոնց եկող կողեր չկան։ <code>operator*</code>-ը վերադարձնում է հղում
հերթական գագաթում գրառված տվյալին։ <code>operator++</code>-ը իտերատորը փոխանցում է հաջորդ
գագաթին։ <code>operator!=</code>-ը համեմատում է երկու իտերատորների հերթական գագաթները։
Նորից՝ մանրամասները կոդի մեկնաբանություններում։
<pre class='prettyprint lang-cpp'>
template<typename Y>
class digraph {
// ...
public:
class iterator {
public:
using iterator_category = std::forward_iterator_tag;
using value_type = Y;
//
iterator(const digraph& g, bool init)
: _ref{g}
{
if( init ) {
// հաշվարկվում են մուտքային կիսաաստիճանները
_degrees.resize(_ref._vertices.size());
for( auto& e : _ref._vertices )
for( auto& n : e.second )
_degrees[n] += 1;
// ստեկում ավելացնել բոլոր «սկզբնական» գագաթների ինդեքսները
for( std::size_t i = 0; i < _degrees.size(); ++i )
if( _degrees[i] == 0 )
_S.push(i);
// կատարել առաջին քայլը՝ արժեքավորել դիտարկվող գագաթի ինդեքսը
this->operator++();
}
}
//
iterator& operator++()
{
// եթե ստեկը դատարկ չէ, ապա անցնել հերթական գագաթին, հակառակ
// դեպքում հերթական գագաթի ինդեքսին վերագրել -1
if( !_S.empty() ) {
_current = _S.top();
_S.pop();
for( auto& vi : _ref._vertices[_current].second ) {
if( _degrees[vi] == 0 )
continue;
_degrees[vi] -= 1;
if( _degrees[vi] == 0 )
_S.push(vi);
}
}
else
_current = -1;
return *this;
}
//
bool operator!=(const iterator& other)
{
// համեմատել երկու իտերատորներում ընթացիկ գագաթի ինդեքսը
return _current != other._current;
}
//
const value_type& operator*() const
{
// վերադարձնել ընթացիկ գագաթում պահվող տվյալը
return _ref._vertices[_current].first;
}
private:
const digraph& _ref; // հղում գրաֆին
index _current = -1; // հերթական դիտարկվող գագաթի ինդեքսը
std::vector<std::size_t> _degrees; // գագափթների մուտքային կիսաաստիճանները
std::stack<index> _S; // ինդեքսների ստեկը
};
};
</pre>
Մնում է հիմա <code>digraph</code> դասի համար իրականացնել իտերատոր վերադարձնող <code>begin</code> և <code>end</code> մեթոդները։
<pre class='prettyprint lang-cpp'>
iterator begin()
{
return iterator(*this, true);
}
iterator end()
{
return iterator(*this, false);
}
</pre>
Իտերատորի բերված իրականացումը և <code>digraph</code> դասի <code>begin</code> և <code>end</code> մեթոդներն այն նվազագույնն են, որոնք ապահովում են range-for տիպի ցիկլի աշխատանքը։armenbadalhttp://www.blogger.com/profile/01200997467308938068noreply@blogger.com0tag:blogger.com,1999:blog-118668552408252052.post-56741591393016495022019-11-19T12:38:00.000+04:002019-11-19T12:38:34.413+04:00Էսսե տեղադրությունների մասին<p>— Ուսուցի՛չ,— ասաց Ուաո Գոն,— ինչպե՞ս կարելի է գեներացնել տողի բոլոր <i>տեղադրությունները</i> (<i>permutations</i>):</p>
<p>Կոնֆուցիոսը մի քիչ մտածեց ու հիշեց, որ այդ մասին կարդացել է Կնուտի «Ծրագրավորման արվեսստը» գրքի չորրորդ հատորում (Donald Knuth, «The Art of Computer Programming», vol. 4A)։ Այդ գրքի <i>7.2.1.2 Generating all permutations</i> բաժնում պատմվում է տեղադրությունները գեներացնելու զանազան ալգորիթմների մասին. ինչպես միշտ՝ Կնուտն իր բարձունքի վրա է։</p>
<p>— Չգիտեմ։ Կարդացեք դասականներին,— ասաց Կոնֆուցիոսը մի քիչ էլ մտածելուց հետո։</p>
<center><b>* * *</b></center>
<p>Հաջորդ օրը Կոնֆուցիոսը տաճար մտավ ու տեսավ Ուաո Գոին աղոթելիս։ Կանչեց նրան իր սեղանի մոտ ու ցույց տվեց տախտակների վրա գրված այս տեքստը.</p>
<pre class="prettyprint lang-lisp">(defun insert-at (e l i)
(if (zerop i)
(cons e l)
(cons (car l) (insert-at e (cdr l) (1- i)))))
(defun range (e r)
(if (= 0 e)
(cons 0 r)
(range (1- e) (cons e r))))
(defun insert-at-all (e l)
(mapcar #'(lambda (i) (insert-at e l i))
(range (length l) '())))
(defun insert-to-all-items (e ls)
(apply #'append (mapcar #'(lambda (q) (insert-at-all e q)) ls)))
(defun permutations-of (l)
(if (null (cdr l))
(list l)
(insert-to-all-items (car l) (permutations-of (cdr l)))))
</pre>
<p>— Ի՞նչ է սա, ուսուցի՛չ,— հարցրեց Ուաո Գոն։</p>
<p>— <code>permutations-of-string</code>-ը գեներացնում է տրված տողի բոլոր տեղադրությունները։</p>
<p>— Ինչպե՞ս։</p>
<p>— Տե՛ս։ Մի որևէ հաջորդականության բոլոր տեղադրությունները ստանալու համար կարելի է առանձնացնել դրա տարրերից մեկը, օրինակ առաջինը, ապա, <i>ռեկուրսիվ</i> եղանակով, հաշվել մյուս տարրերի հաջորդականության բոլոր տեղադրությունները և վերջում առանձնացված տարրը «խցկել» կառուցված տեղադրությունների բոլոր հնարավոր դիրքերում՝ ամեն մի «խցկելու» գործողությամբ գեներացնելով նոր տեղադրություն։ Պա՞րզ է։</p>
<p>— Լավ կլիներ օրինակ բերեիք, ուսուցի՛չ։</p>
<p>— Լավ։ Վերցնենք <code>{a, b, c}</code>։ Առաձնացնենք դրա առաջին տարրը՝ <code>a</code>-ն, մնացած տարրերի հաջորդականությունը կլինի <code>{b, c}</code>: Այս վերջինիս բոլոր հնարավոր տեղադրություններն են.</p>
<pre class="prettyprint lang-text">{(b, c), (c, b)}
</pre>
<p>Հիմա առանձնացված <code>a</code> տարրը տեղադրենք այս բազմության բոլոր տարրերի բոլոր դիրքերում։ <code>(b, c)</code>-ի համար կստանանք.</p>
<pre class="prettyprint lang-text">(a, b, c), (b, a, c), (b, c, a)
</pre>
<p>իսկ <code>(c, b)</code>-ի համար էլ.</p>
<pre class="prettyprint lang-text">(a, c, b), (c, a, b), (c, b, a)
</pre>
<p>Ահա սրանց միավորումն էլ հենց <code>{a, b, c}</code> հաջորդականության բոլոր տեղադրություններն են.</p>
<pre class="prettyprint lang-text">{(a, b, c), (b, a, c), (b, c, a), (a, c, b), (c, a, b), (c, b, a)}
</pre>
<p>— Ուսուցի՛չ, ո՞րն է ռեկուրսիայի տարրական դեպքը։</p>
<p>— Դա միայն մեկ տարր ունեցող հաջորդականությունն է։ Օրինակ, <code>{a}</code>-ի բոլոր տեղադրությունների բազմությունն է. <code>{(a)}</code>։</p>
<p>— Իսկ ի՞նչ հեզվով են գրված ձեր տախտակները, ուսուցի՛չ։</p>
<p>— Օ՜, դա Լիսպն է, լեզուների մեջ վեհագույնը։</p>
<p>— Թույլ տվեք մի անգամ էլ նայել տախտակներին,— խնդրեց Ուաո Գոն։</p>
<p>— Ահա՛։</p>
<p>— Թարգմանեք, խնդրում եմ, շատ հետաքրքիր է։</p>
<p>— Լավ։ Սկսենք առաջին տախտակից՝ ամենապարզից։ Ինչպես տեսար, տեղադրություններ կառուցելու տարրական գործողությունը տրված հաորդականության տրված դիրքում մի որևէ տարր խցկելն է։ Օրինակ, եթե հաջորդականությունն ունի երեք տարր՝ <code>abc</code>, ապա գոյություն ունեն նոր տարրը խցկելու 4 հնարավոր դիրքեր՝ <code>₀a₁b₂c₃</code>։ <code>insert-at</code> գործողությունը <code>e</code> տարրը տեղադրում է <code>l</code> ցուցակի <code>i</code>-րդ դիրքում։</p>
<pre class="prettyprint lang-lisp">(defun insert-at (e l i)
(if (zerop i)
(cons e l)
(cons (car l) (insert-at e (cdr l) (1- i)))))
</pre>
<p>Հաջորդ տախտակի վրա գրված <code>insert-at-all</code> գործողությունը <code>e</code> տարրը խցկում է <code>l</code> ցուցակի բոլոր թույլատրելի դիրքերում (դրանք <code>|l|+1</code> հատ են), և վերադարձնում է խցկելու յուրաքանչյուր գործողությունից «ծնված» ցուցակների ցուցակը։ <code>range</code> օժանդակ ֆունկցիան պարզապես կառուցում է տարրը տեղադրելու ինդեքսների ցուցակը։</p>
<pre class="prettyprint lang-lisp">(defun range (e r)
(if (= 0 e)
(cons 0 r)
(range (1- e) (cons e r))))
(defun insert-at-all (e l)
(mapcar #'(lambda (i) (insert-at e l i))
(range (length l) '())))
</pre>
<p>Երրորդ տախտակի <code>insert-to-all-items</code> գործողությունը <code>insert-at-all</code> ֆունկցիան կիրառում է <code>ls</code> ցուցակի բոլոր տարրերի նկատմամբ, և այդ կիրառություններից ստացված բոլոր ցուցակները միավորում է մի ընդհանուրի մեջ։</p>
<pre class="prettyprint lang-lisp">(defun insert-to-all-items (e ls)
(apply #'append (mapcar #'(lambda (q) (insert-at-all e q)) ls)))
</pre>
<p>Դե, իսկ չորրորդ տախտակին գրված <code>permutations-of</code> գործողությունը հենց խնդրի բուն լուծումն է՝ ռեկուրսիայի կազմակերպմամբ։ Եթե տրված <code>l</code> ցուցակը միայն մի տարր ունի, ապա պատասխանը հենց այդ ցուցակը պարունակող ցուցակն է։ Ռեկուրսիայի քայլում կառուցվում է ցուցակի պոչի տեղադրությունների բազմությունը, ապա՝ <code>insert-to-all-items</code> նախնական ցուցակի առաջին տարրը ավելացվում է բոլոր այդ տեղադրություններին։<p>
<pre class="prettyprint lang-lisp">(defun permutations-of (l)
(if (null (cdr l))
(list l)
(insert-to-all-items (car l) (permutations-of (cdr l)))))
</pre>
<p>— Իսկ ինչպե՞ս ենք կառուցելու _տողի_ բոլոր տեղադրությունների բազմությունը, ուսուցի՛չ։</p>
<p>— Դրա համար պետք է տողից կառուցենք նրա տառերի ցուցակը, կառուցենք այդ ցուցակի բոլոր տեղադրությունների բազմությունը, ապա ամեն մի տեղադրությունից ստանանք նոր տող։ Տո՛ւր ինձ մի մաքուր տախտակ։</p>
<p>Կոնֆուցիոսը վերցրեց Ուաո Գոի մեկնած տախտակն ու դրա վրա գրեց.</p>
<pre class="prettyprint lang-lisp">(defun permutations-of-string (s)
(mapcar #'(lambda (e) (format nil "~(~{~C~}~)" e))
(permutations-of (coerce s 'list))))
</pre>
<p>— Հիմա, Ուաո Գո՛, գնա ու շարունակիր աղոթքդ,— ասաց Կոնֆուցիոսը։</p>
<p>Ուաո Գոն խոնարհվեց ուսոցչին ու խնդրեց.</p>
<p>— Թույլ տուր մի անգամ էլ նայեմ տախտակներին։</p>
armenbadalhttp://www.blogger.com/profile/01200997467308938068noreply@blogger.com0tag:blogger.com,1999:blog-118668552408252052.post-79108140171813392722019-05-23T12:36:00.000+04:002019-05-23T12:36:33.119+04:00Go: Quick Sort-ի ևս մի ներկայացում<p>Կարգավորման ալգորիթմներից գեղեցկագույնի՝ QuickSort-ի մասին գրված է ծրագրավորման ալգորիթմներին վերաբերող համարյա բոլոր գրքերում։ Բայց արժե առանձնացնել հատկապես <i>Robert Sedgewick</i>, <i>Kevin Wayne</i>, «Algorithms, 4th Edition» գրքի <a href="https://algs4.cs.princeton.edu/23quicksort/">իլյուստրացիան</a>, որից օգտվելով էլ (ինչպես նաև Go լեզվի <a href="https://golang.org/pkg/sort/">sort</a> փաթեթի կոդից) կառուցել եմ ստորև բերվող իրականացումը։ Սակայն իմ նպատակը QuickSort-ի վերլուծությունը չէ. ես ուզում եմ դրա օրինակով ներկայացնել, թե ինչպես կարելի է Go լեզվով իրականացնել <i>ընդհանրացված</i> (generic) ալգորիթմ։</p>
<p>Սկսեմ պարզ դեպքից։ Ենթադրենք գրել եմ <code>quick</code> փաթեթը, որի <code>Sort</code> ֆունկցիան կարգավորում է ամբողջ թվերի զանգվածը.</p>
<pre class="prettyprint lang-go">
package quick
// Sort ֆունկցիան կարգավորում է ամբողջ թվերի տրված զանգվածը
func Sort(arr []int) {
quickSort(arr, 0, len(arr)-1)
}
func quickSort(arr []int, low, high int) {
if low < high {
m := partition(arr, low, high)
quickSort(arr, low, m-1)
quickSort(arr, m+1, high)
}
}
func partition(arr []int, low, high int) int {
p := low
i, j := low+1, high
for {
for i != high && arr[i] < arr[p] {
i++
}
for arr[p] < arr[j] {
j--
}
if i >= j {
break
}
arr[i], arr[j] = arr[j], arr[i]
}
arr[p], arr[j] = arr[j], arr[p]
return j
}
</pre>
<p>Իմ նպատակն է նույն այս իրականացումն օգտագործել <code>int</code>-երից բացի այլ տիպերի համար։ Այսինքն՝ <code>Sort</code> ֆունկցիան պետք է սահմանել այնպիսի պարամետրով, որ հնարավոր լինի այն կիրառել կամայական տիպի տարրերի զանգվածի նկատմամբ։ Ուշադիր նայելով <code>[]int</code> տիպի համար իրականացմանը, տեսնում եմ, որ զանգվածի հետ կատարվում են երեք գործողություններ. <b>ա</b>) ստանալ զանգվածի չափը՝ <code>len(arr)</code>, <b>բ</b>) համեմատել զանգվածի տարրերը՝ <code>arr[i] < arr[p]</code>, <b>գ</b>) մեկը մյուսով փոխարինել զանգվածի տարրերը՝ <code>arr[i], arr[j] = arr[j], arr[i]</code>։ Սահմանեմ (<code>quick</code> փաթեթում) <code>Sortable</code> ինտերֆեյսը՝ այս երեք գործողություններտ ներկայացնող մեթոդներով.</p>
<pre class="prettyprint lang-go">
package quick
// Sortable ինտերֆեյսով որոշվում է «կարգավորելի» զանգվածը
type Sortable interface {
Size() int // զանգվածի չափը
Less(i, j int) bool // a[i] < a[j] համեմատումը
Swap(i, j int) // a[i] <-> a[j] փոխատեղումը
}
</pre>
<p>Հիմա արդեն կարող եմ հերթով ձևափոխել <code>Sort</code>, <code>quickSort</code> և `partition` ֆունկցիաները։ Առաջինում պետք է փոխել պարամետրի տիպը և <code>len(arr)</code>-ը փոխարինել <code>arr.Size()</code>-ով։</p>
<pre class="prettyprint lang-go">
// Sort ֆունկցիան կարգավորում է ամբողջ թվերի տրված զանգվածը
func Sort(arr Sortable) {
quickSort(arr, 0, arr.Size()-1)
}
</pre>
<p>Պարզվում է, որ <code>quickSort</code> ֆունկցիայում փոխելու բան չկա։</p>
<p><code>partition</code> ֆունկցիայում տարրերի համեմատությունները պետք է փոխարինել <code>Less</code> մեթոդի կիրառությամբ, իսկ տարրերի փոխատեղման վերագրումները՝ <code>Swap</code> մեթոդի կիրառությամբ։</p>
<pre class="prettyprint lang-go">
func partition(arr Sortable, low, high int) int {
pv := low
i, j := low+1, high
for {
for i != high && arr.Less(i, pv) {
i++
}
for arr.Less(pv, j) {
j--
}
if i >= j {
break
}
arr.Swap(i, j)
}
arr.Swap(pv, j)
return j
}
</pre>
<p>Պատրաստ է։ Հիմա տեսնենք, թե ինչպես է այս նոր իրականացումն օգտագործվելու։ Սկսենք արդեն աշխատող տարբերակից. ենթադրենք ուզում եմ կարգավորել <code>int</code>-երի զանգված։ Պետք է իրականացնեմ <code>Sortable</code> ինտերֆեյսը.</p>
<pre class="prettyprint lang-go">
type integers []int
func (a integers) Size() int {
return len(a)
}
func (a integers) Less(i, j int) bool {
return a[i] < a[j]
}
func (a integers) Swap(i, j) {
a[i], a[j] = a[j], a[i]
}
</pre>
<p>Հետո արդեն կարող եմ <code>Sort</code> ֆունկցիան կիրառել `integers` զանգվածի նկատմամբ։</p>
<pre class="prettyprint lang-go">
// ...
a0 := integers{4, 1, 9, 2, 3, 8, 5, 6}
quick.Sort(a0)
fmt.Println(a0)
// ...
</pre>
<blockquote><b>Հարց։</b> Եթե մի որևէ ֆունկցիա վերադարձնում է <code>[]int</code> զանգված, ապա դրա արդյունքի վրա ո՞նց է կիրառվելու <code>Sort</code> ֆունկցիան։</blockquote>
armenbadalhttp://www.blogger.com/profile/01200997467308938068noreply@blogger.com0tag:blogger.com,1999:blog-118668552408252052.post-56460887197995439342018-07-09T10:05:00.001+04:002018-07-09T12:25:04.743+04:00IoT: Էլ-փոստով ղեկավարվող լուսավորություն<p>Ինչ-որ ժամանակ առաջ պիտի գնայինք գյուղ և Յերեվանի տանը մի քանի օր մարդ չէր լինելու։ Մտքովս անցավ մտածել մի սարքավորում, որը հնարավորություն կտա գյուղից, ինչ-որ եղանակով միացնել տան լույսերը (կամ մի այլ սարք)։ Ինտերնետում բավականին քչփորելով գտա մի քանի եղանակներ, որոնք օգտագործում էին օժանդակ ցանցային ծառայություններ։ Վերջապես, համադրելով մի քանի գաղափարներ, կառուցեցի ստորև նկարագրված սարքա-ծրագրային համակարգը։
</p>
<p>Աշխատանքի մեխանիզմն այսպիսինն է. Յերեվանի տանը դրած համակարգիչը, օգտագործում եմ <a href="https://www.raspberrypi.org/">Raspberry Pi 1 Model B Rev. 2</a>, ամեն երկու (կամ 1, կամ 5 և այլն) րոպեն մեկ ստոգում է հատուկ այդ նպատակի համար ստեղծված էլ-փոստը։ Հենց որ ստացվում է նոր նամակ՝ ստուգում նամակի վերնագիրը, որում գրված է կոնկրետ առաջադրանքը, օրինակ, «LIGHT ON» կամ «LIGHT OFF»։ Որպեսզի որևէ օտար մարդ չկարողանա համակարգչին առաջադրանք տալ (նամակ ուղարկել), ստուգվում է նաև ուղարկողի հասցեն (միամիտ ու անհուսալի պաշտպանություն է. կարելի է ու պետք է կատարելագործել)։ Եթե ամեն ինչ սպասվածի պես է, ապա համակարգչի միացված ռելեյի միջոցով, օգտագործում եմ <a href="https://arduinomodules.info/ky-019-5v-relay-module/">KY-019</a> մոդուլը, միացվում կամ անջատվում է էլեկտրական սարքը։</p>
<p>Էլ֊փոստը կարդալու համար օգտագործում եմ <a href="http://pyropus.ca/software/getmail/">getmail</a>֊ը, իսկ <a href="https://en.wikipedia.org/wiki/Cron">cron</a>֊ը օգտագործում եմ getmail֊ը երկու րոպեն մեկ աշխատեցնելու համար։ getmail֊ը տեղադրել եմ սովորական եղանակով․
<pre class="prettyprint lang-bash">
$ sudo apt install -y getmail4
</pre>
</p>
<p>Տեղադրելուց հետո այն պետք է կարգավորել այնպես, որ կարդա իմ էլ֊փոստը։ Դրա համար <code>$HOME</code> պանակում ստեղծում եմ <code>.getmail</code> պանակը, իսկ դրա մեջ էլ <code>getmailrc</code> ֆայլը։ Վերջինս էլ հենց getmail֊ի կարգավորումների ֆայլն է։ Ինձ մոտ այն հետևյալ տեքսի է․
<pre class="prettyprint">
[retriever]
type = SimpleIMAPSSLRetriever
server = imap.yandex.com
port = 993
username = __իմ էլ֊փոստի անունը__
password = __իմ էլ֊փոստի գաղտնաբառը__
[options]
read_all = false
delivered_to = false
received = false
[destination]
type = MDA_external
path = ~/Projects/a5/readanddo.sh
</pre>
</p>
<p><code>retriever</code> բլոկում getmail֊ը կարգավորվում է կոնկրետ փոստարկղի համար։ Կարծում եմ, որ այդ բլոկի պարամետրերը բացատրելու կարիք չկա․ դրանց անուններն ամեն ինչ ասում են իրենց մասին։</p>
<p><code>options</code> բլոկի <code>read_all = false</code> պարամետրը նշանակում է, որ պետք չէ ամեն անգամ սերվերից կարդալ բոլոր նամակները, այլ կարդալ միայն նորերը։ Եթե <code>delivered_to</code> և <code>received</code> պարամետրերը դրված են <code>true</code>, ապա ստացված նամակի վերնագրին (header) ավելացվում են համապատասխանաբար «Delivered To:» և «Received:» դաշտերը (սրանց իմաստը չեմ հասկանում, պարզապես <code>false</code> եմ դրել ավելորդություններից խուսափելու համար)։</p>
<p>Ամենակարևորն իմ աշխատանքում <code>destination</code> բլոկն է։ Սրա պարամետրերով են որոշվում, թե ինչ պետք է անել փոստարկղի սերվերից ներբեռնված նամակների հետ։ Իմ դեպքում <code>type = MDA_external</code> պարամետրն ասում է, որ նամակները պետք է մշակվեն արտաքին (ոչ ներդրված) MDA ― mail delivery application ծրագրով։ Ամեն անգամ, հենց որ getmail֊ը սերվերից նոր նամակ է կարդում, այն ուղղարկում է <code>path</code> պարամետրով տրված ծրագր (կամ սկրիպտի) ստանդարտ ներմուծման հոսքին։</p>
<p>Ես գրել եմ <code>readanddo.sh</code> սկրիպտը, որը ստուգում է նամակի «From:» և «Subject:» դաշտերը։ Եթե դրանցում գրված են սպավող արժեքները՝ «From:» դաշտում հրամաններ ուղարկող էլ֊փոստի հասցեն, իսկ «Subject:» դաշտում՝ կոնկրետ հրամանը, ապա Raspberry Pi֊ի GPIO֊ին ուղղարկվում է համապատասխան ազդանշանը։</p>
<pre class="prettyprint lang-bash">
#!/bin/bash
operation=''
commander=''
while read line
do
if [[ ${line} =~ ^Subject: ]]
then
if [[ ${line} =~ DO:LIGHT:ON ]]
then
operation="LIGHT:ON"
elif [[ ${line} =~ DO:LIGHT:OFF ]]
then
operation="LIGHT:OFF"
fi
fi
if [[ ${line} =~ ^From: ]]
then
if [[ ${line} =~ __իմ էլ֊փոստի հասցեն__ ]]
then
commander=${line}
fi
fi
done
if [ -z ${commander} ]
then
exit 0
fi
if [ -z ${operation} ]
then
exit 0
fi
gpio -g mode 4 out
if [ ${operation} = "LIGHT:ON" ]
then
gpio -g write 4 1
exit 0
elif [ ${operation} = "LIGHT:OFF" ]
then
gpio -g write 4 0
exit 0
fi
</pre>
<p>Ռելեյի KY-019 մոդուլն ունի երեք մուտքային ոտիկներ․ «<code>+</code>», «<code>-</code>» և «<code>S</code>»։ «<code>+</code>»-ը միացնում եմ Raspberry Pi-ի 2֊րդ GPIO֊ին՝ 5v, «<code>-</code>»-ը միացնում եմ 6֊րդ GPIO֊ին՝ GND, իսկ «<code>S</code>»֊ը, որը ղեկավարող ազդանշանն է, միացնում եմ 7-րդ GPIO֊ին (ֆիզիկական համարակալմամբ 7֊րդը BCM համարակալմամբ 4֊րդն է)։</p>
<p>Երբ <code>readanddo.sh</code> սկրիպտը համոզվում է, որ հրամանն ուղարկվել է նախապես որոշված հասցեից, և հրամանի ֆորմատն էլ նախապես որոշվածներից մեկն է, RPi֊ի 4֊րդ GPIO֊ի (BCM համարակալմամբ) ուղղությունը դարձնում է «<code>out</code>».
<pre class="prettyprint lang-bash">
gpio -g mode 4 out
</pre>
և այդ GPIO֊ի արժեքը դնում է <code>0</code> կամ <code>1</code>.
<pre class="prettyprint lang-bash">
gpio -g write 4 1
gpio -g write 4 0</pre>
</p>
<p>Մնում է միայն սահմանել cron֊ի առաջադրանք, որը երկու րոպեն մեկ կգործարկի <code>getmail</code> ծրագիրը։</p>
<center>* * *</center>
<p>Դժվար թե սա կիրառելի լինի իրական կյանքում։ Կարծում եմ, որ կան տանը մարդու ներկայության իմիտացիայի ավելի լավ միջոցներ։</p>armenbadalhttp://www.blogger.com/profile/01200997467308938068noreply@blogger.com0tag:blogger.com,1999:blog-118668552408252052.post-27984388666224314252018-07-06T10:59:00.000+04:002018-07-06T10:59:34.743+04:00JavaScript: mapcar-ի ևս մի իրականացման մասինՉեմ հիշում, թե ինչի համար, բայց ինձ պետք էր JavaScript ծրագրում օգտագործել Common Lisp-ի <a href="http://www.lispworks.com/documentation/HyperSpec/Body/f_mapc_.htm">mapcar</a> ֆունկցիայի պես մի ֆունկցիա։ Մի քիչ դեսուդեն քչփորելուց հետո գտա սա. <a href="https://www.npmjs.com/package/mapcar">https://www.npmjs.com/package/mapcar</a>։ Իրականացումից շատ բան չհասկացա ու դրա համար էլ որոշեցի գրել ավելի պարզ տարբերակը։<br />
Ահա այն՝ մանրամասն մեկնաբանություններով.<br />
<pre class="prettyprint lang-javascript">//
// Ֆայլի անունը. mapcar.js
//
//
// Ֆունկցիայի անունը որոշեցի թողնել նույնը, ինչ որ
// Common Lisp լեզվում է՝ mapcar։
//
// mapcar ֆունկցիան սպասում է մեկ և ավելի արգումենտներ։
// Դրանցից առաջինը կիրառվող ֆունկցիան է, մյուսները՝ վեկտորներ են։
//
var mapcar = function( func, ...args ) {
// համոզվել, որ առաջին արգումենտը ֆունկցիա է
if( 'function' !== typeof func ) {
throw 'mapcar-ի առաջին արգումենտը ֆունկցիա չէ։'
}
// համոզվել, որ երկրորդ և հաջորդ արգումենտներում վեկտորներ են.
if( !args.every(Array.isArray) ) {
throw 'Ոչ բոլոր արգումենտներն են վեկտոր տիպի։'
}
// համոզվել, որ ֆունկցիայի պարամետրերի քանակն ու mapcar-ին
// տրված արգումենտների քանակները նույնն են
if( func.length != args.length ) {
throw 'Ֆունկցիայի պարամետրերի քանակն ու վեկտորների քանակը տարբեր են։'
}
// mapcar ֆունկցիայի կիրառման արդյունքը վեկտոր է
let result = []
// եթե վեկտորների երկարությունները տարբեր են, ապա mapcar—ի
// արդյունքը ստացվելու է դրանցից ամենակարճի չափով
const lengths = args.map((e) => e.length)
const reslen = Math.min.apply(null, lengths)
// ցիկլը կատարելով վեկտորներից ամենակարճի տարրերի քանակով...
for( let i = 0; i < reslen; ++i ) {
// վերցնել բոլոր վեկտորների i-րդ տարրերը, ...
const atu = args.map((ev) => ev[i])
// ֆունկցիան կիրառել դրանց նկատմամբ, ...
const ri = func.apply(null, atu)
// արդյունքն ավելացնել result վեկտորում
result.push(ri)
}
// վերադարձնել կառուցված արդյունքը
return result
}
// տրամադրել այս ֆունկցիան արտաքին աշխարհին
module.exports.mapcar = mapcar
</pre>
<br />
<ol>
<li><b><a href="http://www.ecma-international.org/ecma-262/8.0/index.html#sec-array.prototype.every">every</a></b> մեթոդը <code>true</code> է վերադարձնում միայն այն դեպքում, երբ զանգվածի <i>բոլոր</i> տարրերը բավարարում են տրված պրեդիկատին։</li>
<li><b><a href="http://www.ecma-international.org/ecma-262/8.0/index.html#sec-array.prototype.map">map</a></b> մեթոդը վերադարձնում է զանգված բոլոր տարրերի նկատմամբ տրված ֆունկցիայի կիրառումների արդյունքում ստացված արժեքների վեկտորը։</li>
<li><b><a href="http://www.ecma-international.org/ecma-262/8.0/index.html#sec-function.prototype.apply">apply</a></b> մեթոդը հնարավորություն է տալիս ֆունկցիան կանչել արգումենտների վեկտորով։ Օրինակ, եթե սահմանված է <code>var f = function(x, y, z) { ... } </code>, ապա <apply>-ը կարելի է օգտագործել այսպես. <code>f.apply(null, [1, 2, 3])</code>։ Հարմար է այն դեպքում, երբ կանչի արգումենտները դինամիկ են ձևավորվում։</apply></li>
</ol>
<br />
<br />
Հիշեցի. <code>mapcar</code>-ն ինձ պետք էր <code>zip</code>-ի նման մի ֆունկցիա իրականացնելու համար։<br />
<pre class="prettyprint lang-javascript">var zip = function(x, y) {
return mapcar((a, b) => [a, b], x, y)
}
</pre>
armenbadalhttp://www.blogger.com/profile/01200997467308938068noreply@blogger.com0tag:blogger.com,1999:blog-118668552408252052.post-51767304800351227792018-06-03T15:24:00.001+04:002018-06-03T15:25:18.444+04:00JavaScript: Լամբդա լեզվի իրականացում (I)<blockquote>
<i>Փորձեր JavaScript-ի և Node.js-ի հետ</i>
</blockquote>
<p>
JavaScript-ը հերթական ծրագրավորման լեզուն է, որի ուսումնասիրությամբ որոշեցի
զբաղվել վերջին մի քանի շաբաթների հանգստյան օրերին։ Քանի որ ինձ մոտ դեռևս
կապակցված պատկերացում չկա WEB տեխնոլոգիաների ու դրանց մեջ նաև JavaScript
լեզվի դերի մասին, ես ընտրեցի <a href="https://nodejs.org/">Node.js®</a>-ը։ Այս ընտրությունը
ինձ թույլ է տալիս JavaScript ծրագրերը փորձարկել, աշխատեցնել որպես ինքնուրույն
ծրագրեր։
</p>
<p>
Եվ ինչպես միշտ՝ նոր լեզվի ուսումնասիրությունը սկսում եմ մի որևէ փոքր, ոչ բարդ
շարահյուսությամբ լեզվի <i>իրականացումով</i>։ Այս անգամ որպես իրականացվող լեզու
ընտրել եմ պարզագույն <i>Լամբդա</i> լեզուն։ Ահա դրա քերականությունը.
</p>
<pre class="prettyprint lang-text">
expression
= REAL
| IDENT
| '(' expression ')'
| BUILTIN expression+
| 'if' expression 'then' expression 'else' expression
| 'lambda' IDENT+ ':' expression
| 'apply' expression 'to' expression
.
</pre>
<p>
Այստեղ <i>իրական</i> թվերն են, <i>փոփոխականները</i>, <i>խմբավորման</i> փակագծերը, լեզվի
<i>ներդրված</i> գործողությունները, <i>պայմանական</i> արտահայտությունը, ինչպես նաև
<i>աբստրակցիայի</i> (անանուն ֆունկցիայի գրառման) ու <i>ապլիկացիայի</i> (ֆունկցիայի
կիրառման) գործողությունները։ Ֆունկցիոնալ ծրագրավորման տեսությունից հայտնի
է, որ այսքանը բավական է Լամբդա լեզուն ոչ միայն որպես ընդլայնված հաշվարկիչ
օգտագործելու, այլ նաև լիարժեք (թվային) ալգորիթմներ կազմելու համար։
</p>
<h2>Շարահյուսական վերլուծություն</h2>
<p>
Լամբդա լեզվով գրված տեքստի վերլուծության <code>parser.js</code> մոդուլը «արտաքին
աշխարհին» տրամադրում է (exports) միակ <code>parse</code> ֆունկցիան։ Վերջինս արգումենտում
ստանում է վերլուծվող տեքստը և վերադարձնում է <i>աբստրակտ քերականական ծառը</i>։
</p>
<p>
Նախ՝ տեքստը տրոհվում է <i>լեքսեմների</i> (lexeme) ցուցակի՝ միաժամանակ ամեն մի
լեքսեմին կապելով համապատասխան <i>պիտակը</i> (token)։ Այնուհետև շարահյուսական
վերլուծիչը, օգտագործելով լեքսեմների ցուցակը, կառուցում է աբստրակտ
քերականական ծառը։
</p>
<p>
Տեքստը լեքսեմների ցուցակի տրոհող <code>scanOne</code> և <code>scanAll</code> ֆունկցիաները գրել եմ
ֆունկցիոնալ մոտեցմամբ։ <code>scanOne</code> ֆունկցիան արգումենտում ստանում է տեքստ, և
վերադարձնում է եռյակ՝ տեքստի սկզբից «պոկված» լեքսեմը, դրա պիտակը և տեքստի
չտրոհված մասը։ Օրինակ, <code>scanOne('if + a b then a else b')</code> կանչի արժեքն է
<code>{ token: 'IF', value: 'if', rest: ' + a b then a else b'}</code> օբյեկտը։ Տեքստից
ինձ հետաքրքրող մասը պոկում եմ կանոնավոր արտահայտություների օգնությամբ։
</p>
<p>
Կանոնավոր արտահայտությունները JavaScript-ում կարելի է կառուցել կամ <code>RegExp</code>
կոնստրուկտորով, կամ օգտագործել դրանց լիտերալային գրառումները։ Օրինակ, ես
իդենտիտիֆիկատորները ճանաչող կանոնավոր արտահայտությունը գրել եմ
<code>/^[a-zA-z][0-9a-zA-z]*/</code> տեսքով։ (Տես ECMAScript ստանդարտի
<a href="https://www.ecma-international.org/ecma-262/8.0/index.html#sec-regexp-regular-expression-objects">RegExp (Regular Expression) Objects</a> բաժինը, ինչպես նաև MDN Web Docs-ի <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp">RegExp</a> բաժինը։)
</p>
<p>
RegExp օբյեկտի <code>exec</code> մեթոդը փորձում է «ճանաչել» տրված տողը։ Եթե այդ
փորձը հաջողվում է, ապա մեթոդը վերադարձնում է արդյունքների զանգվածը,
հակառակ դեպքում՝ <code>null</code>։ Քանի որ լեքսեմները միշտ փնտրում եմ տրված տողի
սկզբում, ապա <code>exec</code>-ի վերադարձրած զանգվածի առաջին տարրը հենց ինձ
հետաքրքրող լեքսեմն է։ Որպես վերադարձվող օբյեկտի <code>value</code> սլոթի արժեք
վերցնում եմ այդ առաջին տարրը, իսկ տրված տեքստի սկզբից կտրում ու դեն
եմ գցում լեքսեմի երկարությամբ հատված։ «Կտրելը» իրականացրել եմ <code>String</code>
օբյեկտի <code>substring</code> մեթոդով։
</p>
<p>
Ահա <code>scanOne</code> ֆունկցիան՝ համապատասխան մեկնաբանություններով.
</p>
<pre class="prettyprint lang-javascript">
// Լեզվի ծառայողական բառերի ցուցակը
const keywords = ['if', 'then', 'else', 'lambda', 'apply', 'to', 'and', 'or']
// Տեքստից կարդալ մեկ (թոքեն, լեքսեմ) զույգ
var scanOne = function(text) {
// եթե տրված տեքստը դատարկ է, ապա վերադարձնել
// տեքստի վերջը ցույց տվող օբյեկտ
if( text == '' ) {
return { token: 'EOS', value:'EOS', rest: '' }
}
// երբ տողը սկսվում է բացատանիշերով, ապա դեն նետել
// դրանք և նօորից կանչել scanOne ֆունկցիան
let mc = /^[ \n\t\r]+/.exec(text)
if( mc != null ) {
return scanOne(text.substring(mc[0].length))
}
// եթե տողը տառով սկսվող տառերի ու թվանշանների հաջորդականություն
// է, ապա հանդիպել է կամ ծառայողական բառ, կամ էլ իդենտիֆիկատոր։
// եթե լեքսեմը ծառայողական բառերի keywords ցուցակից է, ապա
// վերադարձվող օբյեկտի token սլոթիի արժեք որոշվում է այդ բառով,
// հակառակ դեպքում token-ը ստանում է IDENT արժեքը
mc = /^[a-zA-z][0-9a-zA-z]*/.exec(text)
if( mc != null ) {
return {
token: keywords.includes(mc[0]) ? mc[0].toUpperCase() : 'IDENT',
value: mc[0],
rest: text.substring(mc[0].length)
}
}
// իրական թվեր
mc = /^[0-9]+(\.[0-9]+)?/.exec(text)
if( mc != null ) {
return {
token: 'REAL',
value: mc[0],
rest: text.substring(mc[0].length)
}
}
// ծառայողական սիմվոլներ (մետասիմվոլներ) են խմբավորման
// փակագծերն ու անանուն ֆունկցիայի պարամետրերը մարմնից
// անջատող երկու կետը
mc = /^(\(|\)|:)/.exec(text)
if( mc != null ) {
return {
token: mc[0],
value: mc[0],
rest: text.substring(mc[0].length)
}
}
// քանի որ լեզվի քերականությունը ներդրված գործողությունները
// սահմանում է մեկ արտահայտությամբ, ես որոշեցի, որ թվաբանական
// ու համեմատման գործողությունների նշաններին համապատասխանեցնել
// մի ընդհանուր OPER պիտակը
mc = /^(\+|\-|\*|\/|=|<>|>|>=|<|<=)/.exec(text)
if( mc != null ) {
return {
token: 'OPER',
value: mc[0],
rest: text.substring(mc[0].length)
}
}
// եթե տրված տեքստը չի համապատասխանում վերը բերված և ոչ մի
// կանոնի, վերադարձնում եմ UNKNOWN պիտակով օբյեկտ
return { token: 'UNKNOWN', value: text[0], rest: text }
}
</pre>
<p>
Իսկ <code>scanAll</code> ֆունկցիան կանչում է <code>scanOne</code> ֆունկցիան այնքան ժամանակ, քանի
դեռ հերթական կանչի արդյունքում չի ստացվել <code>token == 'EOS'</code> օբյեկտ։
</p>
<pre class="prettyprint lang-javascript">
// Կարդալ բոլոր (թոքեն, լեքսեմ) զույգերն ու վերադարձնել ցուցակ
var scanAll = function(text) {
let res = []
let ec = scanOne(text)
while( ec.token != 'EOS' ) {
res.push({token: ec.token, value: ec.value})
ec = scanOne(ec.rest)
}
res.push({token: 'EOS', value: 'EOS'})
return res
}
</pre>
<p>
Այս երկու ֆունկցիաները կազմում են Լամբդա լեզվի բառային վերլուծիչը։ Հիմա՝
շարահյուսական վերլուծության մասին։
</p>
<p>
<code>parse</code> ֆունկցիան <code>scanAll</code> ֆունկցիայով տրոհում է իր արգումենտում ստացված
ծրագիրը և լեքսեմների ցուցակը վերագրում է <code>lexemes</code> գլոբալ զանգվածին։ Ըստ
էության այս <code>lexemes</code>-ը լեքսեմներ ստեկ է, որից վերլուծիչը տարրերը դուրս է
քաշում (pop) ըստ լեզվի քերականական կանոնների։ <code>index</code> գլոբալ հաշվիչը, որը
ծառայում է որպես ստեկկի գագաթի ցուցիչ, ստանում է նախնական <code>0</code> արժեքը՝
Լամբդա լեզվի բուն շարահյուսական վերլուծիչն իրականացված է <code>expression</code>
ֆունկցիայում. <code>parse</code> ֆունկցիան վերադարձնում է հենց վերջինիս արժեքը։
</p>
<pre class="prettyprint lang-javascript">
// (թոքեն, լեքսեմ) զույգերի ցուցակ
var lexemes = []
// ընթացիկ օգտագործվող տարր ինդեքսը
var index = 0;
// ծրագրի տեքստի վերլուծություն
var parse = function(text) {
lexemes = scanAll(text)
index = 0
return expression()
}
</pre>
<p>
Բայց, մինչև <code>expression</code>-ին անցնելը, մի քանի օգնական ֆունկցիաների մասին։
<code>have</code> ֆունկցիան վերադարձնում է <code>true</code>, եթե լեքսեմների ստեկի գագաթի տարրի
պիտակը հավասար է արգումենտում տրված պիտակին կամ պիտակներից որևէ մեկին։
Այս ֆունկցիայի արգուենտը կարող է լինել ինչպես առանձին պիտակ, այնպես էլ
պիտակների վեկտոր։
</p>
<pre class="prettyprint lang-javascript">
// ստուգել ցուցակի ընթացիկ տարրը
var have = function(exp) {
let head = lexemes[index].token
if( exp instanceof Array )
return exp.includes(head)
return head == exp
}
</pre>
<p>
Հաջորդ, <code>next</code> ֆունկցիան մեկով ավելացնում է լեքսեմների ինդեքսը. մոդելավորում
է ստեկի pop գործողությունը՝ դիտարկելի դարձնելով լեքսեմների ցուցակի հաջորդ
տարրը։ Բայց վերադարձնում է ստեկից հանված տարրի <code>value</code> սլոթի արժեքը։
</p>
<pre class="prettyprint lang-javascript">
// անցնել հաջորդին, և վերադարձնել նախորդի արժեքը
var next = function() {
return lexemes[index++].value
}
</pre>
<p>
<code>match</code> ֆունկցիան համադրում է <code>have</code> և <code>next</code> ֆունկցիաները. եթե լեքսեմների
ցուցակի հերթական դիտարկվող տարրի պիտակը հավասաար է <code>match</code>-ի արգումենտին,
ապա դիտարկելի դարձնել հաջորդ տարրը։ Եթե հավասար չէ, ապա ազդարարվում է
շարահյուսական սխալի մասին։
</p>
<pre class="prettyprint lang-javascript">
// ստուգել և անցնել հաջորդին
var match = function(exp) {
if( have(exp) )
return next()
throw `Syntax error: expected ${exp} but got ${lexemes[index].value}`
}
</pre>
<p>
<code>expression</code> ֆունկցիայի կառուցվածքը ուղղակիորեն արտացոլում է այս գրառման
սկզբում բերված քերականությանը։ Ինչպես քերականությունն աջ մասն է բաղկացած
յոթ այլընտրանքներից (տարբերակներից), այնպես էլ <code>expression</code> ֆունկցիան է
կազմված յոթ տրամաբանական հատվածներից։ Ամեն մի հատվածը ձևավորում ու
վերադարձնում է աբստրակտ քերականական ծառի մի որևէ հանգույց։ Այդ
հանգույցներն ունեն <code>kind</code> սլոթը, որով որոշվում է հանգույցի տեսակը։
Ստորև բերված է <code>expression</code> ֆունկցիան՝ մանրամասն մեկնաբանություններով.
</p>
<pre class="prettyprint lang-javascript">
// Լամբդա լեզվի արտահայտությունները կարող են սկսվել միայն հետևյալ
// պիտակներով։ Գրականության մեջ այս բազմությունը կոչվում է FIRST.
// FIRST(expression)
const exprFirst = ['REAL', 'IDENT', '(', 'OPER', 'IF', 'LAMBDA', 'APPLY']
// Արտահայտությունների վերլուծությունը
var expression = function() {
// եթե դիտարկվող լեքսեմը իրական թիվ է,
// ապա վերադարձնել AST-ի հանգույց, որի
// տիպը REAL է
if( have('REAL') ) {
let vl = next()
return { kind: 'REAL', value: parseFloat(vl) }
}
// եթե լեքսեմը իդենտիֆիկատոր է, ապա կառուցել
// փոփոխականի (անուն) հղում ներկայացնող հանգույց
if( have('IDENT') ) {
let nm = next()
return { kind: 'VAR', name: nm }
}
// եթե լեքսեմը բացվող փակագիծ է, ապա վերադարձնել
// փակագծերի ներսում գրված արտահայտության ծառը
if( have('(') ) {
next()
let ex = expression()
match(')')
return ex
}
// Լամբդա լեզվի օգտագործումը մի քիչ ավելի հեշտացնելու
// համար ես դրանում ավելացրել եմ ներդրված գործողություններ։
// դրանք պրեֆիքսային են, ինչպես Լիսպում՝ ցուցակի առաջին
// տարրը գործողության նիշն է, որը կարող է լինել թվաբանական,
// համեմատման կամ տրամաբանական գործողություն
if( have('OPER') ) {
// վերցնել գործողության նիշը
let op = next()
// վերլուծել առաջին արտահայտությունը
let args = [ expression() ]
// քանի դեռ հերթական լեքսեմը պատկանում է FIRST(expression)
// բազմությանը, վերլուծել հաջորդ արտահայտությունը
while( have(exprFirst) )
args.push(expression())
// կառուցել լեզվի ներդրված գործողության հանգույցը
return { kind: 'BUILTIN', operation: op, arguments: args }
}
// պայմանական արտահայտությունը բաղկացած է if, then, else
// ծառայողական բառերով բաժանված երեք արտահայտություններից
if( have('IF') ) {
next()
// վերլուծել պայմանի արտահայտությունը
let co = expression()
match('THEN')
// վերլուծել պայմանի ճիշտ լինելու դեպքում
// հաշվարկվող արտահայտությունը
let de = expression()
match('ELSE')
// պայմանի կեղծ լինելու դեպքում հաշվարկվող
// արտահայտությունը
let al = expression()
// պայմանակա արտահայտության հանգույցը
return { kind: 'IF', condition: co, decision: de, alternative: al }
}
// անանուն ֆունկցիայի սահմանումը սկսվում է lambda
// բառով, որին հաջորդում են ֆունկցիայի պարամետրերը,
// (ֆունկցիան պիտի ունենա գոնե մեկ պարամետր), հետո,
// «:» նիշից հետո ֆուկցիայի մարմինն է
if( have('LAMBDA') ) {
next()
// պարամետրերը
let ps = [ match('IDENT') ]
while( have('IDENT') )
ps.push(next())
match(':')
// մարմինը
let by = expression()
// անանուն ֆունկցիայի հանգույցը
return { kind: 'LAMBDA', parameters: ps, body: by, captures: [] }
}
// apply գործողությունը իրեն հաջորդող արտահայտությունը
// կիրառում է to բառից հետո գրված արտահայտություններին
if( have('APPLY') ) {
next()
// վերլուծել կիրառելի աարտահայտությունը
let fn = expression()
match('TO')
// վերլուծել արգումենտները
let args = [ expression() ]
while( have(exprFirst) )
args.push(expression())
// ֆունկցիայի կիրառման հանգույցը
return { kind: 'APPLY', callee: fn, arguments: args }
}
// բոլոր այլ դեպքերում ազդարարել շարահյուսական սխալի մասին
throw 'Syntax error.'
}
</pre>
<p>
Վերջում նշեմ, որ Լամբդա լեզվի վերլուծիչն իրականացրել եմ <i>ռեկուրսիվ վայրէջքի</i>
եղանակով։ Այդ մասին կարելի է կարդալ ծրագրավորման լեզուների իրականացմանը
նվիրված ցանկացած գրքում։
</p>
<h2>Աբստրակտ քերականական ծառը</h2>
<p>
Լամբդա լեզվով գրված ծրագրի վերլուծության արդյունքում կառուցվում է աբստրակտ
քերականական ծառ, որի հանգույցների տեսակը որոշվում է <code>kind</code> սլոթով։ Օրինակ,
<code>parse('3.14')</code> կիրառման արդյունքում կառուցվում է <code>{ kind: 'REAL', value: 3.14 }</code>
օբյեկտը, որի <code>kind</code> սլոթի <code>REAL</code> արժեքը ցույց է տալիս, որ սա իրական թիվ
ներկայացնող հանգույց է, իսկ <code>value</code> սլոթի արժեքն էլ թվի մեծությունն է։
</p>
<p>
Մեկ այլ օրինակ, <code>parse('+ 3.14 x')</code> ծրագրի վերլության արդյունքում կառուցվում
է հետևյալ օբյեկտը.
</p>
<pre class="prettyprint lang-javascript">
{ kind: 'BUILTIN',
operation: '+',
arguments: [ { kind: 'REAL', value: 3.14 }, { kind: 'VAR', name: 'x' } ] }
</pre>
<p>
Այստեղ հանգույցի տեսակը <code>BUILTIN</code> է (լեզվի ներդրված գործողություն),
գործողության տեսակը՝ <code>operation</code>, գումարումն է, արգումենտների վեկտորն էլ
պարունակում է երկու օբյեկտ՝ առաջինը իրկան թիվ ներկայացնող հանգույց է,
իսկ երկրորդը փոփոխականի հղում ներկայացնող հանգույց։
</p>
<p>
<code>lambda x : * x x</code> լամբդա արտահայտության վերլուծության արդյունքում
կառուցվում է մի օբյեկտ, որում <code>kind == 'LAMBDA'</code>, պարամետրերի ցուցակը
պարունակում է միայն <code>x</code> փոփոխականի անունը, իսկ մարմինը բազմապատկման
ներդրված գործողությունը ներկայացնող հանգույց է (<code>captures</code> սլոթի մասին
կխոսեմ լամբդա արտահայտությունների ինտերպրետացիայի բաժնում)։
</p>
<pre class="prettyprint lang-javascript">
{ kind: 'LAMBDA',
parameters: [ 'x' ],
body:
{ kind: 'BUILTIN',
operation: '*',
arguments: [ [Object], [Object] ] },
captures: {} }
</pre>
<h2>Ինտերպրետացիա</h2>
<p>
Լամբդա ծրագրի վերլուծության արդյունքում կառուցված ծառի <i>ինտերպրետացիայի</i>
<code>evaluate</code> ֆունկցիան նույնպես կառուցված է ռեկուրսիվ սխեմայով։ Դր առաջին
արգումենտը ծրագրի աբստրակտ քերականական ծառն է, իսկ երկրորդը՝ հաշվարկման
միջավայրը։ Վերջինս մի արտապատկերում է (<code>map</code>), որում փոփոխականներին
համապատասխանեցված են ընթացիկ արժեքները։ Քանի որ Լամբդա լեզվում վերագրման
գործողություն չկա, փոփոխականներին արժեքներ կարող են կապվել ֆունկցիայի
պարամետրերի օգնությամբ։
</p>
<pre class="prettyprint lang-javascript">
var evaluate = function(expr, env) { /* ... */ }
</pre>
<p>
Ինչպես երևում է <code>expression</code> ֆունկցիայից, վերլուծության արդյուքնում
կառուցվում են վեց տեսակի հանգույցներ. <code>REAL</code>, <code>VAR</code>, <code>BUILTIN</code>, <code>IF</code>,
<code>LAMBDA</code> և <code>APPLY</code>։ <code>evaluate</code> ֆունկցիայում դիտարկվում են այս վեց դեպքերը։
Հիմա ես հերթով ու հնարավորինս մանրամասն կներկայացնեմ նշված վեց հանգույցների
հաշվարկման եղանակները։
</p>
<p>
<code>REAL</code> տիպի հանգույցի հաշվարկման արդյունքը դրա <code>value</code> սլոթի արժեքն է։
</p>
<pre class="prettyprint lang-javascript">
if( expr.kind == 'REAL' ) {
return expr.value
}
</pre>
<p>
<code>VAR</code> տիպի հանգույցի հաշվարկման արժեքը ստանալու համար միջավայրից
վերադարձնում եմ <code>name</code> սլոթին կապված արժեքը։
</p>
<pre class="prettyprint lang-javascript">
if( expr.kind == 'VAR' ) {
return env[expr.name]
}
</pre>
<p>
<code>BUILTIN</code> տիպի հանգույցի արժեքը ստանալու համար պետք է նախ հաշվարկել
<code>arguments</code> ցուցակի արտահայտությունների արժեքները, ապա գրանց նկատմամբ
կիրառել <code>operation</code> սլոթում գրանցված գործողությունը։
</p>
<pre class="prettyprint lang-javascript">
if( expr.kind == 'BUILTIN' ) {
let evags = expr.arguments.map(e => evaluate(e, env))
return evags.reduce(builtins[expr.operation])
}
</pre>
<p>
<code>IF</code> տիպի հանգույցը, որ պայմանական արտահայտության մոդելն է, հաշվարկելու
համար նախ հաշվարկվում է <code>condition</code> սլոթի արժեքը՝ պայմանը։ Եթե այն տարբեր
է <code>0.0</code> թվային արժեքից՝ <i>ճշմարիտ</i> է, ապա հաշվարկվում և վերադարձվում է
<code>decision</code> սլոթի արժեքը։ Եթե <code>condition</code>-ի արժեքը զրո է, ապա հաշվարկվում ու
վերադարձվում է <code>alternative</code> սլոթին կապված արտահայտության արժեքը։
</p>
<pre class="prettyprint lang-javascript">
if( expr.kind == 'IF' ) {
let co = evaluate(expr.condition, env)
if( co !== 0.0 )
return evaluate(expr.decision, env)
return evaluate(expr.alternative, env)
}
</pre>
<p>
<code>LAMBDA</code> տիպի հանգույցի հաշվարկման արդյունքում պիտի կառուցվի մի օբյեկտ,
որը կոչվում է <i>closure</i> (չգիտեմ, թե հայերեն սրան ինչ են ասում)։ Իմաստն այն
է, որ <code>LAMBDA</code> օբյեկտի <code>captures</code> սլոթում գրանցվում են <code>body</code> սլոթին կապված
արտահայտության <i>ազատ փոփոխականների</i> արժեքները՝ հաշվարկված ընթացիկ
միջավայրում։ Այս կերպ լրացված <code>LAMBDA</code> օբյեկտն արդեն հնարավոր կլինի <code>apply</code>
գործողության կիրառել արգումենտների նկատմամբ։ (Արտահայտության մեջ մտնող
ազատ փոփոխականների բազմությունը հաշվարկող <code>freeVariables</code> ֆունկցիայի մասին
քիչ ավելի ուշ)։
</p>
<pre class="prettyprint lang-javascript">
if( expr.kind == 'LAMBDA' ) {
let clos = Object.assign({}, expr)
let fvs = freeVariables(clos)
for( let v of fvs )
clos.captures[v] = env[v]
return clos
}
</pre>
<p>
Մի օրինակ. թող որ տրված է <code>lambda y : + x y</code> արտահայտությունը և <code>{ 'x': 7 }</code>
հաշվարկման միջավայրը։ Ինչպես արդեն նշեցի վերլուծության մասին պատմելիս,
այս տրված ծրագրի վերլուծությունը կառուցելու է այսպիսի մի օբյեկտ.
</p>
<pre class="prettyprint lang-javascript">
{ kind: 'LAMBDA',
parameters: [ 'y' ],
body:
{ kind: 'BUILTIN',
operation: '+',
arguments: [ [Object], [Object] ] },
captures: {} }
</pre>
<p>
Երբ այս օբյեկտը հաշվարկում եմ <code>{ 'x': 7 }</code> միջավայրում, ստանում եմ նույն
օբյեկտը, բայց արդեն լրացված <code>captures</code> սլոթով։
</p>
<pre class="prettyprint lang-javascript">
{ kind: 'LAMBDA',
parameters: [ 'y' ],
body:
{ kind: 'BUILTIN',
operation: '+',
arguments: [ [Object], [Object] ] },
captures: { x: 7 } }
</pre>
<p>
<code>apply</code> <i>f</i> <code>to</code> <i>e0 e1 ... en</i> արտահայտության հաշվարկման սեմանտիկան
(իմաստը) <i>f</i> ֆունկցիայի՝ <i>e0 e1 ... en</i> արտահայտությունների նկատմամբ
կիրառելն է։ Քանի որ, ըստ Լամբդա լեզվի քերականության, <i>f</i>-ը նույնպես
արտահայտությունը է, ապա նախ՝ պետք է հաշվարկել այն և համոզվել, որ ստացվել
է <i>կիրառելի</i> օբյեկտ՝ closure (թող դա կոչվի <i>f'</i>), որի <code>captures</code>-ը
պարունակում է լամբդայի մարմնի ազատ փոփոխականների արժեքները (bindings)։
Հետո պետք է հաշվարկել <code>APPLY</code> օբյեկտի <code>arguments</code> սլոթին կապված ցուցակի
արտահայտությունները՝ կիրառման արգումենտները, ու դրանք ըստ հերթականության
կապել closure-ի պարամետրերին։ Եվ վերջապես, <i>f'</i> օբյեկտի մարմինը հաշվարկել
մի միջավայրում, որը կառուցված է closure-ի <code>captures</code>-ի և պարամետրերի ու
արգումենտների արժեքների համադրումով։ (Էս պարբերությունը ոնց որ մի քիչ
լավ չստացվեց։)
</p>
<pre class="prettyprint lang-javascript">
if( expr.kind == 'APPLY' ) {
let clos = evaluate(expr.callee, env)
if( clos.kind != 'LAMBDA' )
throw 'Evaluation error.'
let nenv = Object.assign({}, clos.captures)
let evags = expr.arguments.map(e => evaluate(e, env))
let count = Math.min(clos.parameters.length, evags.length)
for( let k = 0; k < count; ++k )
nenv[clos.parameters[k]] = evags[k]
return evaluate(clos.body, nenv)
}
</pre>
<h2>Օգտագործումը</h2>
<p>
Ամեն մի իրեն հարգող ինտերպրետատոր, առավել ևս՝ ֆունկցիոնալ լեզվի
իրականացում, պետք է ունենա այսպես կոչված <i>REPL</i> (read-eval-print loop,
<i>կարդալ</i>-<i>հաշվարկել</i>-<i>արտածել</i>-<i>կրկնել</i>)։ Դրա իրականացումը օգտագործողին
առաջարկում է ներմուծել արտահայտություն, ապա հաշվարկում է այն և
արտածում է արժեքը։ Այս երեք քայլերը կրկնվում են այնքան ժամանակ, քանի
դեռ օգտագործողը, ի որևէ հատուկ հրամանով, չի ընդհատում աշխատանքը։
</p>
<p>
Որպես հրավերք ես ընտրել եմ հունարեն <i>λάμδα</i> բառը, իսկ որպես աշխատանքի
ավարտի ազդանշան՝ <i>///</i> նիշերը։ Օգտագործող-ինտերպրետատոր երկխոսության
կազմակերպման համար օգտագործել եմ Node.js®-ի
<a href="https://nodejs.org/api/readline.html">readline</a> գրադարանը: Ստորև բերված
<code>repl</code> ֆունկցիայի կոդի մասին շատ մանրամասներ չեմ կարող ասել, որովհետև
ինքս էլ նոր եմ ծանոթանում դրան ու փորձում եմ հասկանալ <i>պատահար</i>-ների
(event) հետ աշխատանքի սկզբունքները։
</p>
<pre class="prettyprint lang-javascript">
var repl = function() {
var rr = rl.createInterface({
input: process.stdin,
output: process.stdout,
prompt: 'λάμδα> ',
terminal: false
});
rr.prompt()
rr.on('line', (line) => {
if( line == 'end' ) {
rr.close()
return
}
console.info(ev.evaluate(ps.parse(line), {}))
rr.prompt()
}).on('close', () => {
console.info('Bye')
process.exit(0)
});
}
</pre>
<p>
Բացի երկխոսության ռեժիմից, Լամբդայի ինտերպրետատորը կարելի է աշխատեցնել
նաև հրամանային տողում տալով լամբդա արտահայտությունը պարունակող ֆայլը։
<code>evalFile</code> ֆունկցիայւոմ նախ ստուգում եմ տրված ֆայլի գոյությունը, ապա
<code>readFileSync</code> ֆունկցիայով կարդում եմ դրա ամբողջ պարունակությունը։
Հաշվարկումը կատարվում է ճիշտ այնպես, ինչպես REPL-ում ներմուծված տողի
հաշվարկը։
</p>
<pre class="prettyprint lang-javascript">
var evalFile = function(path) {
if( !fs.existsSync(path) ) return;
let prog = fs.readFileSync(path, {encoding: 'utf-8'})
console.info(ev.evaluate(ps.parse(prog), {}))
}
</pre>
<p>
Աշխատանքային ռեժիմի ընտրությունը կատարվում է հրամանային տողում տրված
արգումենտների քանակը ստուգելով։ Եթե <code>process.argv.length > 2</code>, ապա
ենթադրում եմ, որ հրամանային տողում տրված է ծրագիրը պարունակող ֆայլ,
և կանչվում է <code>evalFile</code> ֆունկցիան։ Հակառակ դեպքում գործարկվում է REPL-ը։
</p>
<pre class="prettyprint lang-javascript">
if( process.argv.length > 2 ) {
evalFile(process.argv[2])
}
else {
repl()
}
</pre>
<h2>Ընդլայնումներ</h2>
<p>
Չնայած որ իրականացված լեզուն բավարար է թվային ալգորիթմների իրականացման
համար, այնուամենայնիվ այն դեռ բավականին «անհարմար» գործիք է։ Օրինակ, ես
կարող եմ սահմանել անանուն ֆունկցիաներ ու դրանք կիրառել արգումենտների
(արտահայտությունների) նկատմամբ, ինչպես նաև (երևի թե) կարող եմ ռեկուրսիայի
օգնությամբ, օգտագործելով որևէ <i>ֆիքսված կետի կոմբինատոր</i>, գրել կրկնություն
պարունակող ալգորիթմներ, և այլն։ Ավելի մանրամասն տես, օրինակ,
<a href="https://plato.stanford.edu/entries/lambda-calculus/">The Lambda Calculus</a>
tanford Encyclopedia of Philosophy էջում։ Բայց, քիչ թե շատ հարմար, ընթեռնելի
ու հասկանալի ծրագրեր գրելու համար ինձ պետք է, առաջին հերթին, ունենալ
<i>սահմանումների</i> մեխանիզմ։ Հենց թեկուզ հանրահայտ <code>let</code>-ը։ Լամբդա լեզվում
այն կարող է ունենալ այսպիսի տեսք.
</p>
<pre class="prettyprint lang-text">
let
pi is 3.1415
in
lambda r : * pi r r
</pre>
<p>
Այստեղ նախ՝ <code>pi</code> սիմվոլին կապվում է <code>3.1415</code> արժեքը, ապա՝ <code>let</code>-ի մարմնում
<code>pi</code>-ն օգտագործվում է արտահայտության մեջ։
</p>
<p>
Մի այլ օրինակ։ Թվի ֆակտորիալը հաշվող պապենական ֆունկցիան կարող է
սահմանվել հետևյալ կերպ.
</p>
<pre class="prettyprint lang-text">
let
fact is lambda n : if (= n 1) then 1 else * n (apply fact to - n 1)
in
apply fact to 10
</pre>
<p>
Այս դեպքում <code>let</code> կառուցվածքի ինտերպրետացիան պետք է կազմակերպել
այնպես, որ ապահովվի ռեկուրսիան՝ սահմանման մեջ պետք է թույլատրվի
սահմանվող սիմվոլի օգտագործումը։
</p>
<p>
Լեզվի մեկ այլ ընդլայնում կարող է լինել նոր տիպերի հետ աշխատանքը. օրինակ,
տեքստային տիպ և ցուցակներ։ Հենց թեկուզ այս երկու տիպերը կարող են էապես
ընդլայնել Լամբդա լեզվով մոդելավորվող ալգորիթմների շրջանակը։
</p>
<h2>Վերջ</h2>
<p>
Վերջ՝ չնայած բոլոր թերություններին ու կիսատ-պռատ իրականացված մասերին։ Մի որոշ
ժամանակ անց, երբ ավելի լավ կուսումնասիրեմ JavaScript լեզուն, ես կփորձեմ շտկել
պակասող մասերն ու ավելի գրագետ իրականացնել վերլուծիչն ու ինտերպրետատորը։
</p>
<h2>Աղբյուրներ</h2>
<p>
Ֆունկցիոնալ լեզվի իրականացման հարցերը քննարկվում են շատ գրքերում ու
հոդվածներում։ Ես անհրաժեշտ եմ համարում դրանցից մի քանիսի թվարկումը.
</p>
<ol>
<li>Christian Queinnec, <i>Lisp in Small Pieces</i>, Cambridge University Press, 2003.</li>
<li>Peter Norvig, <i>Paradigms of Artificial Intelligence Programming: Case Studies in Common Lisp</i>, Morgan Kaufmann, 1991.</li>
<li>Harold Abelson, Jerald Jay Sussman, Julie Sussman, <i>Structure and Interpretation of Computer Programs</i>, 2nd Edition, MIT Press, 1996.</li>
<li>Peter Norvig, <i><a href="http://norvig.com/lispy.html">(How to Write a (Lisp) Interpreter (in Python))</a></i> և <i><a href="http://norvig.com/lispy2.html">(An ((Even Better) Lisp) Interpreter (in Python))</a></i>.</li>
<li>John McCarthy, <i><a href="http://www-formal.stanford.edu/jmc/recursive/recursive.html">Recursive Functions of Symbolic Expressions and Their Computation by Machine, Part I</a></i>.</li>
<li>Paul Graham, <i><a href="http://www.paulgraham.com/rootsoflisp.html">The Roots of Lisp</a></i>.</li>
</ol>
armenbadalhttp://www.blogger.com/profile/01200997467308938068noreply@blogger.com0tag:blogger.com,1999:blog-118668552408252052.post-17989206618771485372018-04-21T17:50:00.000+04:002018-04-21T17:50:21.511+04:00Ալգորիթմական լեզվի մասին<h2 id="նախաբան">Նախաբան</h2>
<p>Անցյալ դարի վերջերին միջնակարգ դպրոցի «Ինֆորմատիկա» առարկան, որի լրիվ անունն էր «Ինֆորմատիկայի և հաշվողական տեխնիկայի հիմունքներ», ամբողջությամբ նվիրված էր ծրագրավորմանը։ Իսկ քանի որ դպրոցների հիմնական մասում հմակարգիչներ չկային, մշակվել էր, ինֆորմատիկայի՝ այսպես կոչված «առանց ԷՀՄ»֊ի դասավանդման եղանակը։ Ալգորիթմական մտածելության ու ծրագրավորման հմտությունների զարգացման համար դասագրքերում օգտագործվում էին կա՛մ բլոկ֊սխեմաները, կա՛մ <em>Ալգորիթմական լեզուն</em>։ Առաջինի մասին երևի գիտեն բոլորը, քանի որ նույնիսկ ժամանակակից դասագրքերում դրանք հադիպում են հիմնականում ծրագրավորման լեզուների ղեկավարող կառուցվածքների նշանակությունը բացատրելու համար (այլ ոչ թե ալգորիթմները ներկայացնելու համար)։</p>
<p><em>Ալգորիթմական լեզուն</em>, թերևս, մոռացվել է այն պատճառով, որ դպրոցներում դրա համար երբեք իրականացում (կոմպիլյատոր, ինկերպրետատոր կամ ծրագրավորման միջավայր) չի եղել։ Միգուցե անհետաքրքիր, անիմաստ կամ պարզապես անհարմար էր <em>հայերեն</em> ծառայողական բառերով լեզու օգտագործելը։ Օրինակ, երկու իրական թվերից մեծագույնը գտնելու ծրագիրը ալգորիթմական լեզվով կարելի է գրել այսպես․</p>
<pre class='prettyprint lang-text'>
ալգ իրկ մեծը(իրկ ա, բ)
արգ ա, բ
սկիզբ
եթե ա > բ
ապա արժեք := ա
այլապես արժեք := բ
ավարտ
վերջ
</pre>
<p>Կամ, օրինակ, ամբողջ թվերի զանգվածում տրված արժեքի գծային որոնման ալգորիթմը կարելի է գրել մոտավորապես այսպես․</p>
<pre class='prettyprint lang-text'>
ալգ ամբ որոնել(ամբ գ, աղյուսակ տ[0:u], ք)
արգ գ, տ, ք
սկիզբ
թող ի սկսած 0 մինչև ք
ցս
եթե գ = տ[ի]
ապա արժեք := ի
ավարտ
ցվ
վերջ
</pre>
<p>Ընդհանուր առմամբ ալգորիթմական լեզուն Ալգոլ (Algol) լեզվից ժառանգված մի գործիք էր՝ հարմարեցված ուսումնական նպատակների, և հատկապես «առանց ԷՀՄ» մոտեցմամբ դասավանդման համար։</p>
<p>Պետք է նշել, որ Մոսկվայի պետական համալսարանում ստեղծվել է ալգորիթմական լեզվի իրականացում՝ ռուսերեն ծառայողական բառերով։ Ոչ միայն իրականացում, այլև բավականին հարուստ ու հետաքրքիր ծրագրավորման միջավայր։ Արդեն XXI դարում, ինչ֊որ փորձեր եղան վերակենդանացնել այդ լեզուն КуМир համակարգի տեսքով, բայց չեմ կարծում, թե դա հառողություն կունենա։ Այսօր արդեն շատ են մեկը մեկից գեղեցիկ ու նորարարական ուսուցողական համակարգերը։ Դժվար թե որևէ մեկն ուզենա ծրագրավորում սովորել Scratch-ից, Python֊ից, Racket֊ից ու այլ ժամանակակից լեզուներից նախընտրելով Ալգորիթմական լեզուն կամ КуМир֊ը։ Ալգորիթմական լեզուն այսօր կարող է ունենալ միայն պատմական նշանակություն։</p>
<h2 id="ընթացիկ-պլաններ">Ընթացիկ պլաններ</h2>
<p>Իմ, այսպես ասած, <em>անհատական զարգացման</em> ծրագում վաղուց նախատեսել էի ուսումնասիրել Ջավա վիրտուալ մեքենայի (JVM) հրամանների համակարգը, ու գրել կոդի գեներատոր՝ որևէ պարզ լեզվի համար։ Սովորաբար որպես լեզու ընտրում եմ BASIC֊ի մի պարզեցված տարատեսակ, որում թողնում եմ մեկ կամ երկու պրիմիտիվ տիպեր, հիմնական ղեկավարող կառուցվածքներն ու ենթածրագրերը։ Այս անգամ հիշեցի Ալգորիթմական լեզվի մասին։ Ու քանի որ, մինչև JVM֊ի համար կոդի գեներատոր գրելը, պիտի գրեի ալգորիթմական լեզվի վերլուծիչ, որոշեցի, ձեռքի հետ, քչփորել նաև ANTLR4 parser generator֊ը։</p>
<p>Այսպիսով՝ ձևակերպվեցին հետևյալ խնդիրները․</p>
<ol style="list-style-type: decimal">
<li>Կազմել ալգորիթմական լեզվի ֆորմալ քերականությունը, համոզվել, որ դրա համար հնարավոր է գրել քիչ֊թե շատ խելքը գլխին վերլուծիչ։ Խնդիրը սկզբից ինչ֊որ անհարմարություններ էր խոստանում, քանի որ Ալգորիթմական լեզուն հին դասագրքերում օգտագործված է որպես «թղթի վրայի» լեզու, և դասագիրք գրողներն ու թարգմանողները, կարծես թե, այնքան էլ չեն մտածել լեզվի ֆորմալ կողմի մասին։</li>
<li>ANTLR4 գործիքի օգտագործմամբ գրել շարահյուսական վերլուծիչը։ Չնայած ANTLR4֊ի օգտագործումը շատ մոտ է Bison/Yacc գործիքների օգտագործմանը, այնուամենայնիվ, պետք է ժամանակ տրամադրել առանձնահատկությունները հասկանալու ու դրանց ընտելանալու համար։</li>
<li>Սահմանել <em>աբստրակտ քերականական ծառի</em> հանգույցների դասերի հիերարխիան։ Բնականաբար, այդ դասերը պետք է հարմար լինեն ինչպես ANTLR4֊ի գեներացրած վերլուծիչում օգտագործելու համար, այնպես էլ դրանցից JVM կոդ գեներացնելու համար։</li>
<li>Եվ հիմնականը՝ Apache BCEL գրադարանի օգտագործմամբ վերլուծության ծառից գեներացնել կոռեկտ <code>*.class</code> ֆայլ՝ Ջավա վիրտուալ մեքենայի բայթ֊կոդ։ Այս խնդիրը պիտի ամենաժամանակատարը լինի։</li>
<li>Ամբողջ նախագիծն իրականացնել այնպես, որ այն օգտակար լինի վերը շարադրված նյութով հետաքրքրվողներին։</li>
</ol>
<h2 id="քերականության-մշակումը">Քերականության մշակումը</h2>
<p>Ալգորիթմական լեզվի քերականությունը կառուցում եմ հիմնականում ըստ հայերեն դասագրքի առաջին ու երկրորդ մասերում բերված օրինակների, երբեմն փորձում եմ հաշվի առնել նաև տեքստում տրված բացատրությունները։ Քերականությունը գրում եմ EBNF գրառմամբ՝ ANTLR4-ում իրականացված տարբերակով։ <code>*</code> ետածանցային (postfix) գործողությունը նշանակում է, որ տարրը կարող է կրկնվել զրո և ավելի անգամներ։ <code>+</code> ետածանցային գործողությունը կրկնությունները սահմանում է մեկ և ավելի անգամների համար։ <code>?</code> ետածանցային գործողությունը ցույց է տալիս տարրի ոչ պարտադիր առկայությունը (զրո կամ մեկ անգամ)։ <code>(</code> և <code>)</code> փակագծերով տարրերը խմբավորվում են, իսկ <code>|</code> գործողությամբ նշվում են այնըտրանքները։ Քերականական կանոնների ոչ տերմինալային սիմվոլները պետք է սկսել փոքրատառով, իսկ տերմինալայինները՝ մեծատառով։ Քերակականական հավասարման ձախ ու աջ մասերն անջատվում են <code>:</code> նիշով, իսկ հավասարումն ավարտվում է <code>;</code> նիշով։ Այս բաժինը նաև ANTLR4 գործիքի հետ աշխատանքի փորձի կուտակում է։</p>
<p>Սկսում եմ ամենախոշոր միավորից. Ալգորիթմական լեզվով գրված ծրագիրը ալգորիթմների հաջորդականություն է։ Ծրագիրը պարունակող ֆայլի սկզբում կարող են լինել դատարկ տողեր։ Այն տեղերում, որտեղ նոր տողի անցման նիշը պարտադիր չէ, ես օգտագործում եմ <code>NL?</code> գրառումը, իսկ որտեղ որ պարտադիր է՝ <code>NL</code> գրառումը։</p>
<pre class='prettyprint lang-antlr'>grammar Alg0;
program
: NL? algorithm*
;</pre>
<p>Ալգորիթմը սկսվում է <code>ալգ</code> ծառայողական բառով, որին հաջորդում են վերադարձվող արժեքի տիպը, ալգորիթմի անունը, պարամետրերի ցուցակը և մարմինը։ Եթե ալգորիթմը արժեք չի վերադարձնելու, վերադարձվող արժեքի տիպը պետք է բաց թողնել։ Պարամետրերի ցուցակը նույնպես կարող է դատարկ լինել՝ <code>()</code>, ավելին՝ այն կարող է ընդհանրապես բացակայել։ Ալգորիթմի վերնագրի և մարմնի արանքում թվարկվում են ալգորիթմի <em>արգումենտներն</em> ու <em>արդյունքները</em>։ Մարմինը սկսվում է <code>սկիզբ</code> ծառայողական բառով և ավարտվում է <code>վերջ</code> բառով։ <code>սկիզբ</code> բառից հետո, նույն տողում, գրվում են ալգորիթմի լոկալ փոփոխականների հայտարարությունները։ Հայտարարություններից հետո գրվում են ալգորիթմի մարմնի հրամանները։</p>
<pre class='prettyprint lang-antlr'>algorithm
: 'ալգ' scalar? IDENT ('(' (parameter (',' parameter)*)? ')')? NL
arguments? results?
'սկիզբ' (declaration (',' declaration)*)? NL statement* 'վերջ' NL
;</pre>
<p>Ալգորիթմական լեզվի ալգորիթմները կարող են վերադարձնել միայն պրիմիտիվ տիպի արժեքներ։ <code>scalar</code> ոչ տերմինալային սիմվոլը թվարկում է <em>տրամաբանական</em>, <em>ամբողջաթիվ</em>, <em>իրական</em> և <em>տեքստային</em> պրիմիտիվ տիպերը որոշող ծառայողական բառերը։</p>
<pre class='prettyprint lang-antlr'>scalar
: 'տրամ' | 'ամբ' | 'իրկ' | 'տեքստ'
;</pre>
<p>Պարամետրի նկարագրությունը սկսվում է պրիմիտիվ տիպով, որին հետևում է պարամետրի անունների ցուցակ։</p>
<pre class='prettyprint lang-antlr'>parameter
: scalar paramName (',' paramName)*
;</pre>
<p>Պարամետրի անունը կարող է լինել կամ իդենտիֆիկատոր՝ պարզ փոփոխականի անուն, կամ աղյուսակի անուն։ Վերջինս սկսվում է <code>աղյուսակ</code> բառով, դրան հետևում է իդենտիֆիկատոր և աղյուսակի չափողականությունների նկարագրությունը։</p>
<pre class='prettyprint lang-antlr'>paramName
: IDENT
| 'աղյուսակ' IDENT '[' range (',' range)? ']'
;
range
: (INTEGER | IDENT) ':' (INTEGER | IDENT)
;</pre>
<p>Ալգորիթմական լեզուն հնարավորություն է տալիս սահմանել միայն միչափանի ու երկչափանի զանգվածներ՝ վեկտորներ և մատրիցներ։ Եվ հնարավորություն է տալիս նշելու տարրերի ինդեքսների միջակայքը։ Օրինակ, հետևյալը ամբողջ թվերի վեկտոր է, որի տարրերն ինդեքսավորվում են <code>1..8</code> թվերով․</p>
<pre class='prettyprint lang-text'>ամբ աղյուսակ վ[1:8]</pre>
<p>Գրքում բերված օրինակներում աղյուսակի ինդեքսների միջակայքը նշելիս հաստատունի հետ միասին օգտագործված է մի որևէ փոփոխական։ Հավանաբար ենթադրվել է, որ «աղյուսակ» օբյեկտից հնարավոր չի ստանալ ինդեքսների միջակայքը։ Օրինակ․</p>
<pre class='prettyprint lang-text'>ալգ ֆ(ամբ N, իրկ աղյուսակ ա[2:N])
...</pre>
<p>(Սա ավելորդ բան է ու իրականացման անհարմարություններ է ստեղծելու։ Այս մասը պետք է վերանայել ու ավելի հարմար գրառում մշակել։ Հաշվի առնելով նաև այն փաստը, որ աղյուսակները լինելու են ստատիկ և դրանք մոդելավորելու եմ Ջավայի օբյեկտներով, դրանց մասին ամբողջ ինֆորմացիան հնարավոր է լինելու ստանալ հենց աղյուսակի հղումից։)</p>
<p>Արգումենտների թվարկումը սկսվում է <code>արգ</code> բառով, որին հետևում են պարամետրերի ցուցակում թվարկված այն պարամետրերի անունները, որոնք ալգորիթմին են փոխանցվելու <em>ըստ արժեքի</em> (by value)։ Նոր տողից, <code>արդ</code> բառով սկսվում է արդյունք֊պարամետրերի թվարկում, դրան այնպիսիներն են, որոնք ալգորիթմին են փոխանցվելու <em>հղումով</em> (by reference)։</p>
<pre class='prettyprint lang-antlr'>arguments
: 'արգ' IDENT (',' IDENT)* NL
;
results
: 'արդ' IDENT (',' IDENT)* NL
;</pre>
<p>(Սա էլ է երևի ավելորդ բան։ Ռուսերեն ավելի ուշ հրատարակված դասագրքերում <code>արգ</code> ու <code>արդ</code> ծառայողական բառերը գրվում են հենց պարամետրերի ցուցակում՝ տիպից առաջ։)</p>
<p>Հիմա՝ ալգորիթմի մարմնի մասին։ Ինչպես արդեն նշեցի, այն սկսվում է <code>սկիզբ</code> բառով և ավարտվում է <code>վերջ</code> բառով։ <code>սկիզբ</code> բառի հետ նույն տողում սահմանվում են ալգորիթմի լոկալ փոփոխականները (կամ, դասագրքի տերմիններով, ժամանակավոր մեծությունները)։ Լոկալ փոփոխականների հայտարարման քերականությունը շատ նման է ալգորիթմի պարամետրերի քերականությանը։ Միակ բացառությունն այն է, որ զանգվածների ինդեքսների միջակայքերը պիտի լինեն հաստատուններ։</p>
<pre class='prettyprint lang-antlr'>declaration
: scalar declName (',' declName)*
;
declName
: IDENT
| 'աղյուսակ' IDENT '[' INTEGER ':' INTEGER ']'
| 'աղյուսակ' IDENT '[' INTEGER ':' INTEGER ',' INTEGER ':' INTEGER ']'
;</pre>
<p>Լոկալ փոփոխականների հայտարարություններին հաջորդում է հրամանների շարքը։ Ալգորիթմական լեզվի հրամանները կամ ղեկավարող կառուցվածքները, որքանով ես կարողացա ընդհանրացնել, հետևյալներն են․ <em>վերագրում</em>, <em>ճյուղավորում</em>, <em>պայմանով ցիկլ</em>, <em>պարամետրով ցիկլ</em>, <em>ընտրություն</em> և <em>ենթածրագիր կանչ</em>։</p>
<pre class='prettyprint lang-antlr'>statement
: assign | branch | condLoop | countLoop | select | algCall
;</pre>
<p>Վերագրման հրամանը թույլ է տալիս <code>:=</code> նշանի աջ կողմում գրված արտահայտությոն արժեքը վերագրել փոփոխականին համ զանգվածի տարրին։</p>
<pre class='prettyprint lang-antlr'>assign
: place ':=' expression NL
;
place
: IDENT
| IDENT '[' expression ']'
| IDENT '[' expression ',' expression ']'
;</pre>
<p>Ճյուղավորման հրամանը սկսվում է <code>եթե</code> բառով և ավարտվում է <code>ավարտ</code> բառով։ Եթե <code>եթե</code> բառին հաջորդող պայմանը ճիշտ է, ապա կատարվում է <code>ապա</code> բառին հաջորդող հրամանների շարքը։ Հակառակ դեպքում կատարվում են <code>այլապես</code> բառին հաջորդող հրամանները։ Հրամանի <code>այլապես</code> բլոկը կարող է բացակայել։</p>
<pre class='prettyprint lang-antlr'>branch
: 'եթե' expression NL 'ապա' NL? statement* ('այլապես' NL? statement*)? 'ավարտ' NL
;</pre>
<p>Պայմանով ցիկլը սկսվում է <code>մինչ</code> բառով, որին հետևում է կրկնման պայմանը։ Այնուհետև, նոր տողից <code>ցս</code> (ցիկլի սկիզբ) և <code>ցվ</code> (ցիկլի վերջ) բառերի միջև գրվում են կրկնվող հրամանները։</p>
<pre class='prettyprint lang-antlr'>condLoop
: 'մինչ' expression NL 'ցս' NL? statement* 'ցվ' NL
;</pre>
<p>Հաշվիչով ցիկլը սկսվում է <code>թող</code> բառով, որին հաջորդում է ցիկլի պարամետրը, ապա <code>սկսած</code> բառից հետո գրվում է հաշվիչի սկզբնական արժեքի արտահայտությունը, իսկ <code>մինչև</code> բառից հետո՝ հաշվիչի վերջնական արժեքի արտահայտությունը։ Եթե հաշվիչը պետք է փոխել ոչ թե <code>1</code>, այլ մի որևէ այլ քայլով, ապա <code>քայլ</code> բառից հետո տրվում է այդ հատատունը։ Այս ցիկլի դեպքում նույնպես մարմինը գրվում է <code>ցս</code> և <code>ցվ</code> բառերի միջև։</p>
<pre class='prettyprint lang-antlr'>countLoop
: 'թող' IDENT 'սկսած' expression 'մինչև' expression
('քայլ' expression)? NL 'ցս' NL? statement* 'ցվ' NL
;</pre>
<p>Եվ վերջին հրամանը՝ ալգորիթմի կանչը։ Սա ալգորիթմի անունն է, որին հետևում է արգումենտների ցուցակը։ Արգումենտների ցուցակը կարող է դատարկ լինել կամ բացակայել ընդհանրապես։</p>
<pre class='prettyprint lang-antlr'>algCall
: IDENT ('(' (expression (',' expression)*) ')')? NL
;</pre>
<p>Ղեկավարող կառուցվածքների մասին այսքանը ես կարողացա դուրս բերել ձեռքիս տակ եղած օրինակներից։ Առ այս պահը չսահմանված են մնացել միայն արտահայտությունները։ Դասագրքում արտահայտությունների համար հիմնականում օգտագործված է ազատ, մաթեմատիկական գրառումը, սակայն պարզ է, որ ծրագրավորման լեզվի համար դա այնքան էլ հարմար չէ, ու պետք է օգտագործել ընդունված տեքստային գրառում։ Արդյունքում կառուցել եմ արտահայտությունների ստորև բերված քքերականությունը։ Այստեղ թվաբանական, համեմատման, տրամաբանական գործողություններն են, ինչպես նաև զանգվածի տարրին դիմելն ու ֆունկցիայի կանչը։</p>
<pre class='prettyprint lang-antlr'>expression
: simple
| '(' expression ')'
| IDENT '[' expression (',' expression)? ']'
| IDENT '(' (expression (',' expression)*)? ')'
| ('ոչ' | '-' | '+') expression
| <assoc=right> expression '**' expression
| expression ('*' | '/') expression
| expression ('+' | '-') expression
| expression ('>' | '>=' | '<' | '<=') expression
| expression ('=' | '<>') expression
| expression 'և' expression
| expression 'կամ' expression
;</pre>
<p>ANTLR4֊ը պահանջում է, որ արտահայտությունների քերականության մեջ գործողությունները գրվեն ըստ իրենց նախապատվությունների նվազման։ Այս դեպքում, օրինակ, աստիճան բարձրացնելու <code>**</code> գործողությունն ամենաբարձր նախապատվություն ունեցող բինար գործողությունն է, իսկ ամնեացածր նախապատվություն ունեցողը տրամաբանական <code>կամ</code> գործողությունն է։ Պետք է նկատել նաև, որ <code><assoc=right></code> արտահայտությամ <code>**</code> գործողության համար սահմանվել է աջ բաշխականություն։ Մյուս բինար գործողությունները ձախ֊բաշխական են։</p>
<p>Արտահայտությունների պարզ դեպքերն առանձնացրել եմ <code>simple</code> կանոնի մեջ։ Այստեղ են տեքստային, իրական ամբողջաթիվ ու տրամաբանական լիտերալները, ինչպես նաև պարզ փոփոխականը (<code>IDENT</code>)։</p>
<pre class='prettyprint lang-antlr'>simple
: TEXT
| REAL
| INTEGER
| IDENT
| 'ճիշտ'
| 'կեղծ'
;</pre>
<p>Կարծես թե վերջ։ Հիմաա ANTLR4 գործիքով այս քերականությունից պիտի ստանա Ջավա լեզվով գրված կոդ։</p>
<h2 id="փորձարկում">Փորձարկում</h2>
<p>Բնականաբար, ես չեմ կարծում, թե հենց առաջին փորձից ամեն ինչ աշխատելու է․ կամ ինչ֊որ բան պակաս եմ գրել, կամ ինչ֊որ բան սխալ եմ հասկացել օրինակներից։ Համոզվելու համար պիտի փորձել։</p>
<p>Եվ այսպես, www.antlr.org կայքից ներբեռնում եմ գործիքի 4.7.1 տարբերակը պարունակող <code>antlr-4.7.1-complete.jar</code> ֆայլը ու առայժմ պատճենում եմ այն նույն պանակում, որտեղ քերականության ֆայլն է։ Ի դեպ, քերականությունը պարունակող ֆայլի անունը պետք է համընկնի <code>grammar</code> հրահանգով տրված անվան հետ (իմ դեպքում դա <code>Alg0</code> է), իսկ ընդլայնումը պետք է լինի <code>*.g4</code>։</p>
<p>Քայլ առաջին։ Ջավայի միջոցով աշխատեցնում եմ ANTLR4 գործիքը․</p>
<pre class='prettyprint lang-bash'>
$ java -cp .:antlr-4.7.1-complete.jar org.antlr.v4.Tool Alg0.g4
</pre>
<p>Ու միանգամից ստանում եմ հաղորդագրություններ բացթողումների մասին․</p>
<pre class='prettyprint lang-bash'>
warning(125): Alg0.g4:6:6: implicit definition of token NL in parser
warning(125): Alg0.g4:10:20: implicit definition of token IDENT in parser
warning(125): Alg0.g4:29:7: implicit definition of token INTEGER in parser
warning(125): Alg0.g4:108:6: implicit definition of token TEXT in parser
warning(125): Alg0.g4:109:6: implicit definition of token REAL in parser
</pre>
<p>Իմաստն այն է, որ քերականության կանոններում օգտագործել եմ <code>NL</code>, <code>IDENT</code>, <code>INTEGER</code>, <code>TEXT</code> և <code>REAL</code> տերմինալային սիմվոլները, բայց դրանց տեսքը չեմ սահմանել։ Վերադառնում եմ <code>Alg0.g4</code> ֆայլին ու դրա պոչից ավելացնում եմ հետևյալ մի քանի սահմանումները։</p>
<p>Իդենտիֆիկատորը հայերեն կամ լատիներեն փոքրատառով սկսվող և նույն տառերից ու թվանշաններց բաղկացած հաջորդականություն է։</p>
<pre class='prettyprint lang-antlr'>IDENT
: [ա-ևa-z][ա-ևa-z0-9]*
;</pre>
<p>Իրական թիվը սահմանել եմ որպես <code>.</code> նիշը պարունակող թվանշանների հաջորդականություն։ Սա, իհարկե, լրիվ սահանումը չէ, բայց տվյալ գործի համար լրիվ հերիք է։</p>
<pre class='prettyprint lang-antlr'>REAL
: [0-9]+'.'[0-9]+?
;</pre>
<p>Ամբողջ թիվը պարզապես թվանշանների հաջորդականություն է․</p>
<pre class='prettyprint lang-antlr'>INTEGER
: [0-9]+
;</pre>
<p>Տեքստային լիտերալը <code>"</code> չակերտների մեջ առնված նիշերի հաջորդականություն է։ Այն չի կարող պարունակել <code>"</code> նիշը։</p>
<pre class='prettyprint lang-antlr'>TEXT
: '"'~('"')*'"'
;</pre>
<p>Ալգորիթմական լեզվում <code>;</code> նիշն ու նոր տողի անցման նիշը համարժեք են։</p>
<pre class='prettyprint lang-antlr'>NL
: [\n;]+
;</pre>
<p>ANTLR4֊ի հետևյալ կանոնն էլ ասում է, որ բացատանիշերի հաջորդականությունը պետք է անտեսել։</p>
<pre class='prettyprint lang-antlr'>WS : [ \t\r]+ -> skip
;</pre>
<p>ANTLR4 գործիքի հաջորդ գործարկումն արդեն հաջող է անցնում, ու գեներացվում են <code>*.java</code> ֆայլերը, որոնք կարելի է կոմպիլյացնել ու ստանալ <code>*.class</code> ֆայլեր։ (Այդ գեներացված ֆայլերի մեջ են <code>Alg0Lexer.java</code> բառային վերլուծիչը, <code>Alg0Parser.java</code> շարահյուսական վերլուծիչը և այլն։ Դրանք բավականին կոկիկ ու ընթեռնելի ծրագրեր են, հետաքրքրության համար կարելի է բացել ու ուսումնասիրել։)</p>
<pre class='prettyprint lang-bash'>
$ javac -cp .:antlr-4.7.1-complete.jar Alg0*.java
</pre>
<p>Իսկ ինչպե՞ս ստուգել։ ANTLR4֊ն իր մեջ պարունակում է TestRig կոչված ծրագիրը։ Ես դեռ լավ չեմ հասկանում, թե դա ինչ է, բայց կարող եմ ցույց տալ դրա հետ աշխատելու ձևը։ Բայց նախ պատրաստեմ մի օրինակ (դասագրքից), ու այն գրեմ <code>case02.alg</code> ֆայլում։</p>
<pre class='prettyprint lang-text'>ալգ փոքրտարր(ամբ k, n, իրկ աղյուսակ a[k:n], ամբ l)
արգ k, n, a
արդ l
սկիզբ ամբ i, իրկ փոքր
փոքր := a[k]
l := k
i := k + 1
մինչ i <= n
ցս
եթե փոքր > a[i]
ապա
փոքր := a[i]
l := i
ավարտ
i := i + 1
ցվ
վերջ</pre>
<p>Հետո աշխատեցնում եմ արդեն <code>TestRig</code>֊ը։</p>
<pre class='prettyprint lang-bash'>
$ java -cp .:antlr-4.7.1-complete.jar org.antlr.v4.gui.TestRig Alg0 program -tree < case02.alg
</pre>
<p>Հրամանում տրված <code>-tree</code> պարամետրը վերլուծության ծառն արտածում է Լիսպ֊ի ցուցակների տեսքով․</p>
<code>(program \n (algorithm ալգ փոքրտարր ( (parameter (scalar ամբ) (paramName k) , (paramName n)) , (parameter (scalar իրկ) (paramName աղյուսակ a [ (range k : n) ])) , (parameter (scalar ամբ) (paramName l)) ) \n (arguments արգ k , n , a \n) (results արդ l \n) սկիզբ (declaration (scalar ամբ) (declName i)) , (declaration (scalar իրկ) (declName փոքր)) \n (statement (assign (place փոքր) := (expression a [ (expression (simple k)) ]) \n)) (statement (assign (place l) := (expression (simple k)) \n)) (statement (assign (place i) := (expression (expression (simple k)) + (expression (simple 1))) \n)) (statement (condLoop մինչ (expression (expression (simple i)) <= (expression (simple n))) \n ցս \n (statement (branch եթե (expression (expression (simple փոքր)) > (expression a [ (expression (simple i)) ])) \n ապա \n (statement (assign (place փոքր) := (expression a [ (expression (simple i)) ]) \n)) (statement (assign (place l) := (expression (simple i)) \n)) ավարտ \n)) (statement (assign (place i) := (expression (expression (simple i)) + (expression (simple 1))) \n)) ցվ \n)) վերջ \n\n))</code>
<p>TestRig֊ին <code>-tree</code>֊ի փոխարեն <code>-gui</code> տալով վերլուծության ծառը կտեսնենք բացված գրաֆիկական պատուհանում։</p>
<p>Կարծես թե ամեն ինչ աշխատում է։ Բայց, կրկնեմ նորից, այս սահմանված քերականությունը պետքական է միայն բզբզելու, խաղալու, ինչ֊որ փորձեր անելու համար։ Քիչ թե շատ պետքական լեզու ստեղծելու համար պիտի ավելի լավ ուսումնասիրել ANTLR4֊ի վարքը՝ քերականությունը ավելի գրագետ սահմանելու համար։ Բացի այդ, դասագրքում եղած լեզուն արդեն հնացած է, պիտի վերանայել բոլոր կառուցվածքներն ու մշակել ծրագրեր գրելու ավելի հարմար լեզու։</p>
<p>Բայց այդ մասին, ինչպես ասում է կենդանի դասականը, հաջորդ դասին։</p>
armenbadalhttp://www.blogger.com/profile/01200997467308938068noreply@blogger.com0tag:blogger.com,1999:blog-118668552408252052.post-23300517043934486832017-12-19T11:37:00.000+04:002017-12-19T11:37:42.214+04:00Երեք պատահական խնդիր<h2 style='text-align:center'>Արտահայտության հապավում</h2>
<p><strong>Խնդիրը։</strong> Տրված է ինչ-որ արտահայտություն, օրինակ, «Միացյալ ազգերի կազմակերպություն» և պահանջվում է սրանից ստանալ «ՄԱԿ» հապավումը։</p>
<p>Դպրոցականը կամ ուսանողը, հավանաբար, առաջին լուծումը կտանի այսպես. տողը դարձնել ցուցակ, հետո անցնել տողի վրայով ու հավաքել բոլոր այն տառերը, որոնց նախորդում են տառ չհանդիսացող այլ սիմվոլներ։ Հետո՝ հավաքած տառերը դարձնել մեծատառ ու միավորել մեկ տողի մեջ։
</p>
<p>Տողից նիշերի ցուցակ ստացվում է <code>coerce</code> ֆունկցիայով.
<pre class='prettyprint lang-lisp'>
(coerce "abcd" 'list) ; => (#\a #\b #\c #\d)
</pre>
Նույն <code>coerce</code> ֆունկցիայով նիշերի ցուցակից ստացվում է տող.
<pre class='prettyprint lang-lisp'>
(coerce '(#\a #\b #\c #\d) 'string) ; => "abcd"
</pre>
</p>
<p>Նիշերի ցուցակից բառերի առաջին տառերն ընտրող ֆունկցիան կարելի է գրել ռեկուրսիվ եղանակով։
<pre class='prettyprint lang-lisp'>
(defun select-first-letters (sl)
(if (endp sl)
'()
(if (and (not (alpha-char-p (car sl))) (alpha-char-p (cadr sl)))
(cons (cadr sl) (select-first-letters (cddr sl)))
(select-first-letters (cdr sl)))))
</pre>
</p>
<p>Դե իսկ հապավում կառուցող ֆունկցիան արդեն կարելի է կառուցել այսպես․
<pre class='prettyprint lang-lisp'>
(defun acronym-of (s)
(string-upcase (coerce (select-first-letters (coerce s 'list)) 'string)))
</pre>
Բայց այս ֆունկցիան ճիշտ չի աշխատելու, որովհետև տողի առաջին տառը, որը պետք է լինի հապավման առաջին տառը, չի բավարարում <code>select-first-letters</code> ֆունկցիայի 4֊րդ տողում գրված պայմանին։ Այդ թերությունը շտկելու համար պետք է պարզապես տողը նիշերի ցուցակ դարձնելուց հետո դրա սկզբից կցել մի որևէ նիշ։ Այսինքն <code>acronym-of</code> ֆունկցիան սահմանել հետևյալ կերպ․
<pre class='prettyprint lang-lisp'>
(defun acronym-of (s)
(string-upcase (coerce (select-first-letters (cons #\Space (coerce s 'list)) 'string))))
</pre>
Սրա հետևանքով <code>select-first-letters</code> ֆունկցիայի երկրերդ տողում գրված պայմանը կձևափոխվի․
<pre class='prettyprint lang-lisp'>
(defun select-first-letters (sl)
(if (or (endp sl) (endp (cdr sl)))
'()
(if (and (not (alpha-char-p (car sl))) (alpha-char-p (cadr sl)))
(cons (cadr sl) (select-first-letters (cddr sl)))
(select-first-letters (cdr sl)))))
</pre>
</p>
<center><strong>* * *</strong></center>
<p>
Փորձառու ծրագրավորողն այսպիսի բան, իհարկե, չի գրի։ Նա միանգամից կնկատի, որ արտահայտության հապավումը կառուցելու համար բավական է մեծատառ դարձնել բառերի միայն առաջին տառերը, իսկ մնացածները թողնել փոքրատառ։ Հետո դեն գցել ամեն ինչ՝ բացի մեծատառերից։
<pre class='prettyprint lang-lisp'>
(defun acronym (text)
(remove-if-not #'upper-case-p (string-capitalize text)))
</pre>
Սա Արդեն ֆունկցիոնալ լուծում է։ <code>string-capitalize</code> ֆունկցիան վերադարձնում է տողը՝ որում բառերի միայն առաջին տառերն են մեծատառ։ <code>remove-if-not</code> ֆունկցիան ֆիլտրող ֆունկցիա է. այն իր երկրորդ արգումենտում տրված հաջորդականությունից դեն է գցում իր առաջին արգումենտում տրված պրեդիկատին չբավարարող տարրերը։
</p>
<center><strong>* * *</strong></center>
<p>Տիպիկ C-ական լուծումն էլ այսպիսին կլինի.
<pre class='prettyprint lang-c'>
void acronym(const char *text, char *acr)
{
*acr++ = toupper(*text++);
while( *text != '\0' ) {
if( isalpha(*text) && !isalpha(*(text-1)) )
*acr++ = toupper(*text);
++text;
}
*acr = '\0';
}
</pre>
</p>
<br> </br>
<h2 style='text-align:center'>Բառերի հեմինգյան հեռավորություն</h2>
<p><strong>Խնդիրը։</strong> Երկու նույն երկորությունն ունեցող բառերի <em>հեմինգյան հեռավորություն</em> է կոչվում դրանց նույն դիրքերում տարբերվող տառերի քանակը։ Օրինակ, <code>abc</code> և <code>abc</code> բառերի հեմինգյան հեռավորությունը զրո է, իսկ <code>abc</code> և <code>aec</code> բառերի հեմինգյան հեռավորությունը մեկ է, և այլն։</p>
<p>Իտերատիվ լուծումը կարող է լինել <code>loop</code> մակրոսի օգտագործմամբ։ Երկու զուգահեռ հաշվիչներ անցնում են տողերի վրայով և համեմատում են նույն դիրքում գտնվող տառերը։ Եթե դրանք տարբեր են, ապա հաշվարկման արդյունքին գումարվում է մեկ։
<pre class='prettyprint lang-lisp'>
(defun hamming-distance (so si)
(loop for x across so
for y across si
when (char-not-equal x y)
sum 1))
</pre>
</p>
<p>Ֆունկցիոնալ լուծման առաջին մոտարկումը կարող է լինել այսպես. <code>map</code> ֆունկցիայով տրված բառերից կառուցվում է մի ցուցակ, որի <i>i</i>-րդ դիրքում գրված է <i>0</i>` եթե բառերի <i>i</i>-րդ դիրքերի տառերը հավասար են, և <i>1</i>՝ հակառակ դեպքում։ Այնուհետև <code>apply</code> ֆունկցիայով <code>+</code> գործողությունը կիրառվում է այդ ցուցակի նկատմամբ՝ վերադարձնելով տարբերվող տառերի քանակը։
<pre class='prettyprint lang-lisp'>
(defun hamming-distance (so si)
(apply #'+ (map 'list #'(lambda (x y) (if (char-equal x y) 0 1))
so si)))
</pre>
</p>
<p>Վերջնական ֆունկցիոնալ լուծումն ավելի լավն է. նույն <code>map</code> ֆունկցիայով ստեղծվում է <code>char-not-equal</code> ֆունկցիայի արդյունքների ցուցակ՝ կազմված <code>t</code>-երից և <code>nil</code>-երից։ Իսկ հետո <code>count</code> ֆունցիայով հաշվվում է <code>t</code>-երի քանակը, որն էլ հենց տրված բառերի հեմինգյան հեռավորությունն է։
<pre class='prettyprint lang-lisp'>
(defun hamming-distance (so si)
(count t (map 'list #'char-not-equal so si)))
</pre>
</p>
<center><strong>* * *</strong></center>
<p>C-ական իրականացումը պարզապես հաշվում է բառերի նույն դիրքում տարբերվող տառերի քանակը.
<pre class='prettyprint lang-c'>
unsigned int hamming_distance(const char *so, const char *si)
{
unsigned int dist = 0;
while( *so != '\0' && *si != '\0' )
if( *so++ != *si++ )
++dist;
return dist;
}
</pre>
</p>
<br> </br>
<h2 style='text-align:center'>Ենթացուցակի ստուգում</h2>
<p><strong>Խնդիրը։</strong> Ստուգել, թե արդյոք <i>s<sub>0</sub></i> ցուցակը <i>s<sub>1</sub></i> ցուցակի ենթացուցակն է։ Օրինակ, <code>[3, 4, 5]</code> ցուցակը <code>[1, 2, 3, 4, 5, 6]</code> ցուցակի ենթացուցակ է։</p>
<p>Միանգամից «ֆունկցիոնալ» լուծումը. <i>s<sub>0</sub></i>-ն <i>s<sub>1</sub></i>-ի ենթացուցակ է, եթե կա՛մ <i>s<sub>0</sub></i>-ն համընկնում է <i>s<sub>1</sub></i>-ի սկիզբի հետ՝ նրա պրեֆիքսն է, կա՛մ <i>s<sub>0</sub></i>-ն <i>s<sub>1</sub></i>-ի պոչի ենթացուցակն է։</p>
<p>Common Lisp լեզվով գրառումը.
<pre class='prettyprint lang-lisp'>
(defun is-sublist (so si)
(or (is-prefix so si)
(is-sublist so (cdr si))))
</pre>
</p>
<p><code>is-prefix</code> ֆունկցիայի իրականացումն էլ շատ հետաքրքիր է.
<pre class='prettyprint lang-lisp'>
(defun is-prefix (so si)
(not (member nil (mapcar #'eq so si))))
</pre>
<code>mapcar</code> ֆունկցիայով կառուցվում է երկու ցուցակների համապատասխան տարրերի՝ իրար հավասար լինելու (կամ չլինելու) ցուցակը։ <code>member</code> ֆունկցիայով այդ ցուցակում որոնվում է որևէ <code>nil</code> արժեք, իսկ <code>not</code> ֆունկցիայով էլ պահանջվում է, որ <code>nil</code> չլինի։
</p>
<p>C լեզվով պրեֆիքսի և ենթացուցակի ստուգման ֆունկցիաները կունենան հետևյալ ոչ պակաս հետաքրքիր տեսքը.</p>
<p>Եթե, օրինակ, ցուցակի հանգույցը սահմանված է այսպես.
<pre class='prettyprint lang-c'>
struct node {
char data;
struct node *next;
};
</pre>
<p>ապա <code>s</code> ցուցակի՝ <code>l</code> ցուցակի պրեֆիքս լինելը կաստուգվի այսպես.
<pre class='prettyprint lang-c'>
bool is_prefix(const struct node *s, const struct node *l)
{
while( NULL != s && NULL != l && s->data == l->data ) {
s = s->next;
l = l->next;
}
return NULL == s;
}
</pre>
</p>
<p>իսկ <code>s</code> ցուցակի՝ <code>l</code> ցուցակի ենթացուցակ լինելն էլ այսպես.
<pre class='prettyprint lang-c'>
bool is_sublist(const struct node *s, const struct node *l)
{
if( NULL == s )
return true;
if( NULL == l )
return false;
return is_prefix(s, l) || is_sublist(s, l->next);
}
</pre>
</p>
<p></p>armenbadalhttp://www.blogger.com/profile/01200997467308938068noreply@blogger.com0tag:blogger.com,1999:blog-118668552408252052.post-61905442310743763582017-04-28T11:53:00.001+04:002017-04-28T11:55:15.304+04:00Օրացույց 2017<div class="separator" style="clear: both; text-align: center;">
<a href="https://3.bp.blogspot.com/-E72y3JNVZo8/WQL0m0eL5DI/AAAAAAAAECw/IcPNzjsrV60b17f78x7oM2nn0otg3T_7gCLcB/s1600/calendar-c-2.png" imageanchor="1"><img border="0" height="640" src="https://3.bp.blogspot.com/-E72y3JNVZo8/WQL0m0eL5DI/AAAAAAAAECw/IcPNzjsrV60b17f78x7oM2nn0otg3T_7gCLcB/s640/calendar-c-2.png" width="452" /></a></div>
armenbadalhttp://www.blogger.com/profile/01200997467308938068noreply@blogger.com0tag:blogger.com,1999:blog-118668552408252052.post-66383790367045825412016-12-13T11:22:00.001+04:002016-12-13T11:35:43.082+04:00Միակապ ցուցակի կարգավորումը (Insertion sort)Վերջերս մի հարցազրույցի ժամանակ խնդիր առաջադրվեց C լեզվով իրականացնել միակապ ցուցակի (singly linked list) կարգավորման ալգորիթմը՝ անպայման ռեկուրսիայի օգտագործմամբ։ Ես ընտրեցի տեղադրումով կարգավորման (insertion sort) մեթոդը։ Ստորև ներկայացնում եմ դա։<br/>
<br/>
Նախ՝ ցուցակի հանգույցի (node) սահմանումը, որտեղ բանալին <code>double</code> տիպի է․
<pre class='prettyprint lang-c'>
typedef struct _node node;
struct _node {
double data; /* բանալի */
node* next; /* կապ */
};
</pre>
Հիմա տեղադրումով կարգավորման մասին։ Ալգորիթմի էությունն այն է, որ ամեն մի քայլում հերթական տարրը (իմ դեպքում՝ հանգույցը) <i>տեղադրվում</i> է իր <i>ճիշտ</i> տեղում։ Բնականաբար բուն տեղադրման գործողությունը կարևոր գործողություն է։ Սահմանում եմ <code>insert_into()</code> ֆունկցիան, որը տրված հանգույցը տեղադրում է տրված ցուցակի իր ճիշտ տեղում և վերադարձնում է ձևաձոխված ցուցակը։
<pre class='prettyprint lang-c'>
node* insert_into( node* n, node* l )
{
/* եթե ցուցակը դատարկ է, ապա տրված հանգույցը
վերադարձնել որպես կարգավորված ցուցակ */
if( NULL == l ) {
n->next = NULL;
return n;
}
/* եթե տրված հանգույցի բանալին փոքր է տրված ցուցակի
առաջին հանգույցի բանալու արժեքից, ապա տրված
հանգույցը կցել ցուցակի սկզբից */
if( n->data <= l->data ) {
n->next = l;
return n;
}
/* ռեկուրսիվ կանչով տրված հանգույցը տեղադրել ցոցակի
պոչի մեջ, ապա նախնական ցուցակի առաջին հանգույցը
կապել ձևափոխված պոչին */
l->next = insert_into(n, l->next);
return l;
}
</pre>
Ցուցակը կարգավորող <code>sort_list()</code> ֆունկցիան պարզապես կանչում է <code>insert_into()</code> ֆունկցիան․
<pre class='prettyprint lang-c'>
node* sort_list( node* l )
{
/* ցուցակը դատարկ լինելու դեպքը */
if( NULL == l )
return NULL;
/* ցուցակի առաջին հանգույցը տեղադրել կարգավորված
պոչի մեջ՝ ճիշտ տեղում */
return insert_into(l, sort_list(l->next));
}
</pre>
Այսքանը։armenbadalhttp://www.blogger.com/profile/01200997467308938068noreply@blogger.com0tag:blogger.com,1999:blog-118668552408252052.post-82701285562700105402016-12-09T11:19:00.002+04:002016-12-09T12:01:25.973+04:00Թվի երկուական ներկայացման ևս մի մեթոդի մասինՄի անգամ արդեն ես առիթ եմ ունեցել գրելու ամբողջ թիվը տասական ներկայացումից երկուական ներկայացման ձևափոխելու մասին։ <a href='http://hy-it.org/'>ՀայIT.org</a>-ի <a href='http://hy-it.org/1454/decimal-to-binary/'>Թվի ձևափոխումը տասականից երկուական տեսքի</a> հոդվածում գրել եմ ձևափոխության ռեկուրսիվ եղանակի մասին, որտեղ օգտագործել եմ ծրագրավորման լեզվում տողերի կոնկատենացիայի հնարավորությունը։<br/>
<br/>
Հիմա ուզում եմ խոսել այն մասին, թե ինչպես կառուցել թվի երկուական ներկայացումը, երբ լեզվում տողերի կցման հնարավորություն չկա, և արդյունքը պետք է գրել սիմվոլների բուֆերի մեջ։ Ընդունված մեթոդն այն է, որ տրված տասական թիվը, քանի դեռ այն զրո չի դարձել, հաջորդաբար բաժանվում է `2`֊ի, և բաժանումից ստացված մնացորդները գրառվում են հակառակ կարգով։ Այստեղ խնդիրն այն է, որ կամ պետք է հենց սկզբից մնացորդները բուֆերի մեջ գրառել հակառակ հաջորդականությամբ՝ նախապես իմանալով երկուական տեսքի զբաղեցրած նիշերի քանակը, կամ մնացորդները գրառել դրանց ստացվելու ուղիղ հաջորդականությամբ և վերջում վերադասավորել հակառակ կարգով։ Թվի երկուական տեսքի զբաղեցրած նիշերի քանակը կարելի է ստանալ լոգարիթմի օգնությամբ․ \(length=\lceil\log_2{n}\rceil\)
<pre class='prettyprint lang-c'>
// տարբերակ I
void bin_a( int num, char* res )
{
size_t length = log(num)/log(2);
while( num ) {
res[length--] = "01"[num & 0x01];
num >>= 1;
}
}
// տարբերակ II
void bin_b( int num, char* res )
{
char* p = res;
while( num ) {
*p++ = "01"[num & 0x01];
num >>= 1;
}
while( p > res ) {
char t = *(--p);
*p = *res;
*res = t;
++res;
}
}
</pre>
Թե զբաղեցնելիք նիշերի քանակը, և թե մնացորդները հակառակ գրելուց հետո դրանք շրջելը ես համարում եմ ավելորդ աշխատանք։ Ստորև ցուցադրում եմ մի եղանակ, որում թվի տասական տեսքից երկուական տեսքի կառուցումը կատարվում է առանց վերը նշված «ավելորդ» (կամ ոչ ցանկալի) գործողությունների։<br/>
<br/>Եվ այսպես, <code>int bin( int num, char* res )</code> ֆունկցիան արգումենտում ստանում է ձևափոխվելիք թիվը և արդյունքը գրառելու տեղը (նիշերի բուֆեր), իսկ վերադարձնում է երկուական ներկայացման նիշերի քանակը։ Սա ինտերֆեյսը։ Իսկ իրականացումը ռեկուրսիվ է․ <b>բազա)</b> եթե <code>num</code>֊ը փոքր է 2֊ից, ապա բուֆերի սկզբում գրել <code>'0'</code> կամ <code>'1'</code> համապատասխան նիշը, <b>քայլ)</b> եթե <code>num</code>֊ը մեծ է կամ հավասար երկուսի, ապա <code>bin()</code> ֆունկցիան ռեկուրսիվ կանչել <code>num / 2</code> քանորդով ու ստանալ <code>len</code> թիվը, որը բուֆերում այդ քանորդի զբաղեցրած նիշերի քանակն է, ապա բուֆերի <code>len + 1</code> դիրքում գրել <code>num % 2</code> մնացորդին համապատասխան <code>'0'</code> կամ <code>'1'</code> նիշը։ Ռեկուրսիայի բազային ճյուղում որպես արդյունք ֆունկցիան պետք է վերադարձնի <code>1</code>, քայլի ճյուղում՝ <code>len + 1</code>։<br/>
<br/>
Ահա իրականացումը C լեզվով․
<pre class='prettyprint lang-c'>
int bin( int num, char* res )
{
if( num < 2 ) {
*res = "01"[num];
return 1;
}
int len = bin(num >> 1, res);
*(res + len) = "01"[num & 0x01];
return 1 + len;
}
</pre>
Նկարագրված երեք ֆունկցիաների արդյունքները համեմատելու համար օգտագործում եմ <code>test_bin()</code> ֆունկցիան․
<pre class='prettyprint lang-c'>
bool test_bin( int num )
{
char res_a[32] = { 0 };
bin_a(num, res_a);
char res_b[32] = { 0 };
bin_b(num, res_b);
char res[32] = { 0 };
bin(num, res);
bool pass = 0 == strcmp(res, res_a);
pass = pass && (0 == strcmp(res, res_b));
if( !pass )
printf("| num = %d\tres = %s\tres_a = %s\tres_b = %s\n",
num, res, res_a, res_b);
else
printf("| num = %d\tres= %s\n", num, res);
return pass;
}
</pre>
armenbadalhttp://www.blogger.com/profile/01200997467308938068noreply@blogger.com1tag:blogger.com,1999:blog-118668552408252052.post-30252135358718838182016-11-15T10:21:00.001+04:002016-11-15T16:26:25.633+04:00Yacc֊ի և Lex֊ի մասին<ul>
<li><a href="#ովքեր-են-այդ-yaccն-ու-lexը">Ովքե՞ր են այդ Yacc֊ն ու Lex֊ը</a></li>
<li><a href="#ինչ-է-լեզվի-քերականությունը">Ի՞նչ է լեզվի քերականությունը</a></li>
<li><a href="#լեզվի-սահմանում">Լեզվի սահմանում</a></li>
<li><a href="#gnu-bisonի-ֆայլը">GNU Bison֊ի ֆայլը</a></li>
<li><a href="#քերականության-ստուգումը-bisonի-միջոցով">Քերականության ստուգումը Bison֊ի միջոցով</a></li>
<li><a href="#բառային-վերլուծություն-flexի-միջոցով">Բառային վերլուծություն Flex֊ի միջոցով</a></li>
<li><a href="#գործարկման-առաջին-փորձ">Գործարկման առաջին փորձ</a></li>
<li><a href="#թեսթավորում-առաջին-մաս">Թեսթավորում․ առաջին մաս</a></li>
<li><a href="#արվածի-ամփոփում-և-հետագա-քայլերի-մշակում">Արվածի ամփոփում և հետագա քայլերի մշակում</a></li>
<li><a href="#աբստրակտ-քերականական-ծառ">Աբստրակտ քերականական ծառ</a></li>
<li><a href="#bison-նկարագրության-ընդլայնում">Bison նկարագրության ընդլայնում</a></li>
<li><a href="#գործարկման-երկրորդ-փորձ">Գործարկման երկրորդ փորձ</a></li>
</ul>
<p>Ես պատմում եմ ծրագրավորման լեզվի շարահյուսական վերլուծիչի իրականացման մասին։ Պատմությունս հնարավորին պարզ պահելու համար ցույց կտամ, թե ինչպես, օրինակ, պարզեցված Բեյսիկ (BASIC) լեզվով գրված ծրագիրը թարգմանել JSON լեզվով գրված ծրագրի։ Բեյսիկն ընտրել եմ իր հայտնիության ու քերականության պարզության համար։ JSON-ն ընտրել եմ ներկայացման պարզության համար, և ծրագրի հիերարխիկ (ծառաձև) կառուցվածքն ուղղակիորեն արտացոլելու համար։</p>
<p>Այս գրառման մեջ օգտագործված կոդն ու օրինակները իմ <a href="https://github.com/armenbadal/bisonflex/">GitHub</a> էջում են։</p>
<h2 id="ովքեր-են-այդ-yaccն-ու-lexը"><a href="#ովքեր-են-այդ-yaccն-ու-lexը">Ովքե՞ր են այդ Yacc֊ն ու Lex֊ը</a></h2>
<p>Yacc֊ը, որ այժմ ավելի հայտնի է GNU Bison իրականացմամբ, շարահյուսական վերլուծիչների գեներատոր է։ Այն հնարավորություն է տալիս դեկլարատիվ լեզվով սահմանել լեզվի շարահյուսական վերլուծիչը, ամեն մի քերականական կանոնի համար սահմանել համապատասխան գործողություններ, նկարագրել հնարավոր շարահյուսական սխալները և այլն։ Yacc֊ը նկարագրությունից գեներացնում է C (կամ մի ուրիշ՝ Go, SML և այլ) լեզվով գրված արդյունավետ ծրագիր։ Գեներացված ծրագիրն արդեն կոմպիլյացվում և կապակցվում (link) է լեզվի իրականացման հիմնական կոդի հետ։ Lex֊ը, որ այժմ հայտնի է GNU Flex իրականացմամբ, նախատեսված է բառային վերլուծիչի գներացման համար։ Սրա համար նույնպես դեկլարատիվվ լեզվով սահմանվում է լեզվի բառային կառուցվածքը, իսկ Lex֊ը գեներացնում է բառային վերլուծիչի արդյունավետ իրականացում C (կամ այլ) լեզվով։</p>
<p>Այս գործիքների մասին մանրամասն կարելի է կարդալ «<a href="http://shop.oreilly.com/product/9781565920002.do">Doug Brown, John Levine, Tony Mason, <em>lex & yacc</em>, 2nd Edition, O'Reilly Media, 1992</a>» և «<a href="http://shop.oreilly.com/product/9780596155988.do">John Levine, <em>flex & bison</em>, O'Reilly Media, 2009</a>» գրքերում։</p>
<h2 id="ինչ-է-լեզվի-քերականությունը"><a href="#ինչ-է-լեզվի-քերականությունը">Ի՞նչ է լեզվի քերականությունը</a></h2>
<p>Քանի որ և՛ վերլուծվող Բեյսիկ լեզուն սահմանելու համար, և՛ շարահյուսական վերլուծիչը GNU Bison ֆայլում կոդավորելու համար օգտագործելու եմ <em>Բեկուսի֊Նաուրի</em> գրառումը (BNF ― Backus-Naur Form), լավ կլինի, որ շատ կարճ խոսեմ նաև դրա մասին։</p>
<p><em><code>L</code> լեզվի <code>G(L)</code> քերականությունը <code>⟨T,N,R,S⟩</code> քառյակն է, որտեղ <code>T</code>֊ն տերմինալային սիմվոլների բազմությունն է, <code>N</code>֊ը՝ ոչ տերմինալային սիմվոլներինը, <code>R</code> քերականական կանոնների (կամ հավասարումների) բազմությունն է և <code>S</code>֊ն էլ սկզբնական սիմվոլն է։</em></p>
<p><em>Տերմինալային</em> սիմվոլները լեզվի քերականության անտրոհելի, ատոմար տարրերն են։ Օրինակ, ծառայողական բառերը, թվային ու տեքստային լիտերալները, մետասիմվոլները և այլն։</p>
<p><em>Ոչ տերմինալային</em> սիմվոլները լեզվի առանձին տարրերի սահմանումներին տրված անուններն են։</p>
<p><em>Քերականական կանոնը</em> լեզվի քերականության կառուցման հիմնական միավորն է, դրանով է որոշվում լեզվական կառուցվածքի տեսքը։ Քերականական կանոնը <code>→</code> (սլաք) նիշով բաժանված է ձախ ու աջ մասերի։ Ձախ կողմում սահմանվող ոչ տերմինալային սիմվոլն է, իսկ աջում՝ սահմանումը որոշող տերմինալային և ոչ տերմինալային սիմվոլների շարքը։ Օրինակ, Բեյսիկ լեզվի <em>վերագրման</em> հրամանը սահմանող երկու քերականական կանոններն ունեն այսպիսի տեսք․</p>
<pre class='prettyprint lang-text'>Assignment → LetOpt IDENT '=' Expression
LetOpt → LET | ε</pre>
<p>Այստեղ գլխատառերով գրված են տերմինալային սիմվոլները՝ <code>IDENT</code> և <code>LET</code>, իսկ Pascal-Case կանոնով՝ ոչ տերմինալայինները՝ <code>Assignment</code>, <code>Exprsssion</code> և <code>LetOpt</code>։ Առաջին կանոնն «ասում է», որ վերագրման հրամանը (<code>Assignment</code>) բաղկացած է իրար հաջորդող <code>LET</code> սիմվոլից, իդենտիֆիկատորից, <code>=</code> վերգրման նշանից և վերագրվող արտահայտությունից (<code>Expression</code>)։ Երկրորդ կանոնով սահմանվում է <code>LET</code> սիմվոլի ոչ պարտադիր լինելը՝ <code>LetOpt</code>֊ը կամ <code>LET</code> սիմվոլն է, կամ դատարկ է՝ <code>ε</code>։ Քերականական կանոնի աջ կողմում այլընտրանքային տերբերակները (alternatives) իրարից անջատվում են <code>|</code> նիշով։</p>
<p><em>Սկզբնական սիմվոլն</em> այն ոչ տերմինալային սիմվոլն է, որից պետք է սկսել լեզվի վերլուծությունը։</p>
<p>Քերականության ավելի ճշգրիտ ու մանրամասն սահմանման համար տես, օրինակ. «<a href="http://www.informit.com/store/compilers-principles-techniques-and-tools-9780321486813">Alfred Aho, Monica Lam, Ravi Sethi, Jeffrey Ullman, <em>Compilers: Principles, Techniques, and Tools</em>, 2nd edition, Pearson/Addison-Wesley, 2006</a>»։</p>
<h2 id="լեզվի-սահմանում"><a href="#լեզվի-սահմանում">Լեզվի սահմանում</a></h2>
<p>Այստեղ քննարկվող Բեյսիկ լեզուն ունի տվյալների միայն մեկ տիպ՝ <em>իրական թիվ</em>։ Ծառայողական բառերը գրվում են միայն գլխատառերով, իդենտիֆիկատորներում մեծատառերն ու փոքրատառերը տարբերվում են (case sensitive)։</p>
<p>Բեյսիկի քերականությունը ես կսահմանեմ «վերևից֊ներքև»։ Այսինքն, նախ կսահմանեմ լեզվի «խոշոր» բաղադրիչները, ապա հերթականությամբ կսահմանեմ կանոններում հանդիպող բոլոր չսահմանված ոչ տերմինալային սիմվոլները։</p>
<p>Բեյսիկ լեզվով գրված ծրագիրը ֆունկցիաների հաջորդականություն է․</p>
<pre class='prettyprint lang-text'>Program → FunctionList</pre>
<p>Ֆունկցիաների հաջորդականությունը, որ կարող է նաև դատարկ լինել, սահմանված է ռեկուրսիվ եղանակով (ընդհանրապես ցուցակներ, հաջորդականություններ, կրկնություններ պարունակող քերականական տարրերը սահմանված են ռեկուրսիայի միջոցով).</p>
<pre class='prettyprint lang-text'>FunctionList → FunctionList Function
| ε</pre>
<p>Ֆունկցիայի կանոնով որոշվում է և՛ ֆունկցիայի հայտարարությունը, և՛ ֆունկցիայի սահմանումը․</p>
<pre class='prettyprint lang-text'>Function → Declaration
| Definition</pre>
<p>Ֆունկցիայի հայտարարությունը սկսվում է <code>DECLARE</code> ծառայողական բառով, որին հետևում է ֆունկցիայի վերնագիրը․</p>
<pre class='prettyprint lang-text'>Declaration → DECLARE FunctionHeader</pre>
<p>Ֆունկցիայի սահմանումը սկսվում է վերնագրով, որին հաջորդում է հրամանների ցուցակ, և ավարտվում է <code>END</code> և <code>FUNCTION</code> ծառայողական բառերով․</p>
<pre class='prettyprint lang-text'>Definition → FunctionHeader StatementList END FUNCTION</pre>
<p>Ֆունկցիայի վերնագիրը սկսվում է <code>FUNCTION</code> ծառայողական բառով, որին հետևում է ֆունկցիայի անունը որոշող իդենտիֆիկատոր, ապա՝ <code>(</code> և <code>)</code> փակագծերի մեջ վերցրած պարամետրերի ցուցակ։</p>
<pre class='prettyprint lang-text'>FunctionHeader → FUNCTION IDENT '(' ParameterList ')' NewLines</pre>
<p>Պարամետրերի ցուցակը կամ դատարկ է, կամ էլ ստորակետով իրարից բաժանված իդենտիֆիկատորների հաջորդականություն է․</p>
<pre class='prettyprint lang-text'>ParameterList → IdentifierList
| ε
IdentifierList → IdentifierList ',' IDENT
| IDENT</pre>
<p><code>NewLines</code> ոչ տերմինալային սիմվոլով որոշվում է նոր տողի անցման մեկ և ավելի նիշերի շարքը․</p>
<pre class='prettyprint lang-text'>NewLines → NewLines '\n'
| '\n'</pre>
<p>Հրամանների ցուցակը կամ դատարկ է, կամ նոր տողերի նիշերով վերջացող հրամանների շարք է.</p>
<pre class='prettyprint lang-text'>StatementList → StatementList Statement NewLines
| ε</pre>
<p>Բեյսիկի հրամաններն են․ <em>ներմուծում</em>, <em>արտածում</em>, <em>վերագրում</em>, <em>ճյուղավորում</em>, <em>պարամետրով ցիկլ</em>, <em>նախապայմանով ցիկլ</em>, <em>ենթածրագրի կանչ</em>։ Դրանք բոլորը սահմանված են որպես <code>Statement</code> կանոնի այլընտրանքներ։</p>
<p>Ներմուծման հրամանը սկսվում է <code>INPUT</code> ծառայողական բառով, որին հաջորդում է ներմուծվող փոփոխականի անունը.</p>
<pre class='prettyprint lang-text'>Statement → INPUT IDENT</pre>
<p>Արտածման հրամանը սկսվում է <code>PRINT</code> բառով, որին հետևում է արտածվող արտահայտությունը․</p>
<pre class='prettyprint lang-text'>Statement → PRINT Expression</pre>
<p>Վերագրման հրամանն արդեն սահմանել եմ վերևում, այստեղ պարզապես կրկնեմ այն․</p>
<pre class='prettyprint lang-text'>Statement → LetOpt IDENT '=' Expression
LetOpt → LET | ε</pre>
<p>Ճյուղավորման հրամանը բոլորիս լավ հայտնի <code>IF</code> կառուցվածքն է։ Այն բաղկացած է երեք կարևոր բլոկներից, որոնցից միայն առաջինն է պարտադիր։ Առաջին և պարտադիր բլոկը սկսվում է <code>IF</code> ծառայողական բառով, որին հետևում է ճյուղավորման պայմանի արտահայտությունը, հետո՝ <code>THEN</code> ծառայողական բառը, նոր տողերի նիշեր և պայմանի ճշմարիտ լինելու դեպքում կատարվող հրամանների ցուցակը։ Երկրորդ և ոչ պարդադիր բլոկը այլընտրանքային պայմանները որոշող <code>ElseIfPartList</code> ցուցակն է, որի ամեն մի էլեմենտը սկսվում է <code>ELSEIF</code> ծառայողական բառով, ապա՝ պայմանի արտահայտությունը, <code>THEN</code> ծառայողական բառը, նոր տողի նիշեր և պայմանի ճշմարիտ լինելու դեպքում կատարվող հրամանների ցուցակ։ Երրորդ և ոչ պարտադիր բլոկը սկսվում է <code>ELSE</code> ծառայողական բառով, որին հաջորդում են նոր տողի նիշեր և հրամանների շարք։ Ճյուղավորման ամբողջ կառուցվածքն ավարտվում է <code>END</code> և <code>IF</code> ծառայողական բառերով։</p>
<pre class='prettyprint lang-text'>Statement → IF Expression THEN NewLines StatementList ElseIfPartList ElsePart END IF
ElseIfPartList → ElseIfPartList ELSEIF Expression THEN NewLines StatementList
| ε
ElsePart → ELSE StatementList
| ε</pre>
<p>Պարամետրով ցիկլի հրամանը սկսվում է <code>FOR</code> ծառայողական բառով, որին հաջորդում են ցիկլի պարամետրի իդենտիֆիկատորը, <code>=</code> նիշը, պարամետրի սկզբնական արժեքը որոշող արտահայտությունը, <code>TO</code> բառը, պարամետրի արժեքի վերին սահմանի արտահայտությունը, <code>STEP</code> բառը, պարամետրի քայլը որոշող արտահայտությունը, նոր տողի նիշեր, ցիկլի մարմինը որոշող հրամանների ցուցակ։ Պարամետրով ցիկլի հրամանն ավարտվում է <code>END</code> և <code>FOR</code> բառերով։</p>
<pre class='prettyprint lang-text'>Statement → FOR IDENT '=' Expression TO Expression StepOpt NewLines StatementList END FOR
StepOpt → STEP Expression</pre>
<p>Նախապայմանով ցիկլի հրամանը սկսվում է <code>WHILE</code> ծառայողական բառով, որին հետևում են ցիկլի կրկնման պայմանի արտահայտությունը, նոր տողի նիշեր, ցիկլի մարմնի հրամանների շարք։ Հրամանն ավարտվում է <code>END</code> և <code>WHILE</code> բառերով։</p>
<pre class='prettyprint lang-text'>Statement → WHILE Expression NewLines StatementList END WHILE</pre>
<p>Ենթածրագրի կանչը սկսվում է <code>CALL</code> բառով, որին հետևում են ֆունկցիայի անունի իդենտիֆիկատորը և արգումենտների ցուցակը (այն կարող է և դատարկ լինել)․</p>
<pre class='prettyprint lang-text'>Statement → CALL IDENT ArgumentList
ArgumentList → ExpressionList
| ε</pre>
<p>Արտահայտությունների ցուցակը ստորակետով իրարից անջատված արտահայտություննների շարք է․</p>
<pre class='prettyprint lang-text'>ExpressionList → ExpressionList ',' Expression
| Expression</pre>
<p>Հրամաններն այսքանն էին։ Անցնեմ <em>արտահայտությունների</em> սահմանմանը։ Դրանք կարող են պարունակել թվաբանական, տրամաբանական ու համեմատման գործողություններ, ինչպես նաև ֆունկցիայի կանչ։ Բացի բացասման ու ժխտման ունար գործողություններից, մյուս գործողությունները բինար են։</p>
<p>Որպեսզի արտահայտության քերականության մեջ արտացոլվի գործողությունների բնական (ընդունված) բաշխականությունն (associativity) ու նախապատվությունը (precedence), քերականությունը բաժանված է մի քանի մակարդակների։</p>
<pre class='prettyprint lang-text'>Expression → Conjunction OR Conjunction
Conjunction → Equality AND Equality
Equality → Comparison ('='|'<>') Comparison
Comparison → Addition ('>'| '>=' | '<' | '<=') Addition
Addition → Multiplication ('+'|'-') Multiplication
Multiplication → Power ('*' | '/') Power
Power → Factor ['^' Power]
Factor → IDENT '(' ArgumentList ')'
| '(' Expression ')'
| '-' Factor
| NOT Factor
| NUMBER
| IDENT</pre>
<p>Ես այստեղ շեղվեցի BNF֊ի սովորական գրառումից, պարզապես արտահայտություններում հանդիպող գործողությունների բաշխականությունն ու նախապատվությունը ցույց տալու համար։ Սակայն Bison֊ը հնարավորություն է տալիս նույն հասկացությունները սահմանել ավելի հարմր մեխանիզմներով։ Այդ մասին՝ իր տեղում։</p>
<p>Այսքանը լեզվի սահմանման մասին։ Կարծում եմ, որ ավելի մանրամասն նկարագրությունը պարզապես ավելորդ կլիներ։</p>
<h2 id="gnu-bisonի-ֆայլը"><a href="#gnu-bisonի-ֆայլը">GNU Bison֊ի ֆայլը</a></h2>
<p>Yacc֊ի և GNU Bison֊ի մուտքին տրվող ֆայլը սովորաբար ունի <code>.y</code> վերջավորությունը (երբեմն օգտագործում են նաև <code>.yacc</code>, բայց ինձ <code>.y</code> ձևն ավելի է դուր գալիս)։ Այդ ֆայլը բաղկացած է երեք հատվածներից, որոնք իրարից բաժանվում են <code>%%</code> նիշերը պարունակող տողերով։</p>
<pre class='prettyprint lang-text'>սահմանումներ
%%
քերականական կանոններ
%%
օժանդակ ֆունկցիաներ</pre>
<p>Առաջին հատվածում սահմանումներն են, մասնավորապես, այստեղ են սահմանվում տերմինալային սիմվոլները, և սկզբնական սիմվոլը։ Երկրորդ բաժնում թվարկվում են քերականական կանոնները։ Իսկ երրորդն էլ C լեզվով գրված օժանդակ ֆունկցիաների համար է։ Առայժմ ես ցուցադրեմ միայն առաջին ու երկրորդ բաժինները։</p>
<p>Որպես օրինակ ցույց տամ միայն արտահայտությունների վերլուծիչի համար գրված <code>.y</code> ֆայլը։ Այն պարունակում է վերը նկարագրված Բեյսիկ լեզվի արտահայտությունների քերականությունը և դրանում օգտագործված տերմինալային սիմվոլների թվարկումը։</p>
<p>Ստեղծում եմ <code>expr.y</code> ֆայլը և <code>%%</code> նիշերով այն բաժանում եմ երկու մասի։ Առաջին մասում <code>%token</code>, <code>%left</code>, <code>%right</code> և <code>%nonassoc</code> հրահանգներով թվարկում եմ տերմինալային սիմվոլները։ <code>%token</code>֊ը պարզապես հայտարարում է տերմինալային սիմվոլ։ (Bison֊ի ֆայլի ընթեռնելիությունը պարզեցնելու համար ես տերմինալային սիմվոլները գրելու եմ ոչ թե գլխատառերով, այլ <code>x</code> տառով սկսվող camel-case֊ով։)</p>
<pre class='prettyprint lang-text'>%token xNumber
%token xIdent</pre>
<p><code>%left</code>֊ը և <code>%right</code>֊ը ցուց են տալիս, որ իրենց սահմանած տերմինալային սիմվոլը (տվյալ դեպքում, գործողության անունը) ունի համապատասխանաբար ձախ կամ աջ բաշխականություն։ <code>%nonassoc</code> հրահանգի սահմանած տերմինալային սիմվոլները բաշխականություն չունեն։ Գործողությունների նախապատվությունը սահմանվում է ըստ դրանց թվարկման կարգի՝ ցածրից դեպի բարձր։</p>
<p>Հետևյալ սահմանումներում ձախ բաշխականություն ունեն կոնյունկցիան, դիզյունկցիան, գումարման, հանման, բազմապատկման ու բաժանման գործողությունները։ Համեմատման վեց ործողությունները բաշխականություն չունեն։ Աստիճան բարձրացնելու բինար գործողությունը և ժխտման ունար գործողությունն ունեն աջ բաշխականություն։</p>
<pre class='prettyprint lang-text'>%left xOr
%left xAnd
%nonassoc xEq xNe
%nonassoc xGt xGe xLt xLe
%left xAdd xSub
%left xMul xDiv
%right xPow
%right xNot</pre>
<p>Թվարկված գործողություններից ամենացածր նախապատվությունն ունի դիզյունկցիայի <code>OR</code> գործողությունը, իսկ ամենաբարձրը՝ <code>NOT</code> գործողությունը։ Նույն տողի վրա գրված են հավասար նախապատվություն ունեցող գործողությունները։</p>
<p>Հիմա գրում եմ <code>.y</code> ֆայլի երկրորդ բաժինը։ Այստեղ պարզապես պետք է քերականական կանոններով սահմանել, թե ինչպես են արտահայտությունները գործողություններով կապված իրար։ Bison֊ի ֆայլում քերականական կանոնների ձախ ու աջ մասերն իրարից բաժանվում են <code>:</code> նիշով, և ամեն մի կանոն ավարտվում է <code>;</code> նիշով։</p>
<pre class='prettyprint lang-text'>Expression
: Expression xOr Expression
| Expression xAnd Expression
| Expression xEq Expression
| Expression xNe Expression
| Expression xGt Expression
| Expression xGe Expression
| Expression xLt Expression
| Expression xLe Expression
| Expression xAdd Expression
| Expression xSub Expression
| Expression xMul Expression
| Expression xDiv Expression
| Expression xPow Expression
| '(' Expression ')'
| xIdent '(' ArgumentList ')'
| '-' Expression %prec xNot
| xNot Expression
| xNumber
| xIdent
;</pre>
<p>Ոչ մի արտասովոր բան․ պարզապես բոլոր նախատեսված գործողությունների համար նշված է, թե նրանց օպերանդ֊արտահայտությունները ինչ շարահյուսական դիրքում են գտնվում գործողության նշանի նկատմամբ։ Միայն հետևյալ կանոնն է մի քիչ անսովոր․</p>
<pre class='prettyprint lang-text'>Expression : ...
| '-' Expression %prec xNot</pre>
<p>բայց դրա բացատրությունն էլ է պարզ։ Այստեղ <code>%prec</code> հրահանգով նշված է, որ բացասման (ունար մինուս) գործողությունը պետք է ունենա նույն բաշխականությունը, ինչ որ ժխտման <code>NOT</code> գործողությունը։</p>
<p>Մի քիչ առաջ անցնելով նշեմ, որ Bison֊ի ամեն մի քերեկանական կանոնին (իսկ ավելի ճիշտ՝ կանոնի աջ մասի ամեն մի տարրին) կարելի է համապատասխանեցնել գործողություն (action)՝ C կոդի բլոկ։ Օրինակ, Բեյսիկ լեզվի քերականության կանոնը Bison֊ի համար կարելի է գրել․</p>
<pre class='prettyprint lang-text'>Program
: FunctionList
{
puts("PARSED");
}
;</pre>
<p>Սա նշանակում է, որ ֆունկցիաների ցուցակի վերլուծությունից հետո պետք է արտածել <code>PARSED</code> բառը։</p>
<p>Հիմա <code>expr.y</code> ֆայլը տամ Bison֊ի մուտքին․</p>
<pre class='prettyprint lang-text'>$ bison expr.y
expr.y:31.22-33: error: symbol ArgumentList is used, but is not defined as a token and has no rules
| xIdent '(' ArgumentList ')'
^^^^^^^^^^^^</pre>
<p>Սխալի մասին հաղորդագրությունն ասում է, որ <code>ArgumentList</code> սիմվոլն օգտագործված է առանց սահմանման։ Լրացնեմ այդ սահմանումը ևս․ ֆունկցիայի կանչի արգումենտների ցուցակը կամ դատարկ է, կամ ստորակետով իրարից անջատված արտահայտությունների ցուցակ է․</p>
<pre class='prettyprint lang-text'>ArgumentList
: ExpressionList
| %empty
;
ExpressionList
: ExpressionList ',' Expression
| Expression
;</pre>
<p>Bison-ը թույլ է տալիս դատարկ կանոն սահմանելու համար օգտագործել <code>%empty</code> հատուկ սիմվոլը (BNF֊ում այդ դերը կատարում է <code>ε</code> տառը)։</p>
<p>Այս վերջն լրացումից հետո <code>expr.y</code> ֆայլը նորից Bison֊ի մուտքին տալով տեսնում եմ, որ գոնե քերականության տեսակետից ամեն ինչ կարգին է․ Bison-ը այլևս բողոքներ չունի։</p>
<h2 id="քերականության-ստուգումը-bisonի-միջոցով"><a href="#քերականության-ստուգումը-bisonի-միջոցով">Քերականության ստուգումը Bison֊ի միջոցով</a></h2>
<p>Վերադառնամ իմ հիմնական գործին։ Երբ Բեյսիկ լեզվի սահմանման հետ արդեն ամեն ինչ պարզ է, ես պիտի փորձեմ դրա քերականությունը ստուգել Bison֊ի միջոցով (ինչպես դա արեցի արտահայտությունների համար)։</p>
<p>Ստեղծում եմ <code>parser.y</code> ֆայլը (սա արդեն լինելու է Բեյսիկի շարահյուսական վերլուծիչի հիմնական նկարագրությունը) և դրա մեջ Bison֊ի BNF կանոններով գրառում եմ Բեյսիկի ամբողջ քերականությունը։ Ահա այն․</p>
<pre class='prettyprint lang-text'>/* parser.y */
%token xIdent
%token xNumber
%token xDeclare
%token xFunction
%token xLet
%token xInput
%token xPrint
%token xIf
%token xThen
%token xElseIf
%token xElse
%token xEnd
%token xFor
%token xTo
%token xStep
%token xWhile
%token xCall
%left xOr
%left xAnd
%nonassoc xEq xNe
%nonassoc xGt xGe xLt xLe
%left xAdd xSub
%left xMul xDiv
%right xPow
%right xNot
%token xEol
%start Program
%%
Program
: FunctionList
;
FunctionList
: FunctionList Function
| %empty
;
Function
: xDeclare FunctionHeader
| FunctionHeader StatementList xEnd xFunction NewLines
;
FunctionHeader
: xFunction xIdent '(' ParameterList ')' NewLines
;
ParameterList
: IdentifierList
| %empty
;
NewLines
: NewLines xEol
| xEol
;
IdentifierList
: IdentifierList ',' xIdent
| xIdent
;
StatementList
: StatementList Statement NewLines
| %empty
;
Statement
: xInput xIdent
| xPrint Expression
| LetOpt xIdent xEq Expression
| xIf Expression xThen NewLines StatementList ElseIfPartList ElsePart xEnd xIf
| xFor xIdent xEq Expression xTo Expression StepOpt NewLines StatementList xEnd xFor
| xWhile Expression NewLines StatementList xEnd xWhile
| xCall xIdent ArgumentList
;
LetOpt
: xLet
| %empty
;
ElseIfPartList
: ElseIfPartList xElseIf Expression xThen NewLines StatementList
| %empty
;
ElsePart
: xElse NewLines StatementList
| %empty */
;
StepOpt
: xStep Expression
| %empty
;
ArgumentList
: ExpressionList
| %empty
;
ExpressionList
: ExpressionList ',' Expression
| Expression
;
Expression
: Expression xOr Expression
| Expression xAnd Expression
| Expression xEq Expression
| Expression xNe Expression
| Expression xGt Expression
| Expression xGe Expression
| Expression xLt Expression
| Expression xLe Expression
| Expression xAdd Expression
| Expression xSub Expression
| Expression xMul Expression
| Expression xDiv Expression
| Expression xPow Expression
| '(' Expression ')'
| xIdent '(' ArgumentList ')'
| xSub Expression %prec xNot
| xNot Expression
| xNumber
| xIdent
;</pre>
<p>Նորություն է միայն ֆայլի առաջին բաժնի վերջում գրված <code>%start Program</code> հրահանգը։ Սրանով նշվում է, որ սահմանված քերականության սկզբնական սիմվոլը <code>Program</code> ոչ տերմինալային սիմվոլն է։ Եթե քերականության սկզբնական սիմվոլն առանձնացված չէ <code>%start</code> հարահանգով, ապա առաջին սահմանված ոչ տերմինալային սիմվոլն է համարվում սկզբնական սիմվոլ։</p>
<p><code>parser.y</code> ֆայլը Bison֊ի մուտքին տալու հենց առաջին փորձից պարզվում է, որ ամեն ինչ կարգին է, Bison֊ը քերականության տեսակետից բողոքներ չունի։</p>
<p>__* * *__</p>
<p><em>Ի՞նչ ունեմ այս պահին։</em> Bison֊ի լեզվով գրված Բեյսիկի քերականությունը, որ հակասություններ կամ սխալներ չի պարունակում, և պատրաստ է վերածվելու լիարժեք շարահյուսական վերլուծիչի։</p>
<p><em>Ո՞րն է իմ հաջորդ քայլը և ի՞նչ է պակասում դրան անցնելու համար։</em> Հիմա ես պետք է գրեմ բառային վերլուծիչ (կամ՝ լեքսիկական անալիզատոր, lexical analyzer, scanner), որը Bison֊ին է «մատակարարելու» սահմանված տերմինալային սիմվոլները։ Ապա բառային ու շարահյուսական վերլուծիչների համադրմամբ ստանամ մի նախնական ծրագիր, որը «հաստատում» է (accepts), որ Բեյսիկ լեզվով գրված ծրագրերը կարող են վերլուծվել, իսկ եթե չեն կարող՝ տեղեկացնի սխալների մասին։ Այլ կերպ ասած՝ իմ առաջիկա նպատակը Բեյսիկով գրված ծրագրի՝ Բեյսիկի քերականությանը համապատասխանող լինելը ստուգող գործիքն է։</p>
<h2 id="բառային-վերլուծություն-flexի-միջոցով"><a href="#բառային-վերլուծություն-flexի-միջոցով">Բառային վերլուծություն Flex֊ի միջոցով</a></h2>
<p>GNU Flex գործիքը նախատեսված է բառային վերլուծիչի դեկլարատիվ նկարագրությունից արդյունավետ իրականացում գեներացնելու համար։ Թեև Flex֊ն ինքնուրույն գործիք է և կարող է օգտագործվել առանձին խնդիրների համար, այն հիմնականում օգտագործվում է Bison֊ի գեներացրած շարահյուսական վերլուծիչներին բառային վերլուծիչ տրամադրելու համար։ Flex֊ի համար գրված նկարագրության ֆայլերն ունենում են <code>.l</code> վերջավորությունը (երբեմն նաև՝ <code>*.lex</code>)։ Bison֊ի ֆայլի պես Flex֊ի ֆայլն էլ է <code>%%</code> նիշերով բաժանվում երեք հատվածների։ Առաջինում սահմանումներն են, երկրորդում՝ թոքենները (տերմինալային սիմվոլներ) ճանաչելու կանոնները, երրորդում՝ օժանդակ ֆունկցիաները։</p>
<pre class='prettyprint lang-text'>սահմանումներ
%%
կանոններ
%%
ֆունկցիաներ</pre>
<p>Սահմանումների հատվածն օգտագործվում է բարդ կանոնավոր արտահայտություններին կարճ անուններ տալու համար։ Այսպես․</p>
<pre class='prettyprint lang-text'>number [0-9]+(\.[0-9]+)?
ident [a-zA-Z][a-zA-Z0-9]*
comment \'.*$</pre>
<p>Այստեղ <code>numebr</code>֊ը սահմանված է որպես տասնորդական կետ պարունակող իրական թիվ, <code>ident</code>֊ը՝ որպես տառով սկսվող և կառերից ու թվերից բաղկացած հաջորդականություն, իսկ <code>comment</code>֊ը <code>'</code> նիշով սկսվող և մինչև տողի վերջը շարունակվող նիշերի հաջորդականություն։</p>
<p>Երկրորդ բաժնում գրվում են թոքենները ճանաչող կանոնները։ Flex֊ի կանոնը բաղկացած է երկու մասից․ թոքենի նկարագրիչ (pattern) և գործողություն (action)։</p>
<pre class='prettyprint lang-text'>pattern action</pre>
<p>Նկարագրիչը կամ կանոնավոր արտահայտություն է, կամ սահմանումների բաժնում սահմանված անուն։ Երկրորդ դեպքում անունը պետք է վերցնել <code>{</code> և <code>}</code> փակագծերի մեջ։ Այսպես․</p>
<pre class='prettyprint lang-text'>[ \t] { /**/ }
{comment} { /**/ }
{number} { return xNumber; }</pre>
<p>Առաջին ու երկրորդ կանոններն «ասում են», որ պետք է անտեսել բացատանիշերն ու մեկնաբանությունները։ Երրորդ կանոնն «ասում է», որ պետք է վերադարձնել <code>xNumber</code> թոքենը, եթե հանդիպել է <code>number</code> սահմանումով նկարագրիչը (<code>number</code>֊ը և <code>comment</code>֊ը սահմանվել են ֆայլի առաջին բաժնում)։</p>
<p>Ծառայողական բառերից ամեն մեկի համար վերադարձվում են իրենց համապատասխան թոքենները․</p>
<pre class='prettyprint lang-text'>"DECLARE" { return xDeclare; }
"FUNCTION" { return xFunction; }
"LET" { return xLet; }
"INPUT" { return xInput; }
"PRINT" { return xPrint; }
"IF" { return xIf; }
"THEN" { return xThen; }
"ELSEIF" { return xElseIf; }
"ELSE" { return xElse; }
"END" { return xEnd; }
"FOR" { return xFor; }
"TO" { return xTo; }
"STEP" { return xStep; }
"WHILE" { return xWhile; }
"CALL" { return xCall; }
"OR" { return xOr; }
"AND" { return xAnd; }
"NOT" { return xNot; }</pre>
<p>Քանի որ Flex֊ը թոքենների նկարագրիչները ստուգում է վերևից ներքև, իդենտիֆիկատորները ճանաչող կանոնը պետք է գրել ծառայողական բառերից հետո․</p>
<pre class='prettyprint lang-text'>{ident} { return xIdent; }</pre>
<p>Հաջորդ խմբում գործողությունների նշանակումները ճանաչող կանոններն են, որոնք նույպես վերադարձնում են ամեն մի գործողությանը համապատասխանեցված թոքենը։</p>
<pre class='prettyprint lang-text'>"=" { return xEq; }
"<>" { return xNe; }
">" { return xGt; }
">=" { return xGe; }
"<" { return xLt; }
"<=" { return xLe; }
"+" { return xAdd; }
"-" { return xSub; }
"*" { return xMul; }
"/" { return xDiv; }
"^" { return xPow; }
\n { return xEol; }</pre>
<p>Flex֊ի կանոնավոր արտահայտություններում <code>.</code> (կետ) նիշը համապատասխանում է կամայական նիշի, բացի նոր տողի նիշից։ Գրելով հետևյալ կանոնը․</p>
<pre class='prettyprint lang-text'>. { return (int)yytext[0]; }</pre>
<p>որպես թոքեն վերադարձնում եմ տվյալ նիշի համապատասխան ASCII կոդը։ <code>yytext</code>-ը այն բուֆերն է, որի մեջ Flex-ը պահում է ճանաչված տեքստը՝ <em>լեքսեմը</em>։ Քիչ հետո այս բուֆերի օգտագործման այլ օրինակներ էլ ցույց կտամ։</p>
<p>Հիմա արդեն ժամանակն է Flex֊ի միջացով ստուգել նկարագրված բառային վերլուծիչի ճշտությունը։ Դրա համար պետք է <code>scanner.l</code> ֆայլը տալ Flex֊ի մուտքին․</p>
<pre class='prettyprint lang-text'>$ flex scanner.l</pre>
<p>Եթե Flex֊ը սխալների մասին հաղորդագրություններ չի արտածում, ապա ամեն ինչ կարգին է։ Գեներացվել է բառային վերլուծիչի իրականացումը պարունակող <code>lex.yy.c</code> ֆայլը, որը պարունակում է <code>int yylex()</code> ֆունկցիան։ Լռելությամբ գեներացված բառային վերլուծիչը նիշերի հաջորդականությունը կարդում է ներմուծման ստանդարտ հոսքից՝ <code>stdin</code>։ Ավելի ուշ ցույց կտամ, թե ինչպես կարդալ տրված ֆայլը։</p>
<h2 id="գործարկման-առաջին-փորձ"><a href="#գործարկման-առաջին-փորձ">Գործարկման առաջին փորձ</a></h2>
<p>Bison֊ն իրեն տրված քերականության նկարագրությունից գեներացնում է C կոդ։ Եթե <code>.y</code> ֆայլն ունի <code>⟨name⟩</code> անունը, ապա Bison֊ը լռելությամբ գեներացնում է <code>⟨name⟩.tab.c</code> ֆայլը։ Գեներացրած շարահյուսական վերլուծիչի մուտքի կետը <code>int yyparse()</code> ֆունկցիան է։ Flex-ն էլ իրեն տրված նկարագրությունից գեներացնում է C կոդ։ Նրա գեներացրած ֆայլը լռելությամբ ունի <code>lex.yy.c</code> անունը, բայց ես սովորություն ունեմ Flex֊ի <code>-o</code> պարամետրով <code>⟨name⟩</code> անունի համար գեներացնել <code>⟨name⟩.yy.c</code> ֆայլը։ Flex֊ի գեներացրած բառային վերլուծիչի մուտքի կետը <code>yylex()</code> ֆունկցիան է։ <code>yyparse()</code> ֆունկցիան իրեն անգհրաժեշտ հերթական թոքենը ստանալու համար պարբերաբար կանչում է հենց այդ <code>yylex()</code> ֆունկցիան։</p>
<p>Bison֊ի և Flex֊ի գեներացրած ֆայլերն իրար հետ կոմպիլյացնելու ու կատարվող մոդուլ ստանալու համար պետք է պիտի գրեմ նաև <code>main()</code> ֆունկցիա, որում կանչվում է <code>yyparse()</code> ֆունկցիան։ Ահա այն․</p>
<pre class='prettyprint lang-text'>/* main.c */
int main()
{
extern int yyparse();
int ok = yyparse();
return ok;
}</pre>
<p>Երբ GNU GCC կոմպիլյատորորով փորձում եմ թարգմանել (compile) ու կապակցել (link) <code>parser.tab.c</code>, <code>scanner.yy.c</code> և <code>main.c</code> ֆայլերը, ստանում եմ սխալների հաղորդագրությունների մի շարք։ Ահա դրանցից առաջին չորսը․</p>
<pre class='prettyprint lang-text'>scanner.l: In function ‘yylex’:
scanner.l:9:10: error: ‘xNumber’ undeclared (first use in this function)
{number} { return xNumber; }
^
scanner.l:9:10: note: each undeclared identifier is reported only once for each function it appears in
scanner.l:10:10: error: ‘xDeclare’ undeclared (first use in this function)
"DECLARE" { return xDeclare; }
^
scanner.l:11:10: error: ‘xFunction’ undeclared (first use in this function)
"FUNCTION" { return xFunction; }
^
scanner.l:12:10: error: ‘xLet’ undeclared (first use in this function)
"LET" { return xLet; }
^
....</pre>
<p>Տեսնում եմ, որ կոմպիլյատորը չի գտնում <code>yylex()</code> ֆունկցիայում օգտագործված թոքենները (դրանք սահմանված էին <code>parser.y</code> ֆայլում)։ Բանն այն է, որ Flex֊ի և Bison֊ի գեներացրած C ֆայլերը կոմպիլյացիայի տարբեր միավորներ (compilation unit) են, և բնական է, որ կոմպիլյատորը չի կարող դրանցից մեկը թարգմանելիս «տեսնել» մյուսում սահմանված անունները։ Bison֊ի հրամանային տողի <code>-d</code> պարամետրը <code>⟨name⟩.y</code> ֆայլի համար գեներացնում է նաև <code>⟨name⟩.tab.հ</code> ֆայլը, որը պարունակում է <code>⟨name⟩.y</code>֊ում հայտարարված թոքենների (նաև այլ օբյեկտների) հայտարարությունները։ Ուրեմն <code>parser.y</code> ֆայլը պետք է Bison֊ով թարգմանել հետևյլ հրամանով․</p>
<pre class='prettyprint lang-text'>$ bison -d parser.y</pre>
<p>որի արդյունքում կգեներացվեն <code>parser.tab.h</code> և <code>parser.tab.c</code> ֆայլերը։ Հետո պետք է <code>parser.tab.h</code> ֆայլը կցել <code>scanner.l</code> ֆայլին։</p>
<p>Ե՛վ Bison֊ի, և՛ Flex֊ի համար նախատեսված ֆայլերի սկզբում թույլատրվում է ունենալ C լեզվով գրված կոդի բլոկ։ Այդ բլոկը սկսվում է <code>%{</code> նիշերով և վերջանում է <code>%}</code> նիշերով և առանց փոփոխության պատճենվում է գեներացված <code>.c</code> ֆայլի մեջ։ Այսինքն, <code>.l</code> և <code>.y</code> ֆայլերը ունեն հետևյալ տեսքը․</p>
<pre class='prettyprint lang-text'>%{
C կոդ
%}
սահմանումներ
%%
քերականական/լեքսիկական կանոններ
%%
օժանդակ C ֆունկցիաներ</pre>
<p>Հենց այս <code>%{...%}</code> բլոկում էլ պետք է <code>#include</code> հրահանգով <code>scanner.l</code> ֆայլին կցել <code>parser.tab.h</code> ֆայլը։ Այսինքն, <code>scanner.l</code> ֆայլի սկիզբը պետք է ունենա հետևյալ տեսքը․</p>
<pre class='prettyprint lang-text'>%{
#include "parser.tab.h"
%}
%option noyywrap
number [0-9]+(\.[0-9]+)?
ident [a-zA-Z][a-zA-Z0-9]*
comment \'.*$
....</pre>
<p>Քանի որ խոսք է բացվել <code>scanner.l</code> ֆայլը լրացնելու մասին, բացատրեմ նաև <code>%option noyywrap</code> տողը։ Երբ Flex֊ի գեներացրած բառային վերլուծիչը կարդում է վերլուծվող ֆայլը և հասնում է դրա վերջին, այն կանչում է <code>yywrap()</code> ֆունկցիան։ Եթե վերջինս վերադարձնում է <code>0</code>, ապա վերլուծությունը շարունակվում է, իսկ եթե վերադարձնում է <code>1</code>, ապա <code>yylex()</code>-ը վերադարձնում է <code>0</code> արժեք և վերլուծությունը դադարեցվում է։ <code>%option noyywrap</code> հրահանգով Flex֊ին խնդրում ենք ֆայլի վերջին հասնելիս վերադարձնել <code>0</code> և չկանչել <code>yywrap()</code> ֆունկցիան։</p>
<p><code>scanner.l</code> ֆայլում ուղղումներ անելուց հետո նորից փորձեմ կառուցել կատարվող մոդուլը․</p>
<pre class='prettyprint lang-text'>$ bison -d parser.y
$ flex -oscanner.yy.c scanner.l
$ gcc *.c</pre>
<p>Կոմպիլյատորը նորից տեղեկացնում է սխալների մասին։</p>
<pre class='prettyprint lang-text'>parser.tab.c: In function ‘yyparse’:
parser.tab.c:1259:16: warning: implicit declaration of function ‘yylex’ [-Wimplicit-function-declaration]
yychar = yylex ();
^
parser.tab.c:1388:7: warning: implicit declaration of function ‘yyerror’ [-Wimplicit-function-declaration]
yyerror (YY_("syntax error"));
^</pre>
<p>Առանց սահմանվելու (կամ հայտարարվելու) օգտագործվել են <code>yylex()</code> և <code>yyerror()</code> ֆունկցիաները։ <code>yylex()</code>֊ի դեպքում ամեն ինչ պարզ է․ այն գտնվում է ուրիշ կոմպիլյացիայի միավորում։ Պարզապես պետք է <code>parser.y</code> ֆայլի սկզբում հայտարարել <code>yylex()</code>ֆունկցիան։ <code>yyerror()</code> ֆունկցիան օգտագործվում է սխալների մասին ազդարարելու համար․ այն ևս պետք է հայտարարել <code>parser.y</code> ֆայլի սկզբում։</p>
<pre class='prettyprint lang-text'>%{
extern int yylex();
static int yyerror( const char* );
%}
%token xIdent
%token xNumber
....</pre>
<p>Դե, <code>yylex()</code> ֆունկցիան գեներացվում է Flex֊ի օգնությամբ, իսկ <code>yyerror()</code>֊ը պետք է սահամանի ծրագրավորողը։ <code>parser.y</code> ֆայլի օժանդակ ֆունկցիաների բաժինը ճիշտ այն տեղն է, որտեղ պետք է սահմանել շարահյուսական վերլուծիչում օգտագործվող <code>yyerror()</code> ֆունկցիան։</p>
<pre class='prettyprint lang-text'>%%
static int yyerror( const char* message )
{
fprintf(stderr, "ՍԽԱԼ։ %s\n", message);
return 1;
}</pre>
<p>Հա, չմոռանամ նաև <code>parser.y</code> ֆայլի սկզբում կցել <code>stdio.h</code> ֆայլը՝ C լեզվի ստանդարտ գրադարանի ներմուծման֊արտածման գործողությունների համար։</p>
<pre class='prettyprint lang-text'>%{
#include <stdio.h>
extern int yylex();
static int yyerror( const char* );
%}
%token xIdent
%token xNumber
....</pre>
<p>Կատարվող մոդուլը հիմա արդեն պետք է հաջողությամբ կառուցվի։ Դրա համար պետք է նորից կոմպիլյացնել ու կապակցել ֆայլերը․</p>
<pre class='prettyprint lang-text'>$ bison -d parser.y
$ flex -oscanner.yy.c scanner.l
$ gcc -obasic-s *.c</pre>
<p>Ստեղծվում է <code>basic-s</code> մոդուլը։ Բայց ի՞նչ կարող եմ անել սրանով։ Փորձեմ այս ծրագրի մուտքին տալ մի Բեյսիկ ծրագիր ու տեսնել, թե ինչ պատասխան է տալիս։ Թող փորձարկվողը լինի հետևյալ պարզագույն ծրագիրը՝ գրված <code>case01.bas</code> ֆայլում․</p>
<pre class="prettyprint lang-basic">' case01.bas
' պարզ ծրագիր
FUNCTION Main()
PRINT 3.14
END FUNCTION</pre>
<p>Այն <code>basic-s</code> վերլուծիչի մուտքին տամ ներմուծման հասքի վերաուղղորդման միջոցով․</p>
<pre class='prettyprint lang-text'>$ ./basic-s < case01.bas
ՍԽԱԼ։ syntax error</pre>
<p>Ստանում եմ սխալ։ Չնայած ամեն ինչ պիտի որ կարգին լիներ, բայց ծրագրավորման գեղեցկությունը հենց այն է, որ սխալներ կարող են հանդիպել ամենաանսպասելի պահերին։ Ի՞նչն է այս սխալի պատճառը։ Ոչ տողի համար կա, ոչ էլ քիչ թե շատ կոնկրետ բացատրություն․ «syntax error» ու վերջ։</p>
<p>Բարեբախտաբար Bison֊ն ունի ավելի մանրամասն սխալների հաղորդագրություններ արտածելու հնարավորություն։ Այն ակտիվացնելու համար պետք է <code>parser.y</code> ֆայլի հայտարարությունների (առաջին) բաժնում ավելացնել <code>%error-verbose</code> հրահանգը։ Դրանից հետո, երբ նորից ստեղծում եմ <code>basic-s</code> կատարվող մոդուլը ու դրա մուտքին տալիս եմ թեսթային ֆայլը, ստանում եմ սխալի համեմատաբար ավելի հստակ նկարագրություն։</p>
<pre class='prettyprint lang-text'>$ ./basic-s < case01.bas
ՍԽԱԼ։ syntax error, unexpected xEol, expecting $end</pre>
<p>Այստեղ ասված է, որ շարահյուսական վերլուծիչը բառային վերլուծիչից ստացել է <code>xEol</code>, թեև սպասում էր <code>$end</code> հատուկ սիմվոլը։ Չնայած, որ արդեն գուշակում եմ, թե սխալը ինչումն է, վատ չէր լինի սխալի հաղորդագրության հետ նշվեր նաև սխալը պարունակող տողի համարը։</p>
<p>Flex֊ի <code>%option yylineno</code> հրահանգը բառային վերլուծիչի ֆայլում հայտարարում է <code>yylineno</code> գլոբալ հաշվիչը, որը հենց հերթական վերլուծվող տողի համարն է։ Դա պետք է պարզապես ավելացնել <code>scanner.l</code> ֆայլի սահմանումների (առաջին) բաժնում, օրինակ, <code>%option noyywrap</code> հրահանգից հետո։</p>
<pre class='prettyprint lang-text'>%{
#include "parser.tab.h"
%}
%option noyywrap
%option yylineno
number [0-9]+(\.[0-9]+)?
ident [a-zA-Z][a-zA-Z0-9]*
comment \'.*$
....</pre>
<p>Իսկ <code>parser.y</code> ֆայլի <code>yyerror()</code> ֆունկցիայում պետք է հայտարարել <code>yylineno</code> փոփոխականը, և այն օգտագործել սխալի մասին հաղորդագրությունն արտածելիս։ (Ես սովորություն ունեմ <code>yylineno</code> փոփոխականը հայտարարել <code>parser.y</code> ֆայլի <code>%{...%}</code> բլոկում, որպեսզի կարողանամ այն ազատ օգտագործել ոչ միայն սխալների մասին ազդարարելիս, այլ նաև շարահյուսական վերլուծիչի այլ հատվածներում։)</p>
<pre class='prettyprint lang-text'>%%
static int yyerror( const char* message )
{
extern int yylineno;
fprintf(stderr, "ՍԽԱԼ։ [%d] %s\n", yylineno, message);
return 1;
}</pre>
<p>Երբ նորից կառուցում եմ <code>basic-s</code> մոդուլը ու դրա մուտքին տալիս եմ թեսթային ծրագիրը, տեսնում եմ, որ սխալի մասին հաղորդագրության մեջ հիմա արդեն նշված է շարահյուսական սխալը պարունակող տողը։</p>
<pre class='prettyprint lang-text'>$ ./basic-s < case01.bas
ՍԽԱԼ։ [2] syntax error, unexpected xEol, expecting $end</pre>
<p>Հիմա սխալի մասին։ Բանն այն է, որ ըստ իմ սահմանած քերականության ֆայլի սկզբում նր տողի անցման նիշեր թույլատրված չեն։ Դա երևում է քերականության առաջին մի քանի կանոններից․</p>
<pre class='prettyprint lang-text'>Program
: FunctionList
;
FunctionList
: FunctionList Function
| /* empty */
;
Function
: xDeclare FunctionHeader
| FunctionHeader StatementList xEnd xFunction NewLines
;
FunctionHeader
: xFunction xIdent '(' ParameterList ')' NewLines
;</pre>
<p>Իսկ թեսթային օրինակում <code>Main()</code> ֆուկցիայի սահմանմանը նախորդում են մեկնաբանություններ և դատարկ տող։ Մեկնաբանություններն ու բացատանիշերն անտեսվում են բառային վերլոծիչի կողմից։ Մնում են նոր տողի նիշերը։</p>
<p>Որպեսզի վերլուծիչը կարողանա տեսնել ու անտեսել ֆայլի սկզբում հանդիպող նոր տողի նիշերը, <code>Program</code> կանոնում պետք է ավելացնել զրո կամ ավելի նոր տողերի նիշերի կանոնը․</p>
<pre class='prettyprint lang-text'>Program
: NewLinesOpt FunctionList
;
NewLinesOpt
: NewLines
| /* empty */
;</pre>
<p>Նորից եմ փորձում կառուցել կատորվող մոդուլը և դրան տալ թեսթային Բեյսիկ ծրագիրը։</p>
<pre class='prettyprint lang-text'>$ bison -d parser.y
$ flex -oscanner.yy.c scanner.l
$ gcc -obasic-s scanner.yy.c parser.tab.c main.c
$
$ ./basic-s < case01.bas</pre>
<p>Վա՛հ։ Սխալի հաղորդագրություն չկա։ Մի՞թե ամեն ինչ հաջող է արդեն։ Մի թեսթային օրինակ էլ պատրաստեմ, որում ֆունկցիայի մի հայտարարություն է և երկու սահմանում․</p>
<pre class="prettyprint lang-basic">' case02.bas
DECLARE FUNCTION Gcd(n, m)
FUNCTION Main()
PRINT Gcd(152, 21)
END FUNCTION
' մեծագույն ընդհանուր բաժանարար
FUNCTION Gcd(n, m)
WHILE n <> m
IF n > n THEN
n = n - m
ELSE
m = m - n
END IF
END WHILE
LET Gcd = n
END FUNCTION</pre>
<p>Ու այս օրինակն էլ տամ իմ կառուցած վերլուծիչին, որն, իհարկե, այս պահին ոչ թե վերլուծիչ ― parser է, այլ՝ «ճանաչիչ» ― recognizer, կամ «հաստատիչ» — acceptor (եթե կարելի է այդպիսի բառեր հորինել)․</p>
<pre class='prettyprint lang-text'>$ ./basic-s < case02.bas</pre>
<p>Նորից սխալի հաղորդագրություն չկա։ Սա երկու բան կարող է նշանակել․ կամ վերլուծիչը «ճանաչեց» թեսթային օրինակը, կամ էլ այն ընդհանրապես չաշխատեց։ Վերջին ստուգումն անելու համար քերականության <code>Program</code> կանոնի աջ կողմում ավելացնեմ վերլուծությունը ճիշտ ավարտելու մասին հաղորդագրություն․</p>
<pre class='prettyprint lang-text'>Program
: FunctionList
{
puts("Parsed");
}
;</pre>
<p>Կատարվող մոդուլի կառուցումից հետո, երբ դրա մուտքին տալիս եմ թեսթային ֆայլերը, վերլուծիչն արտածում է երկար սպասված <code>Parsed</code> բառը։</p>
<pre class='prettyprint lang-text'>$ ./basic-s < case01.bas
Parsed
$ ./basic-s < case02.bas
Parsed</pre>
<p>__* * *__</p>
<p>Այս պահին արդեն կարող եմ ասել, որ Բեյսիկ լեզվի համար շարահյուսական վերլուծիչ գրելու իմ առաջին փորձը հաջողվել է։</p>
<h2 id="թեսթավորում-առաջին-մաս"><a href="#թեսթավորում-առաջին-մաս">Թեսթավորում․ առաջին մաս</a></h2>
<p>Իմ հաջորդ քայլն ավելի շատ թեսթային օրինակների կառուցումն է, որոնցում ներառված են Բեյսիկ լեզվի արտահայտությունների բոլոր տեսակներն ու բոլոր ղեկավարող կառուցվածքները։ Դրանց օգնությամբ պիտի համոզվեմ, որ իմ կառուցած վերլուծիչն ընդունակ է ճանաչել ամբողջ Բեյսիկ լեզուն։</p>
<p>Սկսեմ արտահայտություններից։ Դրանք երեք տեսակի էին․ թվաբանական, համեմատման և տրամաբանական։ <code>case03.bas</code> թեսթում սահմանված երեք ֆունկցիաներում ես ծրագրավորել եմ արտահայտությունների բոլոր հնարավոր դեպքերը։</p>
<pre class="prettyprint lang-basic">' case03.bas
' գործողություններ
' թվաբանական
FUNCTION Arithmetic(x, y)
PRINT x + y
PRINT x - y
PRINT x * y
PRINT x / y
PRINT x ^ y
PRINT y
PRINT -x
PRINT 3.14
PRINT (x + y) * (x - y)
END FUNCTION
' համեմատման
FUNCTION Comparison(x, y)
PRINT x = y
PRINT x <> y
PRINT x > y
PRINT x >= y
PRINT x < y
PRINT x <= y
END FUNCTION
' տրամաբանական
FUNCTION Logical(x, y)
PRINT x OR y
PRINT x AND y
PRINT NOT x
END FUNCTION
' ֆունկցիաների ստուգում
FUNCTION Main()
CALL Arothmetical 1.2, 777
CALL Comparison 18, -5
CALL Logical 1, 0
END FUNCTION</pre>
<p>Թեսթերում ես միշտ գրում եմ <code>Main()</code> ֆունկցիան, որպեսզի կարողանամ նույն ֆայլերը հետո օգտագործել թարգմանիչի թեսթավորման համար։ Չնայած կարծում եմ, որ թարգմանիչի ֆունկցիոնալության թեսթավորման համար պետք կլինի խմբագրել այս թեսթերը և գրել նորերը։</p>
<p>Հաջորդ թեսթը ներմուծման ու արտածման հրամանների համար է։ Ներմուծման ստանդարտ հոսքից կարդում եմ <code>r</code> թիվը և արտածում եմ <code>3.1415 * r^2</code> արժեքը։</p>
<pre class="prettyprint lang-basic">' case04.bas
' ներմուծման ու արտածման հրամաններ
FUNCTION Main()
INPUT r
PRINT 3.1415 * r^2
END FUNCTION</pre>
<p>Վերագրման հրամանի թեսթում պետք է հաշվի առնել նաև, որ այն կարող է սկսվել ոչ պարտադիր <code>LET</code> բառով։</p>
<pre class="prettyprint lang-basic">' case05.bas
' վերագրման հրաման
FUNCTION Main()
x = 1 + 2 * 3
LET y = x^2
END FUNCTION</pre>
<p>Պայմանի կամ ճյուղավորման հրամանը ունի բավականին բարդ տեսք։ Հետևյալ թեսթը պարունակում է <code>IF</code> հրամանի բոլոր հնարավոր տարբերակները։</p>
<pre class="prettyprint lang-basic">' case06.bas
' պայմանի կամ ճյուղավորմն հրաման
FUNCTION Main()
x = 77
y = 0
' պարզ դեպք
IF x > y THEN
PRINT x
END IF
' մեկ այլընտրանք
IF x <> y THEN
PRINT y
ELSE
PRINT x
END IF
' շատ այլընտրանքներ
IF x = y THEN
PRINT x + y
ELSEIF x < y THEN
PRINT x - y
ELSEIF x > y THEN
PRINT x * y
END IF
' լրիվ տեսք
IF x * y <> 0 THEN
PRINT y + 1
ELSEIF x / y < 0 THEN
PRINT x + 1
ELSEIF x + y > 0 THEN
PRINT y + 2
ELSEIF x - y = 0 THEN
PRINT x^y
ELSE
PRINT y^2
END IF
END FUNCTION</pre>
<p>Հաջորդը թեսթը պարամետրով ցիկլի <code>FOR</code> հրամանի համար է (դրա <code>STEP</code> մասնիկը կարող է բացակայել)։</p>
<pre class="prettyprint lang-basic">' case07.bas
' պարամետրով ցիկլի հրաման
FUNCTION Main()
' առաջին տարբերակ
FOR i = 7 TO 16
PRINT i^2
END FOR
' երկրորդ տարբերակ
FOR i = 0 TO 12 STEP 3
PRINT i * 3
END FOR
END FUNCTION</pre>
<p>Նախապայմանով ցիկլի հրամանը թերևս ամենապարզերից է։ Դրա թեսթը նկարագրում է մի պարզ դեպք։</p>
<pre class="prettyprint lang-basic">' case08.bas
' նախապայմանով ցիկլ
FUNCTION Main()
LET a = 100
WHILE a > 0
PRINT a
a = a - 1
END WHILE
END FUNCTION</pre>
<p>Հրամանների հաջորդման, ֆունկցիայի հայտարարման ու սահմանման առանձին թեսթեր չեմ գրում, քանի որ այդ կառուցվածքները շատ անգամներ հանդիպում են արդեն գրված օրինակներում։</p>
<p>Բոլոր թեսթերը հավաքում եմ <code>tests</code> պանակում, որտեղ հետագայում ավելացնելու եմ նաև թարգմանության ակնկալվող արդյունքները։ Հենց այդտեղ էլ պետք է գրել թեսթավորող սցենարը (script)։</p>
<h2 id="արվածի-ամփոփում-և-հետագա-քայլերի-մշակում"><a href="#արվածի-ամփոփում-և-հետագա-քայլերի-մշակում">Արվածի ամփոփում և հետագա քայլերի մշակում</a></h2>
<p>Հիմա կոմպյուտերից հնչում է Չայկովսկու <a href="https://www.youtube.com/watch?v=3ZA_Vt3SQRE">«Դաշնամուրային առաջին կոնցերտը»</a>՝ Ալիս Սառա Օտտի կատարմամբ։ Եվ ես ուզում եմ այս հանգիստ պահն օգտագործել արված ամփոփելու և հետագա անելիքներս պլանավորելու համար։</p>
<p><em>Ի՞նչ ունեմ այս պահին։</em> Ունեմ մի գործիք, որի մուտքին տալիս եմ բեյսիկ լեզվով գրված ծրագիր և այն պատասխանում է, թե կարողացա՞վ արդյոք Բեյսիկի քերականական կանոններին համապատասխանեցնել տրված ծրագիրը։ Եթե կարողանում է ծրագիրն ամբողջությամբ վերլուծել, ապա արտածում է «<code>Parsed</code>» բառը, հակառակ դեպքում արտածվում է Bison-ի սովորական սխալի հաղորդագրություն։ Նշեմ, որ ես որևէ սխալների մշակում չեմ նախատեսել․ ինչ որ արվում է, արվում է Bison֊ի կողմից։</p>
<p><em>Ո՞րն էր իմ նպատակը։</em> Հիշեցնեմ, որ իմ նպատակը Բեյսիկ-JSON թարգմանիչի իրականացումն էր։ Թարգմանության համար որոշել եմ Բեյսիկ ծրագիրը վերլուծել ու կառուցել աբստրակտ քերականական ծառ, ապա այդ ծառից գեներացնել JSON կոդը։</p>
<p><em>Ի՞նչ է մնում անելու։</em> Նախ՝ պետք է իրականացնեմ աբստրակտ քերականական ծառի հանգույցների մոդելները և թեսթավորեմ դրանք։ Այդ գործը անմիջական կապ չունի Bison/Flex գործիքների աշխատանքի հետ, և ես առանձին մանրամասնությունների մեջ չեմ մտնի։ Ցանկության դեպքում ընթերցողը կարող է ինքնուրույն ուսումնասիրել այդ կոդը։ Այնուհետև՝ <code>parser.y</code> նկարագրության քերականական կանոնները (rules) պետք է ընդլայնեմ աբստրակտ քերականական ծառը կառուցող գործողություններով (actions)։ Վերջում՝ Բեյսիկ ծրագրի կարդալը և JSON կոդի արտածումը պետք է կազմակերպեմ ֆայլերից։ Իհարկե, այս բոլոր քայլերը պետք է համապատասխան ձևով թեսթավորվեն։</p>
<h2 id="աբստրակտ-քերականական-ծառ"><a href="#աբստրակտ-քերականական-ծառ">Աբստրակտ քերականական ծառ</a></h2>
<p>Ըստ Բեյսիկ լեզվի քերականության <em>աբստրակտ քերականական ծառը</em> կարող է ունենալ երեք տիպի հանգույցներ․ <em>արտահայտություններ</em>, <em>հրամաններ</em> և <em>ֆունկցիաներ</em>։ <code>ast.h</code> ֆայլում սահմանված են այս երեք տիպերն ու դրանց ենթատիպերը։ <code>ast.c</code> ֆայլում համապատասխան «կոնստրուկտորներն» են և թարգմանության ֆունկցիաները։</p>
<p>Արտահայտությունները հինգ տեսակի են․ <em>իրական թիվ</em>, <em>փոփոխական</em>, <em>ունար գործողություն</em>, <em>բինար գործողություն</em> և <em>ֆունկցիայի կանչ</em>։ Բոլոր այդ տարատեսակները մոդելավորել եմ միակ <code>_expression</code> ստրուկտուրայով, որի <code>kind</code> դաշտը ցույց է տալիս, թե դրա նմուշն ինչ արտահայտություն է ներկայացնում։</p>
<pre class='prettyprint lang-text'>/* արտահայտություններ */
typedef struct _expression expression;
struct _expression {
// արտահայտության տեսակը
enum {
NUMBER,
VARIABLE,
UNARY,
BINARY,
APPLY,
} kind;
double number; // իրական թիվ
char* name; // իդենտիֆիկատոր
// գործողությունների կոդերը
enum {
OR, AND, EQ, NE, GT, GE,
LT, LE, ADD, SUB, MUL,
DIV, POW, NOT, NEG
} oper;
expression* exo; // ենթաարտահայտություն
expression* exi; // ենթաարտահայտություն
function* func; // կիրառվող ֆունկցիա
node* args; // ֆունկցիայի կիրառման արգումենտներ
};</pre>
<p>Արտահայտության հինգ ենթատիպերի համար նախատեսված են համապատասխան կոնստրուկտորները։</p>
<pre class='prettyprint lang-text'>extern expression* create_number( double );
extern expression* create_variable( const char* );
extern expression* create_unary( int, expression* );
extern expression* create_binary( int, expression*, expression* );
extern expression* create_apply( function*, node* );</pre>
<p>Արտահայտությունները JSON ներկայացման թարգմանելու համար է <code>expression_as_json()</code> ֆունկցիան։ Սրա առաջին արգումենտը արտահայտության ցուցիչն է, իսկ երկրորդը՝ արտածման ֆայլային հոսքինը։</p>
<pre class='prettyprint lang-text'>extern void expression_as_json( expression*, FILE* );</pre>
<p>Հրամանների ենթատեսակները ութն են. <em>ներմուծում</em>, <em>արտածում</em>, <em>վերագրում</em>, <em>ճյուղավորում</em>, <em>պարամետրով ցիկլ</em>, <em>նախապայմանով ցիկլ</em>, <em>պրոցեդուրայի կանչ</em> և <em>հրամանների հաջորդում</em>։ Կառուցվածքային բազմազանության պատճառով չուզեցի բոլոր հրամանների համար սահմանել մեկ ստրուկտուրա (ինչպես դա արել եմ արտահայտությունների համար)։ Փոխարենը սահմանել եմ <code>_statement</code> ստրուկտուրան՝ <code>kind</code> տեսակի դաշտով, և կոնկրետ հրամանի <code>child</code> ունիվերսալ ցուցիչը։</p>
<pre class='prettyprint lang-text'>/* հրամաններ */
typedef struct _statement statement;
struct _statement {
// հրամանի տեսակը
enum {
INPUT, PRINT, ASSIGN, IF,
FOR, WHILE, CALL, SEQ,
} kind;
void* child; // հրամանի ցուցիչ
};</pre>
<p>Հրամանն էլ JSON ներկայացման է թարգմանվում <code>statement_as_json()</code> ֆունկցիայով։</p>
<pre class='prettyprint lang-text'>extern void statement_as_json( statement*, FILE* );</pre>
<p>Բեյսիկի բոլոր ութ հրամանները մոդելավորող ստրուկտուրաները դուրս են բերված դրանց շարահյուսական տեսքերից՝ դեն նետելով ծառայողական բառերն ու մետասիմվոլները։ Այսպես, ներմուծման հրամանը բաղկացած է <code>INPUT</code> ծառայողական բառից և ներմուծվող փոփոխականի անունից։ Այն ներկայացնող ստրուկտուրան ունի միայն փոփոխականի անունը պարունակող <code>vari</code> դաշտը։ <code>create_input</code> կոնստրուկտորը ստեղծում և վերադարձնում է <code>_statement</code> ստրուկտուրայի նմուշը, որի <code>kind</code> դաշտում <code>INPUT</code> արժեքն է, իսկ <code>child</code> դաշտը կապված է <code>_input_s</code> ստրուկտուրայի նմուշի հետ։</p>
<pre class='prettyprint lang-text'>/* ներմուծում */
typedef struct _input_s input_s;
struct _input_s {
char* vari;
};
extern statement* create_input( const char* );</pre>
<p>Արտածման հրամանը մոդելավորված է <code>_print_s</code> ստրուկտուրայով, որի միակ <code>valu</code> դաշտը կապված է արտածվելիք արտահայտության ծառի հետ։</p>
<pre class='prettyprint lang-text'>typedef struct _print_s print_s;
struct _print_s {
expression* valu;
};
extern statement* create_print( expression* );</pre>
<p>Վերագրման հրամանի <code>_assign_s</code> ստրուկտուրան երկու դաշտ ունի՝ <code>vari</code> և <code>valu</code>, դրանք համապատասխանաբար կապված են վերագրվող փոփոխականի անունին և վերագրվող արտահայտության ծառին։</p>
<pre class='prettyprint lang-text'>typedef struct _assign_s assign_s;
struct _assign_s {
char* vari;
expression* valu;
};
extern statement* create_assign( const char*, expression* );</pre>
<p>Ճյուղավորման հրամանի <code>_if_s</code> կառուցվածքում երեք դաշտեր են՝ <code>cond</code> ― պայման, <code>thenp</code> ― պայմանի ճշմարիտ լինելու դեպքում կատարվող ճյուղը և <code>elsep</code> ― պայմանի կեղծ լինելու դեպքում կատարվող ճյուղը։ (Միայն այս դեպքում եմ շեղվել շարահյուսությունից և ճյուղավորման հրամանի մոդելը կառուցել եմ ավելի պարզ, քան նկարագրված շարահյուսական կանոնում։ Կարծում եմ, որ ընթերցողն առանց մեկնաբանությունների էլ կհասկանա այդ պարզեցման հարմարությունը։)</p>
<pre class='prettyprint lang-text'>typedef struct _if_s if_s;
struct _if_s {
expression* cond;
statement* thenp;
statement* elsep;
};
extern statement* create_if( expression*, statement*, statement* );</pre>
<p>Պարամետրով ցիկլի հրամանի <code>_for_s</code> մոդելն ունի հինգ դաշտ՝ <code>FOR</code> հրամանի բաղադրիչներին համապատասխան։</p>
<pre class='prettyprint lang-text'>typedef struct _for_s for_s;
struct _for_s {
char* param;
expression* start;
expression* stop;
expression* step;
statement* body;
};
extern statement* create_for( const char*, expression*, expression*, expression*, statement* );</pre>
<p>Նախապայմանով ցիկլի մոդելի <code>_while_s</code> ստրուկտուրան ունի երկու դաշտ՝ ցիկլի պայմանի և ցիկլի մարմնի համար։</p>
<pre class='prettyprint lang-text'>typedef struct _while_s while_s;
struct _while_s {
expression* cond;
statement* body;
};
extern statement* create_while( expression*, statement* );</pre>
<p>Պրոցեդուրայի կանչի <code>CALL</code> հրամանը նման է արտահայտություններում ֆունկցիայի կիրառմանը։ <code>_call_s</code> ստրուկտուրայի դաշտերից մեկը կանչվող պրոցեդուրան է, մյուսը արգումենտների ցուցակը.</p>
<pre class='prettyprint lang-text'>typedef struct _call_s call_s;
struct _call_s {
char* func;
node* argus;
};
extern statement* create_call( const char*, node* );</pre>
<p>Հրամանների հաջորդումն էլ պարզապես ցուցակ է։ <code>_sequence_s</code> ստրուկտուրայի <code>elems</code> դաշտը</p>
<pre class='prettyprint lang-text'>typedef struct _sequence_s sequence_s;
struct _sequence_s {
node* elems;
};
extern statement* create_sequence( node* );</pre>
<p>Բեյսիկ լեզվի ֆունկցիան բաղկացած է ֆունկցիայի անունից, պարամետրերի ցուցակից և մարմնի հրամաններից։ <code>_function</code> ստրուկտուրան</p>
<pre class='prettyprint lang-text'>typedef struct _function function;
struct _function {
char* name;
node* parameters;
statement* body;
};</pre>
<p>Ֆունկցիայի JSON ներկայացումը կառուցվում է <code>function_as_json()</code> ֆունկցիայով.</p>
<pre class='prettyprint lang-text'>extern void function_as_json( function*, FILE* );</pre>
<p>Ամբողջ ծրագրի ծառը պահելու համար նախատեսել եմ <code>_program</code> ստրուկտուրան, որի <code>subs</code> դաշտը ծրագրի ֆունկցիաների ցուցակն է.</p>
<pre class='prettyprint lang-text'>typedef struct _program program;
struct _program {
node* subrs;
};</pre>
<p>Իսկ <code>program_as_json()</code> ֆունկցիան ամբողջ ծրագիր ծառից ստանում է դրա JSON ներկայացումը.</p>
<pre class='prettyprint lang-text'>extern void program_as_json( program*, FILE* );</pre>
<h2 id="bison-նկարագրության-ընդլայնում"><a href="#bison-նկարագրության-ընդլայնում">Bison նկարագրության ընդլայնում</a></h2>
<p>Երբ արդեն պատրաստ են աբստրակտ քերականական ծառի բաղադրիչները, պետք է դրա ինտերֆեյսն օգտագործել վերլուծիչում և տրված Բեյսիկ ծրագրի համար կառուցել վերլուծության ծառը։ Բայց, մինչև այդ հիմնական գործին անցնելը, ես պետք է մի քանի բաներ պատմեմ Bison֊ի քերկանական կանոնների մասին․ ցույց տամ, թե ինչպես են քերականական կանոնի տարրերի արժեքները (լեքսեմներ) օգտագործվում ԱՔԾ֊ի հանգույցների կոնստրուկտորների համար։</p>
<p>Bison֊ում քերականական կանոնը բաղկացած է <code>:</code> նիշով իրարից բաժանված ձախ ու աջ մասերից։ Ձախ մասում ոչ տերմինալային սիմվոլ է, աջ մասում՝ տերմինալային և ոչ տերմինալային սիմվոլների հաջորդականություն։ Օրինակ, <code>WHILE</code> հրամանի շարահյուսական տեսքն այսպիսինն է․</p>
<pre class='prettyprint lang-text'>Statement
: xWhile Expression NewLines StatementList xEnd xWhile
;</pre>
<p>Քերականական կանոնի անդամներից ամեն մեկի արժեքը ստանալու համար Bison֊ը դրա սիմվոլներին կցում է փսևդոփոփոխականներ։ Քերականական կանոնի ձախ մասին համապատասխանեցվում է <code>$$</code> անունը, իսկ աջ մասի տարրերին՝ <code>$1</code>, <code>$2</code>, ..., <code>$n</code> հաջորդական անունները։ <code>WHILE</code> հրամանի կանոնի համար․</p>
<pre class='prettyprint lang-text'> $$
Statement
$1 $2 $3 $4 $5 $6
: xWhile Expression NewLines StatementList xEnd xWhile
;</pre>
<p>Հենց այս փոփոխականների միջոցով են քերականական սիմվոլների արժեքներն օգտագործվում աբստրակտ քերականական ծառի հանգույցները կառուցելու (կամ վերլուծության ընթացքում հաշվարկներ կատարելու) համար։ Օրինակ, <code>WHILE</code> հրամանին համապատասախան օբյեկտը կառուցելու համար պետք է վերը բերված կանոնը լրացնել գործողությամբ․</p>
<pre class='prettyprint lang-text'>Statement
: xWhile Expression NewLines StatementList xEnd xWhile
{
$$ = create_while($2, $4);
}
;</pre>
<p>Սակայն այստեղ մի խնդիր կա։ Քերականական սիմվոլների լռելության տիպը <code>int</code> է, իսկ, օրինակ, <code>create_while()</code> կոնստրուկտորը սպասում է <code>expression*</code> ու <code>statement*</code> և վերադրձնում է <code>statement*</code>։ Bison֊ի <code>%union</code> կառուցվածքը հնարավորություն է տալիս քերականական սիմվոլների տիպը սահմանել միավորման միջոցով։ Այսպես, վերլուծելով Բեյսիկի քերականությունը, տեսնում եմ, որ քերականական սիմվոլները վեց տիպի են․ թվային հաստատուն (<code>double</code>), իդենտիֆիկատոր (<code>char*</code>), արտահայտություն (<code>expression*</code>), հրաման (<code>statement*</code>), ֆունկցիա (<code>function*</code>) և զանազան ցուցակներ (<code>node*</code>)։ Bison֊ի նկարագրության ֆայլի առաջին՝ հայտարարությունների բաժնում սահմանում եմ <code>%union</code> կառուցվածքը՝ չմոռանալով <code>%{...%}</code> սեգմենտում կցել <code>ast.h</code> ֆայլը․</p>
<pre class='prettyprint lang-text'>%union {
double number; // իրական թիվ
char* name; // իդենտիֆիկատոր
expression* expr; // արտահայտություն
statement* stat; // հրաման
function* func; // ֆունկցիա
node* list; // ցուցակ
}</pre>
<p>Իսկ <code>%type</code> հրահանգով յուրաքանչյուր քերականական սիմվոլի համապատասխանեցնում եմ <code>%union</code> տիպի մի դաշտ։</p>
<pre class='prettyprint lang-text'>%type <func> Function
%type <func> FunctionHeader
%type <stat> Statement
%type <stat> ElsePart
%type <expr> Expression
%type <expr> StepOpt
%type <list> ParameterList
%type <list> IdentifierList
%type <list> StatementList
%type <list> ElseIfPartList
%type <list> ArgumentList
%type <list> ExpressionList</pre>
<p>Տերմինալային սիմվոլների համար <code>%union</code>֊ի դաշտը կարելի է նշել հենց <code>%token</code> հրահանգով։ <code>xIdent</code> և <code>xNumber</code> սիմվոլների համար պետք է գրել․</p>
<pre class='prettyprint lang-text'>%token <name> xIdent
%token <number> xNumber</pre>
<p>Երբ այս լրացումներից հետո <code>parser.y</code> ֆայլը տալիս եմ Bison֊ի մուտքին, ստանում եմ սխալների մասին հաղորդագրությունների մի երկար շարք։ Ահա այդ հաղորդագրություններից մի քանիսը․</p>
<pre class='prettyprint lang-text'>parser.y:90.7-29: warning: type clash on default action: <func> != <> [-Wother]
: xDeclare FunctionHeader
^^^^^^^^^^^^^^^^^^^^^^^
parser.y:95.7-53: warning: type clash on default action: <func> != <> [-Wother]
: xFunction xIdent '(' ParameterList ')' NewLines
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
parser.y:100.11-16: warning: empty rule for typed nonterminal, and no action [-Wother]
| %empty
^^^^^^</pre>
<p>Առաջին հաղորդագրությունն ասում է, թե <code>Function : xDeclare FunctionHeader</code> կանոնի համար տիպերի անհամապատասխանություն է լռելության գործողության (default action) ժամանակ․ <code><func> != <></code>։ Ի՞նչ է այդ default action֊ը։ Բանն այն է, որ եթե Bison֊ի որևէ քերականական կանոնի համար գործողություն տրված չէ, ապա լռելության գործողություն է համարվում <code>$$ = $1;</code> վերագրումը։ Իմ դեպքում <code>Function</code>֊ի տիպը սահմանված է որպես <code>function*</code> (նրա համար նշված է <code>%union</code>֊ի <code>func</code> դաշտը), իսկ <code>xDeclare</code> տերմինալային սիմվոլի համար տիպ տրված չէ։ Հետևաբար <code>$$ = $1;</code> վերագրման ժամանակ ծագելու է տիպերի անհամապատասխանություն։</p>
<p>Սխալների այս խմբաքանակն ուղղելու (իսկ ավելի ճիշտ՝ լռեցնելու) համար պարզապես պետք է Bison֊ի նշած սխալ պարունակող կանոնների համար գրել «փուչ» կանոններ։ Օրինակ, վերը բերված մի քանի սխալների համար․</p>
<pre class='prettyprint lang-text'>...
Function
: xDeclare FunctionHeader
{}
| FunctionHeader StatementList xEnd xFunction NewLines
;
FunctionHeader
: xFunction xIdent '(' ParameterList ')' NewLines
{}
;
ParameterList
: IdentifierList
| %empty
{}
;
...</pre>
<p>Նույնն անելով Bison֊ի ազդարարած մյուս սխալների համար, ստանում եմ «մաքուր» նկարագրություն, որտեղ տիպերի տեսակետից ամեն ինչ համաձայնեցված է, բայց դեռ օգտակար գործողություններ չկան։</p>
<p>Անցնեմ առաջ։ Ես սովորություն ունեմ աբստրակտ քերականական ծառի կառուցումը սկսել «ամենամանր» տարրերից։ Տվյալ դեպքում սկսում եմ արտահայտություններից։ Նախ՝ պարզ տարրերը․ թվերի ու փոփոխականների համար ծառի տերևներ են կառուցվում համապատասխանաբար <code>create_number()</code> և <code>create_variable()</code> կոնստրուկտորներով․</p>
<pre class='prettyprint lang-text'>Expression
: ....
| xNumber
{
$$ = create_number($1);
}
| xIdent
{
$$ = create_variable($1);
}
;</pre>
<p>Պարզ է, որ <code>xNumber</code> և <code>xIdent</code> տերմինալային սիմվոլների արժեքը պետք է փոխանցվի բառային վերլուծիչից։ Տվյալ երկու կանոններից առաջինի դեպքում <code>$1</code> փսևդոփոփոխականը պետք է պարունակի բառային վերլուծիչի կարդացած լեքսեմի թվային արժեքը, իսկ երկրորդի դեպքում՝ <code>yytext</code> բուֆերի պատճենը։ Bison֊ի գեներացրած ֆայլում քերականական սիմվոլի տիպ ունեցող <code>yylval</code> (lexeme value) փոփոխականը նախատեսված է բառային վերլուծիչից շարահյուսական վերլուծիչ արժեքներ փոխանցելու համար։</p>
<p>Ֆունկցիայի կիրառման և ունար գործողությունների հանգույցները կառուցվում են համապատասխանաբար <code>create_apply()</code> և <code>create_unary()</code> կոնստրուկտորներով․</p>
<pre class='prettyprint lang-text'>Expression
: ....
| xIdent '(' ArgumentList ')'
{
$$ = create_apply($1, $3);
}
| xSub Expression %prec xNot
{
$$ = create_unary(NEG, $2);
}
| xNot Expression
{
$$ = create_unary(NOT, $2);
}
....</pre>
<p>Գործողությունների նախապատվության բարձրացման ու խմբավորման համար օգտագործվող <code>(</code> և <code>)</code> փակագծերի մշակումը պարզից էլ պարզ է․</p>
<pre class='prettyprint lang-text'>Expression
: ....
| '(' Expression ')'
{
$$ = $2;
}
....</pre>
<p>Բինար գործողություններին համապատասխանող բոլոր հանգույցները կառուցվում են նույն <code>create_binary()</code> կոնստրուկտորով, որի առաջին պարամետրը գործողության անունն է։ Այդ անունները սահմանված են <code>_expression</code> ստրուկտուրայի մեջ՝ որպես անանուն թվարկման անդամներ։</p>
<pre class='prettyprint lang-text'>Expression
: Expression xOr Expression
{
$$ = create_binary(OR, $1, $3);
}
...
| Expression xEq Expression
{
$$ = create_binary(EQ, $1, $3);
}
| Expression xNe Expression
{
$$ = create_binary(NE, $1, $3);
}
...
| Expression xAdd Expression
{
$$ = create_binary(ADD, $1, $3);
}
...
| Expression xMul Expression
{
$$ = create_binary(MUL, $1, $3);
}
....</pre>
<p>Աբստրակտ քերականական ծառում հրամաններին (ղեկավարող կառուցվածքներին) համապատասխանող հանգույցները կառուցվում են համապատասխան կոնստրուկտորներով։</p>
<pre class='prettyprint lang-text'>Statement
: xInput xIdent
{
$$ = create_input($2);
}
| xPrint Expression
{
$$ = create_print($2);
}
| LetOpt xIdent xEq Expression
{
$$ = create_assign($2, $4);
}
| xIf Expression xThen NewLines StatementList ElseIfPartList ElsePart xEnd xIf
{
$$ = create_if($2, create_sequence($5), $6, $7);
}
| xFor xIdent xEq Expression xTo Expression StepOpt NewLines StatementList xEnd xFor
{
$$ = create_for($2, $4, $6, $7, create_sequence($9));
}
| xWhile Expression NewLines StatementList xEnd xWhile
{
$$ = create_while($2, create_sequence($4));
}
| xCall xIdent ArgumentList
{
$$ = create_call($2, $3);
}
;</pre>
<p>Այստեղ միայն <code>IF</code> կառուցվածքի կոնստրուկտորն է, որ պարունակում է ավելի շատ դաշտեր, քան <code>_if_s</code> ստրուկտուրան։ <code>_if_s</code> ստրուկտուրան ես գրել եմ երեք դաշետերով՝ <code>cond</code>, <code>thenp</code> <code>elsep</code>։ Նույն <code>IF</code> կառուցվածքի <code>ElseIfPartList</code> տարրը սահմանված է որպես <code>IF</code>֊երի հաջորդականություն, որտեղ ամեն մի հաջորդ <code>IF</code>֊ը կապված է նախորդի <code>elsep</code> անդամին։ Սկզբում <code>ElseIfPartList</code>֊ը սահմանել էի ձախ֊ռեկուրսիվ եղանակով, ու գրել էի մի քիչ երկար կոդ, որը հերթական <code>ELSEIF</code>֊ի համար կառուցած <code>_if_s</code> նմուշը կապում է իրեն նախորդող <code>ELSEIF</code>֊երից վերջինի <code>elsep</code> անդամին։</p>
<pre class='prettyprint lang-text'>ElseIfPartList
: ElseIfPartList xElseIf Expression xThen NewLines StatementList
{
statement* anif = create_if($3, create_sequence($6), NULL, NULL);
if( $1 == NULL )
$$ = anif;
else {
if_s* heif = (if_s*)($1->child);
while( heif->elsep != NULL )
heif = (if_s*)(heif->elsep->child);
heif->elsep = anif;
$$ = $1;
}
}
| %empty
{
$$ = NULL;
}
;</pre>
<p>Հետո որոշեցի ձախ֊ռեկուրսիան փոխարինել աջ֊ռեկուրսիայով ու ստացա ավելի պարզ կոդ․</p>
<pre class='prettyprint lang-text'>ElseIfPartList
: xElseIf Expression xThen NewLines StatementList ElseIfPartList
{
$$ = create_if($2, create_sequence($5), $6, NULL);
}
| %empty
{
$$ = NULL;
}
;</pre>
<p>Չնայած, որ Bison֊ը նույն հաջողությամբ ու արդյունավետությամբ մշակում է ձախ ու աջ ռեկուրսիաները, սակայն կա տարբերություն։ Իր աշխատանքում Bison֊ը օգտագործում է երկու կարևոր գործողություններ՝ Shift և Reduce։ Shift գործողության ժամանակ քերականական սիմվոլն ավելացվում է Bison֊ի աշխատանքային ստեկում, իսկ Reduce գործողության շամանակ ստեկից հեռացվում են քերականական կանոնի աջ մասին համապատասխան տարրերը և դրանց փոխարեն ստեկում ավելացվում է նույն կանոնի աջ կողմի ոչ տերմինալային սիմվոլը։ Երբ կանոնը գրված է աջ֊ռեկուրսիվ տեսքով.</p>
<pre class='prettyprint lang-text'>ExpressionList
: Expression ',' ExpressionList
| Expression
;</pre>
<p>Bison֊ը նախ կատարում է բոլոր Shift գործողությունները և ստեկում ավելացնում է <code>Expression</code> և <code>,</code> սիմվոլները, ապա վերջում ստեկը <code>Reduce</code> գործողությամբ «կրճատում» է ըստ <code>ExpressionList</code> սիմվոլի սահմանման։ Ստացվում է, որ աջ֊ռեկուրսիվ կանոնները մշակելիս ավելի շատ ստեկ է «ծախսվում»։ Իսկ երբ կանոնն ունի ձախ֊ռեկուրսիվ սահմանում․</p>
<pre class='prettyprint lang-text'>ExpressionList
: ExpressionList ',' Expression
| Expression
;</pre>
<p>ապա Reduce գործողություններն ավելի շուտ են կատարվում, և, տվյալ օրինակի դեպքում, «ծախսվում» է ստեկի ամենաշատը երեք տարր։ Ավելի մանրամասն տես <a href="http://shop.oreilly.com/product/9780596155988.do">John Levine, <em>flex & bison</em>, O'Reilly Media, 2009</a> գրքում։</p>
<p>Բեյսիկի քերականության այն ձախ֊ռեկուրսիվ կանոնները, որոնք պիտի ցուցակ կառուցեն, ես ձևափոխեցի աջ֊ռեկուրսիվի։ Դա հեշտացնում է կապակցված ցուցակի կառուցումը։ Օրինակ, իդենտիֆիկատորների ցուցակ կառուցելու համար․</p>
<pre class='prettyprint lang-text'>IdentifierList
: xIdent ',' IdentifierList
{
$$ = create_node($1, $3);
}
| xIdent
{
$$ = create_node($1, NULL);
}
;</pre>
<p>Լավ, արտահայտություններին ու հրամաններին համապատասխան հանգույցների կառուցման հետ կապված ամենի ինչ, կարծես թե, պարզ է։ Ընթերցողին առաջարկում եմ կարդալ <code>parser.y</code> ֆայլ ու ինքնուրույն հասկանակ այն հատվածները, որոնց մասին ես այս տեքստում չեմ գրել։ Իսկ ես անցնեմ առաջ ու խոսեմ ֆունկցիաներին համապատասխան հանգույցների կառուցման մասին։</p>
<p>Երբ վերլուծիչը հանդիպում է ֆունկցիայի վերնագրի՝ <code>FunctionHeader</code>, ստեղծվում է <code>_function</code> ստրուկտուրայի նմուշ, որտեղ <code>create_function()</code> կոնստրուկտորի երրորդ արգումենտը՝ ֆունկցիայի մարմնի ցուցիչը, <code>NULL</code> է։</p>
<pre class='prettyprint lang-text'>FunctionHeader
: xFunction xIdent '(' ParameterList ')' NewLines
{
$$ = create_function($2, $4, NULL);
}
;</pre>
<p>Եթե ֆունկցիայի վերնագիրը հանդիպել է որպես ֆունկցիայի հայտարարություն (<code>DECLARE</code>), ստեղծված <code>_function</code> օբյեկտն ավելացվում է ծրագրի ենթածրագրերի ցուցակին։ Ավելորդ չէր լինի, իհարկե, այստեղ ստուգել ֆունկցիայի՝ արդեն մեկ անգամ հայտարարված լինելը։ Իսկ եթե ֆունկցիայի վերնագիրը ֆունկցիայի սահմանման մաս է, ապա նախ՝ տվյալ անունով ֆունկցիան որոնվում է ծրագրի ենթածրագրերի ցուցակում և, եթե այն արդեն հայտարարված է եղել, ապա այդ հայտարարությանն ավելացվում է սահմանվող ֆունկցիայի մարմինը։ Եթե արդեն գոյություն ունեցող ֆունկցիան մարմին ունի, ապա, թերևս, արժե ազդարաել, որ տվյալ անունով ֆունկցիան արդեն սահմանված է։ Այդ բոլոր ստուգումներն ու լրացումները թողնում եմ որպես վարժություն ընթերցողի համար։ (Ստորև բերված երկու կանոններում <code>prog</code>֊ը <code>_program</code> ստրուկտուրայի ինչ֊որ մի տեղ (կետո կասեմ, թե որտեղ) սահմանված ցուցիչ է։)</p>
<pre class='prettyprint lang-text'>Function
: xDeclare FunctionHeader
{
$$ = $2;
prog->subrs = append_to(prog->subrs, $$);
}
| FunctionHeader StatementList xEnd xFunction NewLines
{
function* fp = function_by_name(prog, $1->name);
if( fp == NULL )
prog->subrs = append_to(prog->subrs, $1);
$1->body = create_sequence($2);
$$ = $1;
}
;</pre>
<p>Bison նկարագրության ընդլայնման մասին այսքանը։ Հիմա իսկը ժամանակն է իրար վրա հավաքելու ամբողջ արված գործն ու տեսնել թե ինչպես է իմ <em>տրանսլյատորը</em> Բեյսիկ լեզվով գրված ծրագիրը թարգմանում JSON ներկայացման։</p>
<h2 id="գործարկման-երկրորդ-փորձ"><a href="#գործարկման-երկրորդ-փորձ">Գործարկման երկրորդ փորձ</a></h2>
<p>Տրանսլյատորի <em>մուտքի կետը</em> պարունակող <code>main.c</code> ֆայլում պետք է ավելացնել վերը հիշատակված <code>prog</code> օբյեկտի ցուցիչի հայտարարությունը։ Իսկ <code>main()</code> ֆունկցիայում պետք ստեղծել <code>_program</code> ստրուկտուրայի նմուշ ու կապել <code>prog</code> ցուցիչին։ Այնուհետև պետք է ստուգել <code>yyparse()</code> ֆունկցիայի վերադարձրած արժեքը. եթե այն <code>0</code> է, ապա վերլուծությունը հաջող է անցել և կարելի է կառուցել JSON ներկայացումը։</p>
<pre class='prettyprint lang-text'>/* main.c */
#include <stdio.h>
#include <gc.h>
#include "ast.h"
program* prog = NULL; // ծրագրի ցուցիչը
int main()
{
prog = GC_MALLOC(sizeof(program));
extern int yyparse();
int ok = yyparse();
if( 0 == ok )
program_as_json(prog, stdout);
return ok;
}</pre>
<p>Ամբողջ ծրագրի կառուցումը հեշտացնելու համար էլ պատրաստել եմ մի պարզ Makefile.</p>
<pre class='prettyprint lang-text'># Makefile
SOURCES=main.c scanner.yy.c parser.tab.c slist.c ast.c
all: $(SOURCES)
gcc --std=gnu11 -gdwarf-2 -obasic-s $(SOURCES) -lgc
scanner.yy.c: scanner.l
flex -oscanner.yy.c scanner.l
parser.tab.c parser.tab.h: parser.y
bison -d parser.y
clean:
rm -f *.tab.*
rm -f *.yy.c
rm -f *.o
rm -f basic-s</pre>
<p>Հիմա պետք է պարզապես bash հրամանային ինտերպրետատորում ներմուծել <code>make</code> հրամանն ու ստանալ <code>basic-o</code> կատարվող մոդուլը։</p>
<p>Այս <code>basic-o</code> մոդուլը Բեյսիկ ծրագիր տեքստը կարդում է ներմուծման ստանդարտ հոսքից, իսկ կառուցված JSON կոդը դուրս է բերում արտածման ստանդարտ հոսքին։ Ահա գործարկման մի օրինակ․</p>
<pre class='prettyprint lang-text'>$ ./basic-s < ../tests/case01.bas
{
"function" : {
"name" : "Main",
"parameters" : {},
"print" : {
"number" : 3.140000
}
}
}</pre>armenbadalhttp://www.blogger.com/profile/01200997467308938068noreply@blogger.com2tag:blogger.com,1999:blog-118668552408252052.post-47588632481041341772016-10-29T18:54:00.001+04:002016-10-30T23:17:30.101+04:00МР-155 կամ իմ «Իսկանդերը»<div abp="1362">
Մի քանի ամիս առաջ զգացի, որ <i abp="1363">ծրագրավորումն</i> ու <i abp="1364">գրականությունն</i> ինձ այլևս չեն հետաքրքրում, և խելքիս փչեց ձեռք բերել․․․ <i abp="1365">հրացան</i>։ Համարելով ինձ զենքերից քիչ թե շատ հասկացող մարդ, սկսեցի քչփորել ինտերնետում, տեսնելու համար, թե ինչպիսի քաղաքացիական զենքեր են օգտագործում զենքի սիրահարները։ Ընտրությունս կանգ առավ Սիմոնովի ինքնալիցքավորվող կարաբինին (СКС ― Самозарядный карабин Симонова)․
</div>
<div abp="1366" class="separator" style="clear: both; text-align: center;">
<a abp="1367" href="https://3.bp.blogspot.com/-VNSh6PSq9vM/WBGjUg78uFI/AAAAAAAABu8/KoxD3QL-kWYbaSQq26YZ1-Fy4vYgwn9BgCLcB/s1600/%25D0%25A1%25D0%259A%25D0%25A1.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img abp="1368" border="0" height="78" src="https://3.bp.blogspot.com/-VNSh6PSq9vM/WBGjUg78uFI/AAAAAAAABu8/KoxD3QL-kWYbaSQq26YZ1-Fy4vYgwn9BgCLcB/s400/%25D0%25A1%25D0%259A%25D0%25A1.jpg" width="400" /></a></div>
<div abp="1369">
Էլեգանտ, գեղեցիկ, հզոր մի գործիք, բայց․․․ ցավոք, <a abp="1370" href="https://www.blogger.com/parliament.am/legislation.php?sel=show&ID=1712">«Զենքի մասին» օրենքը</a> պահանջում է ունենալ զենքի օգտագործման նվազագույնը հինգ տարվա փորձ՝ ակոսավոր փողով (իսկ ՍԻԿ֊ը այդպիսին է) հրացան ձեռք բերելու և օգտագործելու համար։ Սա չստացվեց, չնայած՝ աչքս մնաց վրան, հատկապես, որ գինը բավականին մատչելի է։</div>
<div abp="1371">
<br /></div>
<div abp="1372">
Հաջորդ ընտրությունս կանգ առավ «Բայկալ» ապրանքանիշի ամենապարզ ու ամենաէժան մոդելին՝ <a abp="1373" href="http://www.baikalinc.ru/ru/company/32.html">МР-18М-М</a> հրացանին։ Սա միափող, միալիցք որսորդական հրացան է (հարթ փողով), որ առանձնանում է իր կառուցվածքի պարզությամբ ու տասնամյակների ընթացքում հաստատված հուսալիությամբ։
</div>
<div abp="1374" class="separator" style="clear: both; text-align: center;">
<a abp="1375" href="https://3.bp.blogspot.com/-OFW1PDiAkwo/WBGlBgx6SbI/AAAAAAAABvM/--Ymhe80zy8P6nqbh9lOC6ugqiAxAx1aACLcB/s1600/mr-18.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img abp="1376" border="0" height="88" src="https://3.bp.blogspot.com/-OFW1PDiAkwo/WBGlBgx6SbI/AAAAAAAABvM/--Ymhe80zy8P6nqbh9lOC6ugqiAxAx1aACLcB/s400/mr-18.jpg" width="400" /></a></div>
<div abp="1377">
Բայց հետո հասկացող մարդիկ խորհուրդ տվեցին, որ կրակելու հմտություններ զարգացնելու և սպորտային հրաձգության համար ավելի լավ է վերցնել նույն МР-18֊ի «<a abp="1378" href="http://www.baikalinc.ru/ru/company/30.html">Սպորտինգ</a>» մոդիֆիկացիան։ «Սպորտինգ»֊ը մի քանի լրացումների ունի․ նշանոցային լայն ձող, փողաբերանի փոխվող նեղացումներ, փողի վրա անցքեր՝ հետահարվածը թուլացնելու համար և այլն։
</div>
<div abp="1379" class="separator" style="clear: both; text-align: center;">
<a abp="1380" href="https://4.bp.blogspot.com/-botITRSU3Vk/WBG3re6UQWI/AAAAAAAABvc/KE2zJpSBLRMUj3QGFdlKUULPG1q-9Us_wCLcB/s1600/mr-a8-sporting.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img abp="1381" border="0" height="88" src="https://4.bp.blogspot.com/-botITRSU3Vk/WBG3re6UQWI/AAAAAAAABvc/KE2zJpSBLRMUj3QGFdlKUULPG1q-9Us_wCLcB/s400/mr-a8-sporting.jpg" width="400" /></a></div>
<div abp="1382">
Բայց իմ իմացած խանութներում այս մոդելը չգտա, իսկ «ձեռի վրայից» չեմ ուզում առնել։ Կարճ ասած՝ սա էլ չստացվեց։</div>
<div abp="1383">
<br /></div>
<div abp="1384">
Շարունակելով որոնումներս ու ծանոթ-բարեկամներիս հարցուփորձ անելը՝ հանդիպեցի նույն «Բայկալ» ապրանքանիշի մի շատ գեղեցիկ ներկայացուցչի՝ կիսաավտոմատ (ինքնալիցքավորվող) <a abp="1385" href="http://www.baikalinc.ru/ru/company/374.html">МР-155</a>-ին։
</div>
<div abp="1386" class="separator" style="clear: both; text-align: center;">
<a abp="1387" href="https://1.bp.blogspot.com/-IFxS5HG5QVc/WBJFHvgl-pI/AAAAAAAABvs/V0CJvH6vct8Z-YlD3L-xzY4k02nrXXQ-QCLcB/s1600/mr-155.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img abp="1388" border="0" height="110" src="https://1.bp.blogspot.com/-IFxS5HG5QVc/WBJFHvgl-pI/AAAAAAAABvs/V0CJvH6vct8Z-YlD3L-xzY4k02nrXXQ-QCLcB/s400/mr-155.jpg" width="400" /></a></div>
<div abp="1389">
Գինը... ընդունելի է, կառուցվածքի (մեխանիզմի) պարզությունն ինձ դուր եկավ․ որոշեցի սա գնել։</div>
<div abp="1390">
<br /></div>
<div abp="1391">
<a abp="1392" href="http://arms.am/" target="_blank">Ասպար</a> խանութին ներկայացրեցի պահանջվող փաստաթղթերն ու երկու օր հետո կանչեցին գործիքը վերցնելու։ Լավ ձևավորված ստվարաթղթե տուփը տեսնելով՝ մտածեցի, թե խաղալիք հրացան է։ Տուփում հրացանն է՝ առանձնացված փողով, փողաբերանի փոխվող նեղացումներն են՝ երկու հատ (երրորդը փողի վրա է), Գազամխոցի կարգավորման բանալին է և հրացանի օգտագործողի ձեռնարկը։ (Բոլոր մասերը պատված են կոնսերվացիոն յուղով։ Դա պետք է մաքրել ու նորից յուղել արդեն որպես սովորական զենք։) Առաջին տպավորությունը շատ լավն է․ թեթև է, հարմար է, ընկուզենու փայտից պատրաստված կոթն ու փողակալը շատ գեղեցիկ են։ Միանգամից ցանկություն առաջացավ նրան հատուկ անուն տալ, ու վերջին օրերի տպավորությամբ նրան անվանեցի «<i abp="1393">Իսկանդեր</i>»։</div>
<div abp="1394">
<br /></div>
<div abp="1395">
Հաջորդ անգամ՝ առաջին կրակոցների մասին։ </div>
<div abp="1396">
<br /></div>
<center abp="1397">
* <sup abp="1398"><big abp="1399">*</big></sup> *</center>
<div abp="1400">
<br /></div>
<div abp="1401">
<b abp="1402">30.10.2016:</b> Հրացանը վերցնելիս խանութից գնել էի նաև 12 տրամաչափի մեկական տուփ փամփուշտեր՝ 7 և 4 համարի կոտորակներով (Record մակնիշի)։ Մի քանի հատ կրակեցի առանց թիրախի, առանց նշան բռնելու՝ պարզապես գործիքի բնույթը հասկանալու համար։ Լավն է. բալանսավորված, հետհարվածը չափավոր է, կոմֆորտային, արձակած ձայնը շատ կոպիտ չէ։</div>
armenbadalhttp://www.blogger.com/profile/01200997467308938068noreply@blogger.com0tag:blogger.com,1999:blog-118668552408252052.post-89517487877128539002016-03-12T20:00:00.002+04:002016-03-12T20:00:28.542+04:00Գրքեր Lisp լեզվի մասինՎերջին մի քանի տարիներին ես ակտիվորեն ուսումնասիրում եմ Lisp լեզուն, հատկապես նրա Common Lisp և Scheme տարատեսակները։ Եվ այդ ժամանակի ընթացքում հասցրել եմ ուսումնասիրել ավելի քան հարյուր գրքեր ու հոդվածներ՝ սկսած John McCarthy֊ի առաջին հոդվածից, վերջացրած Common Lisp֊ի ստանդարտով ու արհեստական բանականության մասին մենագրություններով։ Սակայն ես ուզում եմ այդ գրքերից առանձնացնել քանիսը, որոնք հատկապես կարևոր են Lisp լեզվի ուսումնասիրությունը սկսելու համար։<br/>
<br />
<div class="separator" style="clear: both; text-align: right; float:right"><img border="0" src="https://3.bp.blogspot.com/-w0IAVwxJ04I/VuLQlDMYCJI/AAAAAAAABns/ZJvvoO6loCc-DnHgyv2EUQ9mG3V3v4pCw/s200/practical-common-lisp-en.jpg" /><img border="0" src="https://2.bp.blogspot.com/-xlXh6XBjDkY/VuLQn0BtWBI/AAAAAAAABns/1teMjs3h1c0qp8kEnpQwF0KXahGqBuSew/s1600/practical-common-lisp-ru.jpg" /></div>
<b>1.</b> <i>Practical Common Lisp</i>, Peter Seibel ― հրաշալի գիրք է, գրված կենդանի լեզվով, առանց ավելորդ ճոռոմաբանությունների։ Բավականին շատ օրինակներով, որոնք բացահայտում են լեզվի բազմաթիվ հնարավարությունները և դրանց կիրառման առանձնահատկությունները։ Ազատ <a href="http://www.gigamonkeys.com/book/">հասանելի</a> է։ Մի քանի տարի առաջ թարգմանվել է նաև ռուսերեն՝ <i>Практическое использование Common Lisp</i> վերնագրով։<br/>
<br/>
<div class="separator" style="clear: both; text-align: center; float:left"><img border="0" src="https://1.bp.blogspot.com/-mSrr96AdZpU/VuLQmuicEHI/AAAAAAAABns/AZkCGnQ1h80fLiafeo211dziWLMJapTCg/s200/ansi-common-lisp-en.jpg" /><img border="0" src="https://2.bp.blogspot.com/-RrqZdj-8H0o/VuLQmkIgf6I/AAAAAAAABns/v4g1HaDPjFcZQlnVyT3BFDjj5gHVf_EhQ/s200/ansi-common-lisp-ru.jpg" /></div>
<b>2.</b> <i>ANSI Common Lisp</i>, Paul Graham ― Գրքի նախաբանում ասվում է, որ այն նախատեսված է Լիսպ լեզուն արագ ու հիմնավոր սովորելու համար։ Գրքի առաջին մասում մանրամասնորեն նկարագրվում են Լիսպ լեզվի հնարավորությունները, իսկ երկրորդ մասում՝ թվարկված և համառոտ նկարագրված է Common Lisp ստանդարտը։ Հեղինակը ՏՏ աշխարհի թերևս ամենահաջողակ մարդկանցից մեկն է։ Գիրքը նույն վերնագրով թարգմանված է ռուսերեն։<br/>
<br/>
<div class="separator" style="clear: both; text-align: center; float: right"><img border="0" src="https://1.bp.blogspot.com/-DLJdFRqoEUo/VuLQm3iWGdI/AAAAAAAABns/19v8Ms8EJAwVF_KSavbL5XYJxXVMi8Vnw/s200/common-lisp-the-language-2-en.jpg" /></div>
<b>3.</b> <i>Common Lisp: the Language, 2nd ed.</i>, Guy Steel Jr. ― Այս գիրքը հենց Common Lisp ստանդարտն է։ Դրա ավելի քան 1000 էջերում մանրամասնորեն ու սպառիչ նկարագրված է լեզվի յուրաքանչյուր բաղադրիչ։ Լիսպ լեզվով աշխատելիս շատ օգտակար է մշտապես ձեռքի տակ ունենալ այս գիրքը։ Թարգմանված է ռուսերեն, սակայն հրատարակված չէ թղթի տարբերակով։ Common lisp: the Language գրքի և Common Lisp ստանդարտի հիման վրա պատրաստված է <a href="http://www.lispworks.com/documentation/HyperSpec/Front/index.htm">Common Lisp HyperSpec</a>֊ը<br/>
<br/>
<div class="separator" style="clear: both; text-align: center; float:left"><img border="0" src="https://4.bp.blogspot.com/-e5Dhm88I3DE/VuLQnBRHq1I/AAAAAAAABns/yrzPByFZsKU8PrLv5FY7_NyuNn0ZT1ekQ/s200/land-of-lisp-en.jpg" /></div>
<b>4.</b> <i><a href="http://landoflisp.com/">Land of Lisp</a></i>, Conrad Barski ― գծանկարներով ու ծաղրանկարներով հարուստ այս գրքում տարատեսակ խաղեր ծրագրավորելու օգնությամբ ու բավականին սրամիտ լեզվով պատմվում է Common Lisp լեզվի մասին։ Շատ հետաքրքիր գիրք է, հատկապես սկսնակների համար։<br/>
<br/>
<div class="separator" style="clear: both; text-align: center; float:right"><img border="0" src="https://1.bp.blogspot.com/-oDToNtsU0nM/VuLQmvPI3tI/AAAAAAAABns/Ai9LJZBk5ewf1IUROzqicAtGPLVpjVZaQ/s200/common-lisp-recipes-en.jpg" /></div>
<b>5.</b> <i>Common Lisp Recipes</i>, Edmund Weitz ― Նոր եմ գտել այս գիրքը, դեռ չեմ հասցրել կարդալ։ Բայց բովանդակության մեջ քննարկված թեմաներից երևում է, որ շատ օգտակար ու հետաքրքիր նյութ է պարունակում։<br/>
<br/>
Սկսելու համար թերևս այսքանը։
armenbadalhttp://www.blogger.com/profile/01200997467308938068noreply@blogger.com0tag:blogger.com,1999:blog-118668552408252052.post-21643147375553623062016-03-07T11:16:00.002+04:002016-03-07T11:31:05.495+04:00Ինչի՞ց սկսել Java լեզվի ուսումնասիրությունըԻմ ընկերները, ուսանողները և ծանոթները, այն մարդիկ, ովքեր որոշել են սովորել ծրագրավորման Java լեզուն, հաճախ են հարցնում, թե ինչ գրքերից սկսել։ Ստորև ես հավաքել եմ այն մի քանի գրքերը, որոնք ինքս օգտագործել եմ (և օգտագործում եմ) Ջավա լեզվի հետ աշխատելիս։<br/>
<br/>
<div class="separator" style="clear: both; text-align: center;"><a href="http://4.bp.blogspot.com/-FjcyPLAWSuM/VqdqJ6kFwwI/AAAAAAAABl4/scQHmSCnOSA/s1600/introduction-to-programming-in-java.png" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"><img border="0" src="http://4.bp.blogspot.com/-FjcyPLAWSuM/VqdqJ6kFwwI/AAAAAAAABl4/scQHmSCnOSA/s200/introduction-to-programming-in-java.png" /></a></div>
<b>1.</b> Մի քանի տարի առաջ եմ հայտնաբերել Robert Sedgewick-ի, Kevin Wayne֊ի «<i>Introduction to programming in Java</i>», գիրքը, որը նախատեսված է Պրինստոնի համալսարանում ծրագրավորման սկզբնական դասընթացի համար։ Գեղեցիկ ու բազմազան օրինակներով, սխեմաներով ու պատկերներով հարուստ գիրք է։ (Վերջերս այս գիրքը վերահրատարակվել է՝ Java լեզուն Python լեզվով փոխարինված։)
<div class="separator" style="clear: both; text-align: center;"><a href="https://2.bp.blogspot.com/-3pCD7iGwL-4/Vt0od3GjQMI/AAAAAAAABmQ/oLfXqH-7Z2o/s1600/Java-8-%25D0%25A0%25D1%2583%25D0%25BA%25D0%25BE%25D0%25B2%25D0%25BE%25D0%25B4%25D1%2581%25D1%2582%25D0%25B2%25D0%25BE-%25D0%25B4%25D0%25BB%25D1%258F-%25D0%25BD%25D0%25B0%25D1%2587%25D0%25B8%25D0%25BD%25D0%25B0%25D1%258E%25D1%2589%25D0%25B8%25D1%2585.jpg" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"><img border="0" src="https://2.bp.blogspot.com/-3pCD7iGwL-4/Vt0od3GjQMI/AAAAAAAABmQ/oLfXqH-7Z2o/s200/Java-8-%25D0%25A0%25D1%2583%25D0%25BA%25D0%25BE%25D0%25B2%25D0%25BE%25D0%25B4%25D1%2581%25D1%2582%25D0%25B2%25D0%25BE-%25D0%25B4%25D0%25BB%25D1%258F-%25D0%25BD%25D0%25B0%25D1%2587%25D0%25B8%25D0%25BD%25D0%25B0%25D1%258E%25D1%2589%25D0%25B8%25D1%2585.jpg" /></a></div>
<b>2.</b> <i>Java 8. Руководство для начинающих</i>, Герберт Шилдт ― հաջող գիրք է, հայտնի հեղինակը բավականին մանրամասնորեն շարադրում է Ջավա լեզվի վերջին տարբերակի հնարավորությունները։ Շատ հարմար է սկսելու համար։
<div class="separator" style="clear: both; text-align: center;"><a href="https://4.bp.blogspot.com/-XX4G9qVWfKc/Vt0o2rwjxoI/AAAAAAAABmU/GKwPJPUaSv0/s1600/Java-8-%25D0%259F%25D0%25BE%25D0%25BB%25D0%25BD%25D0%25BE%25D0%25B5-%25D1%2580%25D1%2583%25D0%25BA%25D0%25BE%25D0%25B2%25D0%25BE%25D0%25B4%25D1%2581%25D1%2582%25D0%25B2%25D0%25BE.jpg" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"><img border="0" src="https://4.bp.blogspot.com/-XX4G9qVWfKc/Vt0o2rwjxoI/AAAAAAAABmU/GKwPJPUaSv0/s200/Java-8-%25D0%259F%25D0%25BE%25D0%25BB%25D0%25BD%25D0%25BE%25D0%25B5-%25D1%2580%25D1%2583%25D0%25BA%25D0%25BE%25D0%25B2%25D0%25BE%25D0%25B4%25D1%2581%25D1%2582%25D0%25B2%25D0%25BE.jpg" /></a></div>
<b>3.</b> <i>Java 8. Полное руководство</i>, Герберт Шилдт ― նույն հեղինակի մի ուրիշ գիրք, որն արդեն Ջավա լեզվի սպառիչ տեղեկատու է։ Այս գրքում կարելի է գտնել համարյա ամեն ինչ։
<div class="separator" style="clear: both; text-align: center;"><a href="https://2.bp.blogspot.com/-0BsXFQWKXLU/Vt0pDwkfTTI/AAAAAAAABmY/t1Eads_Qzug/s1600/Java-7-%25D0%259D%25D0%25B0%25D0%25B8%25D0%25B1%25D0%25BE%25D0%25BB%25D0%25B5%25D0%25B5-%25D0%25BF%25D0%25BE%25D0%25BB%25D0%25BD%25D0%25BE%25D0%25B5-%25D1%2580%25D1%2583%25D0%25BA%25D0%25BE%25D0%25B2%25D0%25BE%25D0%25B4%25D1%2581%25D1%2582%25D0%25B2%25D0%25BE.jpg" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"><img border="0" src="https://2.bp.blogspot.com/-0BsXFQWKXLU/Vt0pDwkfTTI/AAAAAAAABmY/t1Eads_Qzug/s200/Java-7-%25D0%259D%25D0%25B0%25D0%25B8%25D0%25B1%25D0%25BE%25D0%25BB%25D0%25B5%25D0%25B5-%25D0%25BF%25D0%25BE%25D0%25BB%25D0%25BD%25D0%25BE%25D0%25B5-%25D1%2580%25D1%2583%25D0%25BA%25D0%25BE%25D0%25B2%25D0%25BE%25D0%25B4%25D1%2581%25D1%2582%25D0%25B2%25D0%25BE.jpg" /></a></div>
<b>4.</b> <i>Java 7. Наиболее полное руководство</i>, Хабибуллин Ильдар ― էլի լավ գիրք է, բայց սա ես խորհուրդ կտայի պարզապես ձեռքի տակ ունենալ, և եթե ին֊որ բան չես գտնում Շիլդտի գրքերում, նայել այստեղ։
<div class="separator" style="clear: both; text-align: center;"><a href="https://1.bp.blogspot.com/-649rC_pFQtc/Vt0pNZ_iy9I/AAAAAAAABmg/Ci01W3rCkqk/s1600/%25D0%25AF%25D0%25B7%25D1%258B%25D0%25BA-%25D0%25BF%25D1%2580%25D0%25BE%25D0%25B3%25D1%2580%25D0%25B0%25D0%25BC%25D0%25BC%25D0%25B8%25D1%2580%25D0%25BE%25D0%25B2%25D0%25B0%25D0%25BD%25D0%25B8%25D1%258F-Java-SE-8.jpg" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"><img border="0" src="https://1.bp.blogspot.com/-649rC_pFQtc/Vt0pNZ_iy9I/AAAAAAAABmg/Ci01W3rCkqk/s200/%25D0%25AF%25D0%25B7%25D1%258B%25D0%25BA-%25D0%25BF%25D1%2580%25D0%25BE%25D0%25B3%25D1%2580%25D0%25B0%25D0%25BC%25D0%25BC%25D0%25B8%25D1%2580%25D0%25BE%25D0%25B2%25D0%25B0%25D0%25BD%25D0%25B8%25D1%258F-Java-SE-8.jpg" /></a></div>
<b>5.</b> <i>Язык программирования Java SE 8. Подробное описание</i>, Джеймс Гослинг, Гай Л. Стил և ուրիշներ ― հզոր գիրք է։ Հեղինակները հենց Ջավա լեզվի հեղինակներն են, ովքեր Ջավայի մասին գիտեն ամեն ինչ։
<div class="separator" style="clear: both; text-align: center;"><a href="https://4.bp.blogspot.com/-QMoBYSYxMsI/Vt0pYw2oRzI/AAAAAAAABmk/8UOkfHvtzCI/s1600/Java-%25D0%25AD%25D1%2584%25D1%2584%25D0%25B5%25D0%25BA%25D1%2582%25D0%25B8%25D0%25B2%25D0%25BD%25D0%25BE%25D0%25B5-%25D0%25BF%25D1%2580%25D0%25BE%25D0%25B3%25D1%2580%25D0%25B0%25D0%25BC%25D0%25BC%25D0%25B8%25D1%2580%25D0%25BE%25D0%25B2%25D0%25B0%25D0%25BD%25D0%25B8%25D0%25B5.jpg" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"><img border="0" src="https://4.bp.blogspot.com/-QMoBYSYxMsI/Vt0pYw2oRzI/AAAAAAAABmk/8UOkfHvtzCI/s200/Java-%25D0%25AD%25D1%2584%25D1%2584%25D0%25B5%25D0%25BA%25D1%2582%25D0%25B8%25D0%25B2%25D0%25BD%25D0%25BE%25D0%25B5-%25D0%25BF%25D1%2580%25D0%25BE%25D0%25B3%25D1%2580%25D0%25B0%25D0%25BC%25D0%25BC%25D0%25B8%25D1%2580%25D0%25BE%25D0%25B2%25D0%25B0%25D0%25BD%25D0%25B8%25D0%25B5.jpg" /></a></div>
<b>6.</b> <i>Java. Эффективное программирование</i>, Блох Джошуа ― նորից հետաքրքիր գիրք է, որտեղ քննարկվում են Ջավա լեզվով ծրագրավորման առանձին հարցեր, առաջարկվում են հաճախ հանդիպող խնդիրների արդյունավետ լուծումներ։
<div class="separator" style="clear: both; text-align: center;"><a href="https://2.bp.blogspot.com/-bCuBNsCiViM/Vt0psoTGpsI/AAAAAAAABmw/LXMoP9VhESs/s1600/%25D0%2590%25D0%25BB%25D0%25B3%25D0%25BE%25D1%2580%25D0%25B8%25D1%2582%25D0%25BC%25D1%258B-%25D0%25BD%25D0%25B0-Java.jpg" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"><img border="0" src="https://2.bp.blogspot.com/-bCuBNsCiViM/Vt0psoTGpsI/AAAAAAAABmw/LXMoP9VhESs/s200/%25D0%2590%25D0%25BB%25D0%25B3%25D0%25BE%25D1%2580%25D0%25B8%25D1%2582%25D0%25BC%25D1%258B-%25D0%25BD%25D0%25B0-Java.jpg" /></a></div>
<b>7.</b> <i>Алгоритмы на Java</i>, Седжвик Роберт, Уэйн Кевин ― իմ ամենասիրած գրքերից է։ Սա արդեն ոչ թե Ջավա լեզվի մասին է, այլ Ալգորիթմների իրականացման մասին է Ջավա լեզվի օգտագործմամբ։
<div class="separator" style="clear: both; text-align: center;"><a href="https://2.bp.blogspot.com/-Tm8u1969y4E/Vt0pe90iY0I/AAAAAAAABmo/XURPDIKZbqk/s1600/Algorithms-4th-Edition.png" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"><img border="0" src="https://2.bp.blogspot.com/-Tm8u1969y4E/Vt0pe90iY0I/AAAAAAAABmo/XURPDIKZbqk/s200/Algorithms-4th-Edition.png" /></a></div>
<b>8.</b> <i>Algorithms (4th Edition)</i>, Robert Sedgewick, Kevin Wayne ― նախորդ գրքի անգլերեն (օրիգինալ) տարբերակն է։
<hr/>
Բոլոր գրքերը կարելի է գտնել <a href="https://drive.google.com/folderview?id=0B6qzh3Iyt_afUmtlMXk2SGp3S2M&usp=sharing">այստեղ</a>։armenbadalhttp://www.blogger.com/profile/01200997467308938068noreply@blogger.com1tag:blogger.com,1999:blog-118668552408252052.post-22221437856227135042016-03-04T14:43:00.000+04:002016-03-04T14:43:59.845+04:00GNU Emacs֊ի ևս մեկ ընդլայնում<a href="https://www.gnu.org/software/emacs/">GNU Emacs</a> տեքստային խմբագրիչն իմ ամենօրյա աշխատանքային գործիքն է։ Դրանով եմ ես ծրագրեր գրում, նշումներ անում, OCR արված տեքստեր մաքրագրում և աշխատում իմ սեփական գրքերի ու գրառումների տեքստերի հետ։ Այս բոլոր գործերի մեծամասնությունը հայերեն տեքստերի հետ է կապված։ Եվ, բնականաբար, ինձ պետք է լինում ակտիվորեն օգտագործել Emacs-ի <i>key-binding</i>֊ները՝ ստեղնների համակցությունների հետ կապված գործողությունները։ Օրինակ, նոր ֆայլ ստեղծելու գործողությունը կապված է <code>C-x C-f</code> հաջորդականության հետ, որը նշանակում է․ «<i>սեղմած պահել <code>Control</code> ստեղնը և հաջորդաբար սեղմել <code>x</code> և <code>f</code> ստեղնները</i>»։ Սակայն անհարմարությունն այն է, որ Emacs֊ի բոլոր key-binding֊ները արված են լատինական (անգլերենի այբուբենի) տառերի համար, և հայերեն տեքստերի հետ աշխատելու ժամանակ, երբ մի որևէ գործ է պետք լինում անել, ես ստիպված եմ լինում փոխել ստեղնաշարը հայերենից անգլերենի, կատարել գործողութնունը (օրինակ, պահպանել ֆայլը, նշել տեքստի հատվածը և այլն), ապա վերադառնալ հայերեն դասավորությանն ու շարունակել իմ գործը տեքստի հետ։<br/>
<br/>
Խմբագրման և գործողությունների հետ կապված անհարմարությունները լուծելու համար ես որոշեցի Emacs֊ի key-binding֊ները լրացնել նաև հայերեն տարբերակներով։ Այսինքն, ես ուզում եմ իմ <code>.emacs</code> ֆայլում ունենալ լատիներեն key-binding֊ներից հայերենի արտապատկերող մի այսպիսի արտահայտություն․
<pre class="prettyprint lang-lisp">
(armenian-keys
'(("C-x C-f" . "C-ղ C-ֆ")
("C-x C-s" . "C-ղ C-ս")
("M-w" . "M-ո")))
</pre>
որում <code>armenian-keys</code> ֆունկցիան ստանում է <i>կետով զույգերի</i> (dotted pair) ցուցակ, որի տարրերից առաջինը արդեն գոյություն ունեցող համակցությունն է, իսկ երկրորդը դրա հայերեն համարժեքը, որը պետք է ստեղծել։<br/>
<br/>
Արդեն գոյություն ունեցող key-binding֊ները պահվում են <a href="https://www.gnu.org/software/emacs/manual/html_node/elisp/Controlling-Active-Maps.html">current-global-map</a> ֆունկցիայի վերադարձրած օբյեկտում։ Ինձ հետաքրքրող key-binding֊ը վերցնում եմ <a href="https://www.gnu.org/software/emacs/manual/html_node/elisp/Functions-for-Key-Lookup.html">lookup-key</a> ֆունկցիայով, և <a href="https://www.gnu.org/software/emacs/manual/html_node/elisp/Key-Binding-Commands.html">global-set-key</a> ֆունկցաիյով կապում եմ հայերեն համակցությանը։<br/>
<br/>
<code>armenian-keys</code> ֆունկցիան սահմանել եմ հետևյալ կերպ․ այն անցնում է տրված ցուցակի տարրերով և ամեն մի զույգի համար կատարում է վերը թվարկված գործողությունները։
<pre class="prettyprint lang-lisp">
(defun armenian-keys (kml)
(let ((cgm (current-global-map)))
(dolist (e kml)
(let ((en (kbd (car e)))
(hy (kbd (cdr e))))
(global-set-key hy (lookup-key cgm en))))))
</pre>
Հիմա ես կարող եմ առանց ստեղնաշարը փոխելու օգտագործել ինձ հարկավոր գործողությունները։armenbadalhttp://www.blogger.com/profile/01200997467308938068noreply@blogger.com0tag:blogger.com,1999:blog-118668552408252052.post-27298610744093852562016-01-10T15:15:00.000+04:002016-01-10T15:15:40.325+04:00GNU/Emacs֊ի փոքր ընդլայնում<p>Հայերեն տեքստերը OCR գործիքներով ճանաչելիս բավականին հաճախ է պատահում, որ բառի մեջ հայտնվում են անցանկալի բացատանիշեր։ Դա կապված է, օրինակ հայերեն տառերի պատկերների հետ, երբ տառի ձախ ու աջ կողմերից կան ցցված մասեր։</p>
<p>Երբ մաքրագրում եմ այդպիսի «ցանցառ» տեքստերը, ժամանակիս մեծ մասը ծախսվում է բացատները հեռացնելու վրա։ Հենց այդ պատճառով էլ որոշեցի GNU/Emacs֊ի համար (քանի որ այդ խմբագրիչն եմ առավել հաճախ օգտագործում) գրել մի օժանդակ ֆունկցիա, որը կհեռացնի տեքստնի նշված հատվածի՝ <em>ռեգիոնի</em> բացատները։</p>
<p>Ես պետք է կատարեի հետևյալ քայլերը․ <strong>i</strong>) վերցնել նշված տեքստը, <strong>ii</strong>) տեքստից հեռացնել բացատները, <strong>iii</strong>) հեռացնել հին տեքստը և <strong>iv</strong>) տեղադրել հեռացված բացատներով տեքստը։ Այս քայլերը պետք է ծրագրավորել որպես Emacs Lisp լեզվի ֆունկցիա, և կապել ստեղնների ինչ֊որ համակցության հետ, որպեսզի հնարավոր լինի այն օգտագործել տեքստի խմբագրման ինտերակտիվ ռեժիմում։</p>
<p>Բնականաբար, գործը սկսվեց ինտերնետում փորփրելուց՝ վերը նշված քայլերը կատարող գործողություների որոնմամբ։ Եվ այսպես․ <strong>i</strong>) Emacs֊ի բուֆերից՝ խմբագրվող տեքստի տիրույթից տեքստի հատվածը կարելի է վերցնել <code>buffer-substring</code> և <code>buffer-substring-no-properties</code> ֆունկցիաներով։ Սրանցից առաջիննը տեքստը տալիս է ինչ֊որ ատրիբուտների հետ, իսկ երկրորդը՝ առանց ատրիբուտների: ինձ պետք է երկրորդը։ <strong>ii</strong>) Տեքստը ֆիլտրելու համար նախ պետք է <code>string-to-list</code> ֆունկցիայով նրանից ստանալ ցուցակ, ապա այդ ցուցակից <code>remq</code> ֆունկցիայով հեռացնել ոչ պետքական տարրերը, վերջում էլ ցուցակից նորից ստանալ տող։ <strong>iii</strong>) Ռեգիոնը բուֆերից հեռացվում է <code>delete-region</code> ֆունկցիայով։ <strong>iv</strong>) Բուֆերի ընթացիկ կետում (<code>point</code>) տեքստը տեղադրվում է <code>insert</code> ֆունկցիայով։</p>
<p>Իմ գրած <code>remove-region-spaces</code> ֆունկցիան ունի երկու պարամետր՝ նշված տեքստի սկիզբն ու վերջը ցույց տվող ինդեքսները։ Ֆունկցիայի առաջին տողում գրված <code>(interactive "r")</code> արտահայտությունը պահանջում է, որ խմբագրման ինտերակտիվ ռեժիմում այս ֆունկցիան կանչելիս նրան փոխանվեն ռեգիոնի սկիզբն ու վերջը (ավելի ճիշտ՝ <code>mark</code>֊ը և <code>point</code>֊ը)։</p>
<pre class="prettyprint lang-lisp">
(defun remove-region-spaces (begin end)
(interactive "r")
(let ((text (buffer-substring-no-properties begin end)))
(delete-region begin end)
(insert (apply #'string (remq ?\ (string-to-list text))))))
</pre>
<p>GNU/Emacs֊ում ստեղնների <code>C-x C-j</code> համադրությունն ազատ է։ <code>remove-region-spaces</code> ֆունկցիան կապում եմ այդ ստեղնների հետ՝ և՛ լատինական և հայերեն տառերի համար։</p>
<pre class="prettyprint lang-lisp">
(global-set-key (kbd "C-x C-j") 'remove-region-spaces)
(global-set-key (kbd "C-ղ C-յ") 'remove-region-spaces)
</pre>
<p>Այսքանը։ <code>remove-region-spaces</code> ֆունկցիան և այն ստեղնների համադրության հետ կապող արտահայտությունները գրում եմ <code>.emacs</code> ֆայլում, և վերագործարկում եմ խմբագրիչը։</p>
armenbadalhttp://www.blogger.com/profile/01200997467308938068noreply@blogger.com0tag:blogger.com,1999:blog-118668552408252052.post-82979783645978962972015-12-24T17:26:00.001+04:002015-12-24T17:31:25.030+04:00Ի՞նչ կարդալ ծրագրավորման լեզուների իրականացման մասին<div class="separator" style="clear: both; text-align: center;">
<a href="http://1.bp.blogspot.com/-HfL8JEsoH2A/VnvyTeg-vqI/AAAAAAAABj4/ZPQxA1rldIc/s1600/cptt-cover.jpg" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"><img border="0" height="320" src="http://1.bp.blogspot.com/-HfL8JEsoH2A/VnvyTeg-vqI/AAAAAAAABj4/ZPQxA1rldIc/s320/cptt-cover.jpg" width="211" /></a></div>
Վերջերս ավելի ու ավելի հաճախ եմ լսում այն հարցը, թե <i>ի՛նչ կարդալ կոմպիլյատորների մասին</i>։ Ծրագրավորման լեզուների նախագծման ու իրականացման մասին գրականության պակաս, իհարկե, չկա։ Սա ինֆորմատիկայի և ծրագրավորման այն ոլորտն է, որը թե՛ գիտական, թե՛ գործնական տեսակետից ակտիվորեն ուսումնասիրվել և ուսումնասիրվում է, հրապարակվում են նոր հետազոտություններ, գրվում են նոր գրքեր։ Այս գրառման մեջ ես ուզում եմ առանաձնացնել մի քանի հանրահայտ աշխատանքներ, որոնք իմ գրադարանում զտվել են վերջին 10 տարիների փնտրտունքների արդյունքում, և որոնք ես օգտագործում եմ թե՛ իմ աշխատանքում, թե՛ մասնավոր հետաքրքրություններում և թե՛ դասավանդման ընթացքում։ (Բնականաբար ոչ մի խոսք չի կարող լինել հայերեն նյութերի մասին․ գրքերը որոնք մատչելի են թղթային կամ էլեկտրական տարբերակով մեծամասամբ անգլերենով կամ ռուսերենով են)։<br />
<br/>Բայց ուզում եմ նաև վերաձևակերպել վերը բերված հարցը՝ դարձնելով այն ավելի տարողունակ (միգուցե նաև ավելի ճշգրիտ ու հետաքրքիր)։ Եվ այսպես․ «<i>ի՞նչ կարդալ ծրագրավորման լեզուների և դրանց իրականացման մասին</i>»։<br />
<br/>Սկսելու համար, իմ կարծիքով, լավագույնը Jack Crenshaw֊ի «Let's Build a Compiler» հոդվածների շարքն է։ Սա մի հրաշալի ներածություն է կոմպիլյատորներ կառուցելու գործնական կողմի վերաբերյալ։ Հեղինակը, սկսելով պարզ կառուցվածքներից, ստեղծում է «իսկական» կոմպիլյատոր՝ հընթացս մանրամասն մեկնաբանելով իր ամեն մի քայլը։ (Կա նաև ռուսերեն թարգմանությունը․ Д. Креншоу, <i>Пишем компилятор</i>։)<br />
<br/>Ծավալով փոքր, բայց տաղադավոր գրված գիրք է Niklaus Wirth֊ի «Compiler Construction»-ը։ Հանրահայտ գիտնականն ու մանկավարժը կարողացել է մոտ 200 էջերի մեջ տեղավորել կոմպիլյատորի կառուցման բոլոր հիմնական սկզբունքներն ու քայլերը և ընթերցողին մատուցել անփոխարինելի մի դասագիրք։ Գրքում Oberon ծրագրավորման լեզվով իրականացվում է նույն Oberon֊ի մի ենթաբազմության՝ Oberon-0֊ի կոմպիլյատորը, որը կոդ է գեներացվում գրքի իններորդ գլխու նկարագրված RISC վիրտուալ մեքենայի համար։ (Գիրքը մատչելի է անգլերենով, ռուսերենով և գերմաներենով։)<br />
<br/>Andrew Appel֊ի «Modern Compiler Implementation in C» գիրքը նույնպես ուզում եմ նշել որպես մի հաջող ու հետաքրքիր աշխատանք։ Այն բաժանված է երկու մասերի. առաջինը ներկայացնում է կոմպիլյատորի հիմնական բաղադրիչների իրականացումը, երկրորդում լրացուցիչ թեմանաեր են (աղբի հավաքում, պոլիմորֆիկ տիպեր և այլն)։ Գիրքը հրատարակվել է երեք լեզուների համար՝ C, Java և ML։<br />
<br/>Մի քիչ ավելի կոմպակտ աշխատանք է Torben Mogensen֊ի «Introduction to Compiler Design» գիրքը։ Սա նույնպես հարմար է որպես դասագիրք օգտագործելու համար։<br />
<br/>Արժե ծանոթանալ նաև Terrence Pratt֊ի և Marvin Zelkovitz-ի հեղինակած «Programming Languages: Design and Implementation» արդեն դասական դարձած գիրքը, որում ներկայացված են ծրագրավորման լեզուների<br />
<br/>Ծրագրավորման լեզուների իրականացմանը նվիրված գրքերի մասին խոսելիս, իհարկե, պետք է անպայման նշել Alfred Aho֊ի, Monica Lam֊ի, Ravi Sethi֊ի և Jeffrey Ullman֊ի հռչակավոր «Compilers: Principles, Techniques, & Tool» դասագիրքը։ Այն ընդգրկում է կոմպիլյատորների իրականացմանը վերաբերող բոլոր թեմաները՝ տեսական ու պրակտիկ հարուստ նյութով։ Իմ կարծիքով, սա այն գիրքն է, որը պետք է ինչ֊որ մի պահից դառնա ծրագրավորման լեզուներն ուսումնասիրող մասնագետի սեղանի գիրքը։<br />
<br/>Թերևս այսքանն այն մի քանի կարևորագույն գրքերի մասին, որոնցից, իմ կարծիքով, կարելի է և պետք է սկսել ծրագրավորման լեզուների իրականացման հետ շփումը։ Բնականաբար ցանկը կարելի է շարունակել (նույնիսկ կարելի է ճշգրտումներ անել՝ այս կամ այն գրքը մեկ ուրիշով փոխարինելով), բայց սա այն է, ինչ ես կարողացա այս պահին ամփոփել։ Իմ էլեկտրական գրադարանում այս պահին կան թեմային նվիրված մոտ 200 գիրք, դրանցից յուրաքանչյուրն իր առանձնահատկությունն ունի և արժանի է ուշադրության։<br />
<center><b>* * *</b></center>
<br />Իսկ ի՞նչ կարդալ ծրագրավորման լեզուների իրականացման մասին՝ <i>նրանց աշխատանքի սկզբունքներին ծանոթանալու համար, կոմպիլյատորները և ինտերպրետատորները որպես գործիք ավելի լավ օգտագործելու համար</i>։ Այս թեմայով ինձ դուր է գալիս Robert Sebesta֊ի «Concepts of Programming Languages» գիրքը (ծանոթ պետք է լինի բոլոր ուսանողներին), որում մանրամասնորեն վերլուծված են բազմաթիվ ծրագրավորման լեզուների զարգացումը, կիրառության ոլորտներն ու առանձնահատկությունները, ինչպես նաև բերված են հետաքրքի համեմատականներ։ Հետո կարելի է կարդալ (կամ աչքի անցկացնել) Alice Fischer֊ի և Frances Grodzinsky֊ի «The Anatomy of Programming Languages» գիրքը։ Այստեղ էլ քննարկվում են տարբեր լեզուների ներքին կառուցվածքը, տիպերի համակարգերի և ծրագրավորման պարադիգմների համեմատությունները։ Վերջերս հայտնաբերել եմ, բայց դեռ խորությամբ չեմ ծանոթացել Daniel Friedman֊ի և Mitchell Wand֊ի «Essentials of Programming Languages» գրքին (թեմաներն ու բերված օրինակները բավականին հետաքրքիր են)։armenbadalhttp://www.blogger.com/profile/01200997467308938068noreply@blogger.com3tag:blogger.com,1999:blog-118668552408252052.post-70176667745585135382015-12-12T13:46:00.000+04:002016-12-18T13:54:59.765+04:00Միակապ ցուցակի շրջելը ռեուրսիվ եղանակով<p>Մի քանի օր առաջ Լիլիթն ինձ առաջարկեց գրել <em>միակապ ցուցակը</em> շրջելու ֆունկցիան՝ օգտագործելով ռեկուրսիվ ալգորիթմ։ Առաջին բանը, որ միտքս եկավ՝ թե ինչպես կարելի է դա ան մի այնպիսի լեզվով, որտեղ ցուցակը ներդրված տիպ է, և արդեն առկա են ցուցակի հետ գործողություններ կատարող ֆունկցիաները։ Օրինակ, Scheme լեզվով գրված պրոցեդուրան կարող է ունենալ այսպիսի տեսք․</p>
<pre class="prettyprint lang-lisp">
(define (reverse-it li)
(define (reverse-it-rec l r)
(if (null? l)
r
(reverse-it-rec (cdr l) (cons (car l) r))))
(reverse-it-rec li '()))
</pre>
<p>Այստեղ <code>reverse-it</code> պրոցեդուրայի մարմնում սահմանված է <em>վերջին կանչի ռեկուրսիա</em> (<em>tail recursive</em>) ունեցող <code>reverse-it-rec</code> պրոցոդուրան, որում էլ հենց կտարվում է տրված ցուցակի շրջելը։ <code>reverse-it-rec</code>֊ն ուն երկու պարամետր՝ ցուցակի չշրջված մասը և արդեն շրջված մասը։ Պարզ է, որ <code>reverse-it</code>֊ում նրան կանչելիս առաջին արգումենտը պետք է լինի շրջվելիք ցուցակը, իսկ երկրորդը՝ դարարկ ցուցակ։ Եթե <code>l</code>֊ը դատարկ է, ապա համարվում է, որ <code>r</code>-ը արդեն շրջված ցուցակն է, և այն վերադարձվում է որպես արդյունք։ Հակառակ դեպքում <code>l</code>֊ի առաջին տարրը կցվում է <code>r</code>-ի սկզբից, և <code>reverse-it-rec</code> ի ռեկուրսիվ կանչը կիրառվում է <code>l</code>֊ի պոչի և այդ նոր <code>r</code>֊ի նկատմամբ։</p>
<center><b>* * *</b></center>
<p>Բայց Լիլիթն ուզում էր, որ ես սա գրեմ C++ լեզվով, որից ես շատ քիչ բան եմ հասկանում, և այդ պատճառով էլ որոշեցի գրել C լեզվով։ Սակայն այս դեպքում իրավիճակը բոլորովին այլ է․ C լեզվում չկան ո՛չ ներդրված ցուցակը, ո՛չ էլ դրա հետ աշխատող ֆունկցիաները։ Ես պետք է սկսեմ սկզբից՝ սահմանելով նախ՝ ցուցակը, ապա՝ այն շրջող ֆունկցիան։</p>
<p>Եվ այսպես, սահմանում եմ միակապ ցուցակի մեկ հանգույցը ներկայացնող <code>node</code> ստրուկտուրան։ Այն ունի երկու երկու դաշտ՝ մեկը <em>ինֆորմացիայի</em> համար, մյուսը՝ հաջորդ հանգույցին <em>կապելու</em>։ Պարզության համար ինֆորմացիայի տիպն ընտրել եմ <code>double</code>։</p>
<pre class="prettyprint lang-c">
strcut node {
double data;
struct node* next;
};
</pre>
<p>Հանգույցներ կառուցելու համար ինձ պետ է նաև <code>create_node</code> ֆունկցիան, այն ստանում է <code>double</code> թիվ և վերադարձնում է այդ թիվը պարունակող նոր ստեղծված հանգույցի ցուցիչը։</p>
<pre class="prettyprint lang-c">
struct node* create_node( double d )
{
struct node res = malloc(sizeof(struct node));
res->data = d; res->next = NULL;
return res;
}
</pre>
<p>Աշխատանիքի միջանկյալ ու վերջնական արդյունքները տեսնելու համար պետք է գալու նաև ցուցակն արտածող <code>print_list</code> ֆունկցիան։ Դա էլ սահմանեմ․</p>
<pre class="prettyprint lang-c">
void print_list_rec( struct node* list )
{
if( list == NULL ) return;
printf("%lf ", list->data);
print_list_rec( list->next );
}
void print_list( struct node* list )
{
printf("{ ");
print_list_rec( list );
printf("}\n");
}
</pre>
<p>Հիմա ամենահետաքրքիր պահն է։ Ես հանմանում եմ <code>reverse_it</code> և <code>reverse_it_rec</code> ֆունկցիաները՝ փորձելով վերարտադրել վերը բերված Scheme պրոցեդուրայի վարքը։</p>
<pre class="prettyprint lang-c">
struct node* reverse_it_rec( struct node* l, struct node* r )
{
if( l == NULL ) /* երբ ցուցակը դատարկ է */
return r; /* արդյունքը կապված է r ցուցիչին */
struct node* h = l; /* h֊ը ցուցակի գլուխն է */
l = l->next; ․ /* l֊ը հիմա ցուցակի պոչն է, դեռ չշրջված */
հ->next = r; /* սկզբնական l֊ի առաջին տարրը կապել r֊ին */
return reverse_it_rec( l, h );
}
</pre>
<p>Դե իսկ <code>reverse_it</code> ֆունկցաին պարզապես կանչելու է <code>reverse_it_rec</code>֊ը՝ առաջին արգումենտում տալով շրջվելիք ցուցակը, իսկ երկրորդում՝ <code>NULL</code>։</p>
<pre class="prettyprint lang-c">
struct node* revers_it( struct node* list )
{
return reverse_it_rec( list, NULL );
}
</pre>
<p>Այսքանը։ Հիմա կարող եմ վերը ներկայացված կոդը գրել ֆայլի մեջ, կցել <code>stdio.h</code> և <code>stdlib.h</code> ֆայլերը, օրինակ պատրաստել <code>main</code> ֆունկցիայում և տեսնել, թե ինչպես է աշխատում իմ գրած ֆունկցիան։</p>
<p>Օրինակը շատ պարզ է․ կառուցում եմ ցուցակի հինգ հանգույցներ՝ օգտագործելով <code>create_node</code> ֆունկցիան, ապա դրանք իրար եմ կապում <code>next</code> ցուցիչի օգնությամբ։</p>
<pre class="prettyprint lang-c">
struct node* n0 = create_node(1);
struct node* n1 = create_node(2); n0->next = n1;
struct node* n2 = create_node(3); n1->next = n2;
struct node* n3 = create_node(4); n2->next = n3;
struct node* n4 = create_node(5); n3->next = n4;
</pre>
<p>Ցուցակը տպում եմ, որպեսզի տեսնեմ տարրերի սկզբնական հաջորդականությունը։ Այնուհետև այն շրջում եմ <code>reverse_it</code> ֆուկցիայի օգնությամբ, և նորից տպում եմ ստացված ցուցակը։</p>
<pre class="prettyprint lang-c">
print_list(n0);
struct node* rl = reverse_list(n0);
print_list(rl);
</pre>
<p>Ահա արդյունքը․</p>
<pre>
{ 1.000000 2.000000 3.000000 4.000000 5.000000 }
{ 5.000000 4.000000 3.000000 2.000000 1.000000 }
</pre>
<p>Տեսնելու համար, թե ինչ տեսք ունեն <code>l</code> և <code>r</code> ցուցակները ռեկուրսիայի ամեն մի կանչի ժամանակ, կարելի է <code>reverse_it_rec</code> ֆունկցիայի սկզբում ավելացնել <code>print_list(l)</code> և <code>print_list(r)</code> արտահայտությունները։</p>armenbadalhttp://www.blogger.com/profile/01200997467308938068noreply@blogger.com0tag:blogger.com,1999:blog-118668552408252052.post-14114089942505172842015-10-02T14:39:00.000+04:002015-10-02T14:41:14.326+04:00Ստրուկտուրաներ TCL լեզվի համար
<div style="text-align:right"><i>Կուրսային աշխատանքի նախագիծ։</i></div>
<p>Այս գրառման մեջ ես ուզում եմ TCL լեզվի օրինակով ցույց տալ, թե ինչպես կարելի է ընդլայնել ծրագրավորվող ծրագրավորման լեզուն, և այն ընդլայնումն էլ ուզում եմ ցույց տալ ստրուկտուրաների օրինակով։ Գաղափարները ես փոխառել եմ Common Lisp լեզվից։</p>
<p>Գիտեմ (իհարկե, որոշ վերապահումներով), որ TCL լեզվում բացակայում են ստրուկտուրաների (գրառումների) հետ աշխատելու գործիքները, և TLC լեզվում առկա են ցուցակ և տող տիպերը և դրանց հետ աշխատելու ֆունկցիաները։ Ես պետք է «ստրուկտուրա» (struct, record) և «նմուշ» (instance) գաղափարներն արտապատկերեմ «ցուցակ» (list), միգուցե նաև «տող» (string) գաղափարներին։</p>
<p>Եթե, օրինակ, արդեն սահմանել եմ <code>person</code> (անձ) ստրուկտուրան, ապա 42 տարեկան Վչոյին նկարագրող նմուշը կարող է ունենալ հետևյալ տեսքը։</p>
<pre class="prettyprint lang-tcl">
{person name Վչո age 42}
</pre>
<p>Այստեղ երևում է, որ <code>person</code> ստրուկտուրայի նմուշը ներկայացված է մի ցուցակով, որի առաջին տարրը ստրուկտուրայի անունն է, իսկ հաջորդ տարրերը կազմում են սլոտ֊արժեք զույգերի հաջորդականություն։ Նմուշի այսպիսի ներկայացման դեպքում, կարծում եմ, արդեն դժվար չէ սահմանել այն գործիքները, որոնցով աշխատելու եմ ստրուկտուրաների ու դրանց նմուշների հետ։</p>
<p>Քանի որ TCL լեզվում ծրագրի կառուցման բլոկը (շինանյութը) պրոցեդուրան է, ապա ստրուկտուրաների և դրանց նմուշների հետ աշխատելու համար պետք է ունենալ ա) ստրուկտուրա սահմանող, բ) ստրուկտուրայի նմուշ ստեղծող, գ) ստրուկտուրայի դաշտերի (սլոտների) արժեքներ կարդացող և փոփոխող պրոցեդուրաներ։</p>
<p>Օգտագործելով <code>person</code> ստրուկտուրայի օրինակը, սկսեմ սահմանել այդ թվարկված պրոցեդուրաները։ Բայց, առաջ անցնելով ենթադրեմ, թե արդեն սահմանված է <code>struct</code> պրոցեդուրան, որը կատարման միջավայրում սահմանում է նոր ստրուկտուրա։ Դրա օգնությամբ սահմանեմ <code>person</code> ստրուկտուրան։</p>
<pre class="prettyprint lang-tcl">
struct person { name gender age }
</pre>
<p>Թող <code>create_person</code> պրոցեդուրան վերադարձնում է <code>person</code> ստրուկտուրայի չարժեքավորված նմուշ (կոնստրուկտոր պրոցեդուրա է)։</p>
<pre class="prettyprint lang-tcl">
proc create_person {} {
list person name {} age {}
}
</pre>
<p><strong>Վարժություն 1։</strong> Սահմանել <code>create_person</code> պրոցեդուրայի մի այլ տարբերակ, որն արգումենտում ստանում է սլոտների սկզբնական արժեքները և ստեղծում է <code>person</code> ստրուկտուրայի արժեքավորված նմուշ։</p>
<p>Այնուհետև, թող <code>person_name</code> պրոցեդուրան արգումենտում ստանում է <code>person</code> նմուշը և վերադարձնում է դրա <code>name</code> սլոտի արժեքը։</p>
<pre class="prettyprint lang-tcl">
proc person_name { inst } {
set ps [lsearch $inst name]
lindex $inst [expr {$ps + 1}]
}
</pre>
<p><code>person_name</code>֊ին ստիմետրիկ սահմանեմ նաև <code>person_name_set</code> պրոցեդուրան, որը նմուշի <code>name</code> սլոտին վերագրում է նոր արժեք։</p>
<pre class="prettyprint lang-tcl">
proc person_name_set { inst val } {
upvar $inst obj
set ps [lsearch $obj name]
lset obj [expr {$ps + 1}] $val
}
</pre>
<p><strong>Վարժություն 2։</strong> Ձևափոխել <code>person_name</code> և <code>person_name_set</code> պրոցեդուրաներն այնպես, որ այն ստուգի, թե արդյո՞ք <code>inst</code>֊ը <code>person</code>-ի նմուշ է։</p>
<p><strong>Վարժություն 3։</strong> Սահմանել նաև <code>age</code> սլոտի արժեքը գրող և կարդացող պրոցեդուրաները։</p>
<p>Հիմա վերադառնամ բուն ստրուկտուրան սահմանող <code>struct</code> պրոցեդուրային։ Արդեն պարզ է, որ <code>s0</code>, <code>s1</code>,... <code>sk</code> սլոտներն ունեցող <code>S</code> ստրուկտուրան սահմանել, նշանակում է կատարման միջավայր ներմուծել <code>create_S</code> կոնստրուկտորը, իսկ ամեն մի <code>si</code> սլոտի համար՝ <code>S_si</code> և <code>S_si_set</code> անունով պրոցեդուրաները։ Այլ կերպ ասած, ստրուկտուրաներ սահմանող <code>struct</code> պրոցեդուրան ամեն մի նոր ստրուկտուրայի համար պետք է սահմանի դրա կոնստրուկտոր և սլոտներին դիմող պրոցեեդուրաները, ինչպես նաև նմուշի տիպը հաստատող պրեդիկատ պրոցեդուրան։ Ահա այն․</p>
<pre class="prettyprint lang-tcl">
proc struct { name slots } {
set slpatt [list $name]
foreach sl $slots {
uplevel "proc ${name}_${sl} \{ inst \} \{
set ps \[lsearch \$inst $sl]
lindex \$inst \[expr \{\$ps + 1\}\] \}"
uplevel "proc ${name}_${sl}_set \{ inst val \} \{
upvar \$inst obj
set ps \[lsearch \$obj $sl\]
lset obj \[expr \{\$ps + 1\}\] \$val \}"
lappend slpatt $sl {}
}
uplevel "proc create_$name \{\} \{ list $slpatt \}"
uplevel "proc is_$name \{ inst \} \{
string equal $name \[lindex \$inst 0 0\] \}"
}
</pre>
<p>Չնայած նրա մի քիչ խճճված տեսքին, տրամաբանությունը բավականին պարզ է։ Այն սահմանում է վերը պահանջված պրոցեդուրաները։</p>
<p><strong>Վարժություն 4։</strong> Ստրուկտուրաների սահմանման, դրանց նմուշների ու սլոտների հետ աշխատող պրոցեդուրաները լրացնել (ընդլայնել) սխալների ստուգման մեխանիզմով։</p>
armenbadalhttp://www.blogger.com/profile/01200997467308938068noreply@blogger.com0tag:blogger.com,1999:blog-118668552408252052.post-90990396447606050952015-09-12T16:48:00.000+04:002015-09-12T16:48:32.454+04:00Ծրագրավորման լեզուն ընդլայնելու մասին<p>Վերջերս ես հանդիպեցի թե ինչպես են C++ լեզվում ներմուծել հայերեն ծառայողական բառեր։ Դա արվել էր, բնականաբար, նախապրոցեսորի (preprocessor) օգնությամբ։ Պարզապես ամեն մի բառի համար սահմանվել էր նրա համարժեք հայերեն տարբերակը, որն էլ նախամշակման ժամանակ փոխարինվում էր լեզվի իսկական ծառայողական բառով։ Դա ուներ մոտավորապես ուներ այսպիսի տեսք․</p>
<pre class="prettyprint lang-c++">
#define եթե if
#define այլապես else
#define մինչ while
#define վերադարձնել return
#define ամբողջ int
</pre>
<p>Եվ այս սահմանումներով, օրինակ, էվկլիդեսի ալգորիթմը կարելի է գրառել հետևյալ տեսքով․</p>
<pre class="prettyprint lang-c++">
ամբողջ euclid( ամբողջ n, ամբողջ m ) {
մինչ( n != m ) {
եթե( n > m )
n %= m;
այլապես
m %= n;
}
վերադարձնել n + m;
}
</pre>
<p>Երբ հայերեն ծառայողական բառերի սահմանումներն ու դրանց օգտագործմամբ գրված ծրագիրը գրենք <code>*.cpp</code> ֆայլում, և <code>clang++</code> կոմպիլյատորի նախապրոցեսորին խնդրենք մշակել այդ ֆայլը․</p>
<pre class="prettyprint lang-bash">
$ clang -E ex0.cpp
</pre>
<p>Ապա կստանանք «մաքուր» C++ լեզվով գրված կոդ։</p>
<pre class="prettyprint lang-c++">
int euclid( int n, int m ) {
while( n != m ) {
if( n > m )
n %= m;
else
m %= n;
}
return n + m;
}
</pre>
<p>Պարզ է, որ եթե ուզում ենք ծրագրավորել հայերեն բառերով, ապա C++ լեզուն ավելի լավ տարբերակ չի առաջարկում․ կոմպիլյացիայից առաջ ծրագրի տեքստում փոփոխություններ կատարելու միակ հնարավոր եղանակը նախապրոցեսորի օգտագործումն է։ Պարզ է նաև, որ չենք կարող լեզվում նոր ղեկավարող կառուցվածքներ ավելացնել։ Օրինակ, ինչպե՞ս ավելացնել <code>repeat</code> տիպի կրկնման գործողություն։</p>
<pre class="prettyprint lang-c++">
repeat( 10 ) {
std::cout << "Ողջո՜ւյն։\n";
}
</pre>
<center><strong>* * *</strong></center>
<p>Լրիվ այլ պատկեր է այն լեզուներում, որոնց ընդունված է ասել «ծրագրավորվող ծրագրավորման լեզուներ»։ Այդ դասի վառ ներկայացուցիչներ են Lisp ընտանիքի լեզուները՝ մակրոսների սահմանման իրենց հնարավորություններով։ «Ծրագրավորվող» լինելու հատկությամբ է օժտված նաև Tcl լեզուն, որում վերը բերված <code>repeat</code> կառուցվածքը սահմանելը ամենևին էլ բարդ բան չէ։ Ահա այն․</p>
<pre class="prettyprint lang-tcl">
proc repeat { num body } {
set result {}
while { $num != 0 } {
incr num -1
set result [uplevel $body]
}
return $result
}
</pre>
<p>Նույնիսկ սրա հայերեն տարբերակի սահմանումն է բավականին հետաքրքիր։</p>
<pre class="prettyprint lang-tcl">
proc կրկնել { count անգամ body } {
if { ${անգամ} ne {անգամ} } then {
error "Syntax error."
}
set result {}
while { $count != 0 } {
incr count -1
set result [uplevel $body]
}
return $result
}
</pre>
<p>Այստեղ սահմանված է <code>կրկնել</code> անունով պրոցեդուրան, որը ունի երեք պարամետր։ Պարամետրերից առաջինը կրկնությունների քանակն է, երկրորդը կատարում է <code>անգամ</code> ծառայողական բառի դերը, իսկ երրորդը կրկնման հրամանի մարմինն է։ <code>կրկնել</code> պրոցեդուրայի մարմնում նախ ստուգում եմ, որ երկրորդ պարամետրի արժեքն անպայման լինի <code>անգամ</code> տողը։ Ահա նաև կիրառությունը․</p>
<pre class="prettyprint lang-tcl">
կրկնել 5 անգամ {
puts Hello!
}
</pre>
<p>Բայց ինչպե՞ս է սա աշխատում։ Չէ՞ որ Tcl լեզվի <code>proc</code> հրամանը պարզապես սահմանում է նոր ֆունկցիա, և, բոլորս էլ լավ գիտենք, որ ֆունկցիայի կանչի ժամանակ արգումենտները հաշվարկվում են ֆունկցիային փոխանցելուց առաջ։ Եվ, տվյալ դեպքում, «<code>{ puts {Ողջո՜ւյն։} }</code>» արգումենտի արժեքը պետք է հաշվարկվեր և պետք է մի անգամ արտածվեր «Ողջո՜ւյն։» տեքստը։</p>
<p>Բացատրությունը Tcl լեզվի միակ տիպի՝ <em>տողի</em> հաշվարկման կանոնների մեջ է։ Եթե տողը պարփակված է <code>{</code> և <code>}</code> ձևավոր փակագծերում, ապա այն փոխանցվում է այնպես, ինչպես կա (as-is), ոչ մի հաշվարկ չի կատարվում, ոչ մի ձևափոխություն չի կատարվում։ <code>{</code> և <code>}</code> փակագծերը տողը «պաշտպանում» են հաշվարկումից։ Եվ այդ պաշտպանությունը հնարավորություն է տալիս տողը դիտարկել որպես «ղեկավարող կառուցվածքի» բլոկ։</p>
<p>Օգտագործելով Tcl լեզվում պրոցեդուրաներ սահմանելու <code>proc</code> հրամանը և կոդի բլոկը ստեկի մեկ այլ կադրում հաշվարկելու <code>uplevel</code> հրամանը, կարելի է լեզուն ընդլայնել (համալրել) հայերեն ծառայողական բառեր ունեցող ղեկավարող կառուցվածքներով։ Եվ քանի որ նոր կառուցվածքները սահմանվելու են որպես պրոցեդուրաներ, ապա դա հնարավորություն է տալիս կատարել շարահյուսական և իմաստաբանական ստուգումներ։ Դրա օրինակ է վերը սահմանված <code>կրկնել</code> պրոցեդուրայում <code>անգամ</code> բառի առկայության ստուգումը։</p>
<center><strong>* * *</strong></center>
<p>Լեզուն ընդլայնելու (ասում են նաև՝ լեզվի մեջ նոր լեզու սահմանելու) էլ ավելի լայն ու հետաքրքիր հնարավորություններ են ընձեռնում Lisp ընտանիքի Common Lisp և Scheme լեզուները։ Բայց, ինչպես ասում էր Ֆ․ Դոստոևսկին, դա արդեն ուրիշ պատմության նյութ է։</p>
armenbadalhttp://www.blogger.com/profile/01200997467308938068noreply@blogger.com0tag:blogger.com,1999:blog-118668552408252052.post-7528134166800708292015-08-16T10:26:00.000+04:002015-08-16T10:26:35.492+04:00Ծրագրերի սկզբնային տեքստերի գեղեցկության մասին<p>Կարելի՞ է արդյոք կոմպյուտերային ծրագրի կոդի մասին խոսելիս ասել, որ այն <i>գեղեցիկ</i> է։ Արդյո՞ք ծրագրի տեքստը կարելի է բնութագրել գեղագիտական հատկանիշներով։ Այս հարցին ես առաջին անգամ առնչվեցի, որբ բակալավրիատում սովորելու առաջին կուրսում, ծրագրավորման գործնական պարապմունքի ժամին դասախոսը՝ Արմեն Անդրեասյանը, ցույց տվեց գրատախտակին գրված ծրագիրը ու ասաց. «Գեղեցիկ է, չէ՞»։ Ինչքան հիշում եմ, թեև հնարավոր է, որ սխալվեմ, այդ ծրագիրը Էվկլիդեսի ալգորիթմն էր՝ գրված C++ լեզվով.</p>
<pre class="prettyprint lang-c++">
int gcd( int a, int b )
{
while( a != b )
if( a > b )
a -= b;
else
b -= a;
return a;
}
</pre>
<p>Այն ժամանակ մենք նույնիսկ քմծիծաղեցինք, թե ծրագրի գեղեցիկը ո՞րն է, սովորական ծրագիր է։ Հետագայում, սովորելու և աշխատելու տարիներին, երբ գրեցի շատ ու շատ ծրագրեր, երբ նաև կարդացի ուրիշների գրած շատ ու շատ ծրագրեր, համարյա միշտ ինքս ինձ հարցնում էի, թե գեղեցի՞կ է արդյոք իմ գրածն ու կարդացածը։</p>
<p>Էվկլիդեսի ալգորիթմին վերադառնալով ուզում եմ ասել, որ այն իսկապես գեղեցիկե։ Առավել գեղեցիկ է նրա ռեկուրսիվ իրականացումը, որ ահա գրել եմ Oberon լեզվով․</p>
<pre class="prettyprint lang-pascal">
PROCEDURE Gcd( u, v : INTEGER ) : INTEGER;
VAR
re : INTEGER;
BEGIN
IF v = 0 THEN
re := u
ELSE
re := Gcd(v, u MOD v)
END;
RETURN re END Gcd;
</pre>
<p>Չէ, իսկապես գեղեցիկ է։ Այս ալգորիթմի համար նույնիսկ կարևոր չէ, թե այն ինչ լեզվով է գրված։ Նրա հիմքում գեղեցիկ մաթեմատիկական միտքն է, այն գեղեցիկ է ստացվելու, երևի թե ցանկացած ծրագրավորման լեզվով գրելիս։ Ճիշտ ինչպես Շիլլերի տողերը, որ միատեսակ գեղեցիկ են ինձ ծանոթ բոլոր լեզուներով։ Բնագիրը.
<pre><i> Ihr Matten lebt wohl,
Ihr sonnigen Weiden!
Der Senn muss scheiden,
Der Sommer ist hin.
Wir fahren zu Berg, wir kommen wieder,
Wenn der Kuckuck ruft, wenn erwachen die Lieder,
Wenn mit Blumen die Erde sich kleidet neu,
Wenn die Brünnlein fliessen im lieblichen Mai
Ihr Matten lebt wohl,
Ihr sonnigen Weiden!
Der Senne muss scheiden,
Der Sommer ist hin.</i>
</pre></p>
<p>Հայերեն՝ Հ. Թումանյանի թարգմանությամբ.
<pre><i>Մնաք բարով, դո՛ւք, արոտնե՛ր սիրուն,
Ամառն անց կացավ, հոտն իջնում է տուն։
Մենք ետ կըգանք ձեզ նորեկ գարունքին,
Երբ զարթնեն ուրախ երգերը կըրկին,
Երբ որ սարերը զուգվեն կանաչով,
Երբ որ ջըրերը վազեն կարկաչով։
Մնաք բարով, դո՛ւք, արոտնե՛ր սիրուն,
Ամառն անց կացավ, հոտն իջնում է տուն։</i>
</pre></p>
<p>Ռուսերեն՝ Ն. Սլավյատինսկու թարգմանությամբ.
<pre><i> Прощайте, луга,
Багряные зори!
Разлука нам — горе.
Ах, лето прошло!
Пора нам в долины... Увидимся снова,
Когда все очнется от сна ледяного
И голос кукушки в лесу зазвучит,
Цветы запестреют, родник зажурчит.
Прощайте, луга,
Багряные зори!
Разлука нам — горе.
Ах, лето прошло!</i>
</pre></p>
<p>Անգլերեն՝ Թ. Մարտինի թարգմանությամբ.
<pre><i> Farewell, ye green meadows,
Farewell, sunny shore,
The herdsman must leave you,
The summer is o'er.
We go to the hills, but you'll see us again,
When the cuckoo is calling, and wood-notes are gay,
When flowerets are blooming in dingle and plain,
And the brooks sparkle up in the sunshine of May.
Farewell, ye green meadows,
Farewell, sunny shore,
The herdsman must leave you,
The summer is o'er.</i>
</pre></p>
<p>Հիմա արդեն, ծրագրային կոդերի հետ աշխատանքի ավելի քան տաս տարիների փորձն ամփոփելով, կարող եմ ինքս ինձ համար պնդել, որ այո՛, կոմպյուտերային ծրագրի տեքստը (սկզբնային տեքստ, source code) նույնպես կարող է գեղեցիկ լինել։ Ավելին, հուսալի ու արդյունավետ կարող են աշխատել միայն այնպիսի ծրագրերը, որոնք գեղեցիկ տեքստ ունեն։ Համարյա ինչպես ավիացիայում. ասում են, որ լավ է թռչում միայն գեղեցիկ ինքնաթիռը։ Օրինակ, МиГ-29 կործանիչը։ Ես կարող եմ ժամերով նայել այս ինքնաթիռի թռիչքին։ Այն թռչում է ինչպես բալետի պարուհին է պարում բեմի վրա, ինչպես գեղասահորդը սահում է սառույցին։ Եվ երբ նայում եմ այդ ինքնաթիռի գծապատկերին, տեսնում եմ նույն պարուհուն կամ գեղասահորդին:</p>
<div class="separator" style="clear: both; text-align: center;"><a href="http://1.bp.blogspot.com/-jQ7ZXtZ0Z0g/Vc-JPakwEMI/AAAAAAAAAgY/vrB03dtphqM/s1600/mig29-9-12-picture.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://1.bp.blogspot.com/-jQ7ZXtZ0Z0g/Vc-JPakwEMI/AAAAAAAAAgY/vrB03dtphqM/s400/mig29-9-12-picture.png" /></a></div>
<p>Նույնպիսի համեմատություն կարող եմ անել Դ. Կնուտի (D. Knuth) TeX և METAFONT ծրագրերի համար։ Այդ երկու ծրագրերն էլ գրված են Literate Programming մեթոդով. կարծես գեղարվեստական ստեղծագործություն լինեն։ Դե, իսկ ո՞ւմ չէ հայտնի TeX-ով պատրաստված տեքստերի և METAFONT-ով պատրաստված տպատառերի գեղեցկությունը։</p>
<p>Կան նաև տգեղ ծրագրեր։ Բառացիորեն վերջերս իմ աշխատանքում պետք եղավ C++ լեզվով գրել ծրագրի մի հատված, որտեղ օբյեկտները բնութագրող տողերի զույգերից պետք էր կառուցել օբյեկտների բառարան (map): Տողերի զույգի առաջին տարրը բառարանի բանալին է, իսկ երկրորդ տարրից պետք է կառուցել բանալուն համապատասխանեցված արժեքը։ Ծրագրի բնույթն այնպիսին է, որ տողերի զույգերը ժամանակի ընթացքում ավելանալու են։ Սկզբում, երբ զույգերը դեռ քիչ էին, գրել էի մի այսպիսի տեքստ (պարզեցված տարբերակով, իհարկե).</p>
<pre class="prettyprint lang-c++">
std::vector<std::string> keys;
std::map<std::string,Descriptor*> dict;
keys.push_back("k0");
dict["k0"] = new Descriptor("v0");
keys.push_back("k1");
dict["k1"] = new Descriptor("v1");
keys.push_back("k2");
dict["k2"] = new Descriptor("v2");
</pre>
<p>Չնայած, որ բնութագրող տողերի փոքր քանակի համար սա ընդունելի է, այնուամենայնիվ, ես համարում եմ, որ այս կոդը տգեղ է։ Հենց թեկուզ այն պատճառով, որ տվյալները «կորել» են լեզվի արտահայտությունների մեջ։</p>
<p>Հետո, երբ բնութագրիչների քանակներն ավելանան, ես keys և dict կոնտեյներները լրացնելու համար կոդը ձևափոխեցի հետևյալ տեսքին.</p>
<pre class="prettyprint lang-c++">
using pair_t = std::pair<std::string,std::string>;
std::list<pair_t> cpairs = {
{ "k0", "v0" },
{ "k1", "v1" },
{ "k2", "v2" },
// ....
{ "k12", "v12" }
};
auto maker_f = [&]( pair_t e ) {
keys.push_back(e.first);
dict[e.first] = new descriptor(e.second);
};
std::vector<std::string> keys;
std::map<std::string, Descriptor*> dict;
std::for_each( cpairs.begin(), cpairs.end(), maker_f );
</pre>
<p>Մի քիչ երկար է, բայց այս դեպքում, եթե նոր տվյալներ ավելացնեմ, ապա դրանք ավելացնելու եմ միայն cpairs ցուցակում, իսկ տվյալների մշակման մասն արդեն անփոփոխ է մնալու։ Պետք է նշել, որ այս գեղեցիկ տեքստը ես ստացել եմ C++11 ստանդարտում ավելացված հնարավորությունների շնորհիվ։ Եթե ես այս կոդը գրեի, C++98 ստանդարտի հնարավորություններով, ապա ստանալու էի մի կոդ, որին ես գեղեցիկ ասել չեմ կարող.</p>
<pre class="prettyprint lang-c++">
typedef std::pair<std::string,std::string> pair_t;
std::list<pair_t> cpairs;
cpairs.push_back( std::make_pair( "k0", "v0" ) );
cpairs.push_back( std::make_pair( "k1", "v1" ) );
cpairs.push_back( std::make_pair( "k2", "v2" ) );
// ....
cpairs.push_back( std::make_pair( "k12", "v12" ) );
std::vector<std::string> names;
std::map<std::string,Descriptor*> dict;
for( std::list<pair_t>::iterator it = cpairs.begin(); it != cpairs.end(); ++it ) {
names.push_back( it->first );
dict[it->first] = new Descriptor(it->second);
}
</pre>
<p>Ցանկացած ծրագրում կարելի է գտնել այսպիսի օրինակներ։ Ցանկացած այսպիսի օրինակի գեղեցկության կամ տգեղության մասին կարելի է վիճել։ Բայց ինձ համար մի բան հաստատ է. ծրագրավորումը կարելի է համարել կիրառական արվեստի մի ճյուղ։ Եվ տեղին է այդ արվեստի նմուշների համար օգտագործել <i>գեղեցիկ</i> և <i>տգեղ</i> բնութագրումները։ Իսկ ծրագրավորողներին ու թեսթավորողներին, այն մարդկանց, ովքեր իրենք առօրյա աշխատանքում գրում ու կարդում են հարյուրավոր ու հազարավոր տողերով ծրագրային տեքստ, մնում է այդ գործում գտնել ու գնահատել <i>գեղեցիկը</i>։</p>
armenbadalhttp://www.blogger.com/profile/01200997467308938068noreply@blogger.com0tag:blogger.com,1999:blog-118668552408252052.post-57490296959052080902015-08-01T12:39:00.000+04:002015-08-01T12:39:57.908+04:00Տեքստի հավասարեցում ըստ էջի լայնության<p>«Этюды для программистов» (Чарльз Уэзерелл, ― «Etudes for Programmers», Charles Wetherell) գրքի չորրորդ էտյուդն առաջարկում է գրել տեքստի ֆորմատավորման ծրագիր, որի պահանջներից մեկը տողի բառերի արանքներում բացատներ ավելացնելով տեքստը ըստ էջի լայնության հավասարացնելն է։ Տեքստի ֆորմատավորման այս մասնակի խնդիրն է նկարագրված նաև, օրինակ, «100 задач по программированию» գրքի (հեղինակներ՝ Дагене В. А., Григас Г. К., Аугутис К. Ф.) 78-րդ առաջադրանքում, որ կոչվում է «Տեքստի տեխնիկական խմբագրում»։</p>
<p>Մի կողմից կարող է այս խնդիրը, գործնական տեսակետից, հնացած թվալ, մյուս կողմից էլ, սակայն, ժամանակակից տեքստային խմբագրիչներում և տեքստային պրոցեսորներում նույնպես կիրառվում են տեքստի հավասարեցման գործողություններ։ Օրինակ, LibreOffice Writer ծրագիրը հավասարեցումը կատարում է ոչ թե բացատների քանակն ավելացնելով, այլ դրանց չափերն ավելացնելով։ Կամ, TeX տեքստային պրոցեսորում ընդհանրապես բացակայում է բացատ նիշը, իսկ հավասարեցման համար բառերի արանքները լցվում են «սոսինձ» կոչվող ազատ տարածությամբ։</p>
<p>Խնդրի լուծման ընթացքը կարող է լինել այսպիսին (որ նույնպես կարդացել եմ ինչ-որ գրքում, արդեն չեմ հիշում, թե՝ որ). ա) տրված տեքստը տրոհել առավելագույնը <code>n</code> երկարությամբ տողերի՝ չթույլատրելով բառի տրոհում, բ) եթե արդյունքում տողի երկարությունն ավելի կարճ է ստացվում, քան <code>n</code> թիվը, ապա պատահական բառերի արանքներում ավելացնել այնքան բացատներ, որ տողի երկարությունը դառնա <code>n</code>-ի հավասար։ Հավասարեցման գործողությունը պետք չէ կիրառել վերջին տողի նկատմամբ։</p>
<p>Այս գրառման մեջ ես պատմում եմ, թե ինչպես եմ C լեզվով ծրագրավորել տեքստն ըստ լայնության հավասարեցման գործողությունը։ Ստորև բերված ծրագրում ես դիտարկում եմ տեքստի հետ կապված մի քանի հասկացություններ. <em>բառ</em>, <em>տող</em> և <em>պարագրաֆ</em>։ Տվյալների կառուցվածքների տեսակետից տեքստը պարագրաֆների հաջորդականություն է։ Պարագրաֆը տողերի կապակցված ցուցակ է։ Տողը բառերի կապակցված ցուցակ է։</p>
<pre class="prettyprint lang-c">
#include <ctype.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <stdbool.h>
#include <time.h>
</pre>
<p>Տեքստի մեկ բառը ներկայացնելու համար նախատեսել եմ <code>word</code> ստրուկտուրան։ Սրա <code>w</code> դաշտը բառի տեքստն է, <code>l</code> դաշտը բառի երկարությունն է, <code>s</code> դաշտը բառին հաջորդող բացատների քանակն է, իսկ <code>n</code> դաշտը հաջորդ բառի ցուցիչն է։</p>
<pre class="prettyprint lang-c">/* տեքստի բառի ներկայացումը */
struct word {
char* w; /* պարունակությունը */
size_t l; /* երկարությունը */
size_t s; /* հաջորդող բացատների քանակը */
struct word* n; /* հաջորդ բառի ցուցիչը */
};
</pre>
<p>Իմ առաջին խնդիրն է <code>const char* text</code> ցուցիչով տրված տողի սկզբից առանձնացնել <code>len</code> երկարությամբ հատված, և կազմել այդ հատվածի բառերի կապակցված ցուցակը։ <code>split_to_words</code> ֆունկցիան ստանում է տողի ցուցիչը, դիտարկվող հատվածի երկարությունը և վերադարձնում է կառուցված ցուցակի առաջին տարրի ցուցիչը։ (Բայց քանի որ տողը բառերի տրոհելու ընթացքում բառերի ցուցակը կառուցվում է գլխիվայր, առաջին բառի ցուցիչը վերադարձնելուց առաջ նախ պետք է շրջել կառուցված ցուցակը։) Բոլոր լրացուցիչ բացատրությունները գրված են ֆունկցիայի մարմնում՝ C լեզվի մեկնաբանությունների տեսքով։</p>
<pre class="prettyprint lang-c">
/* Տրոհել տեքստը և կառուցել բառերի կապակցված ցուցակ։
Վերադարձնում է առաջին բառի ցուցիչը։ */
struct word* split_to_words( const char* text, size_t len )
{
/* բառերի ցուցակը */
struct word* res = NULL;
/* տրոհել տողը */
const char* end = text + len; /* դիտարկվող հատվածի վերջը */
/* քանի դեռ text ցուցիչը չի հասել հատվածի վերջին */
while( text != end ) {
/* անցնել բացատների վրայով */
while( isspace(*text) ) ++text;
/* text-ը ցույց է տալիս բառի սկիզբը,
p ցուցիչը որոնում է բառի վերջը */
const char* p = text;
/* քանի դեռ բացատ չէ և տողն ավարտող 0 նիշը չէ,
առաջ տանել p-ն */
while( !isspace(*p) && *p != '\0' ) ++p;
/* բառի ստրուկտուրայի նմուշի համար վերցնել հիշողություն */
struct word* wo = malloc(sizeof(struct word));
/* բառի չափը նրա վերջն ու սկիզբը ցույց տվող ցուցիչների
դիրքերի տարբերությունն է */
wo->l = p - text;
/* հիշղություն վերցնել բառի պարունակության համար */
wo->w = malloc(1 + wo->l);
/* պատճենել գտված բառը ստրուկտուրայի մեջ */
strncpy(wo->w, text, wo->l);
/* բառի վերջում ավելացնել 0 նիշը */
wo->w[wo->l] = '\0';
/* բառին հաջորդում է 1 բացատ */
wo->s = 1;
/* նոր ստեղծվող բառը կցել ցուցակի սկզբից */
wo->n = res;
/* ցուցակի սկիզբ համարել նոր ավելացրած բառը */
res = wo;
/* անցնել հաջորդ բառի որոնմանը */
text = p;
}
/* վերջին բառին հաջորդող բացատների քանակը նշել զրո */
res->s = 0;
/* շրջել բառերի միակապ ցուցակը */
struct word* pp = NULL;
struct word* nn = res->n;
while( nn != NULL ) {
res->n = pp;
pp = res;
res = nn;
nn = nn->n;
}
res->n = pp;
/* վերադարձնել կառուցված ցուցակը */
return res;
}
</pre>
<p>Տեքստի մեկ տողը ներկայացնելու համար սահմանել եմ <code>line</code> ստրուկտուրան։ Սրա <code>w</code> դաշտը կապված է տվյալ տողի բառերի ցուցակի առաջին տարրին, <code>c</code> դաշտում պահվում է տողի բառերի քանակը, <code>l</code> դաշտը ցույց է տալիս տողի երկարությունը, իսկ <code>n</code> դաշտը հաջորդ տողի ցուցիչն է։</p>
<pre class="prettyprint lang-c">
/* տեքստի տողի ներկայացումը */
struct line {
struct word* w; /* բառերի ցուցակ */
size_t c; /* բառերի քանակ */
size_t l; /* տողի երկարություն */
struct line* n; /* հաջորդ տողի ցուցիչ */
};
</pre>
<p>Տեքստի տողը, ինչպես վերն ասացի՝ բառերի կապակցված ցուցակ է, կառուցվում է <code>create_line</code> ֆունկցիայով։ Սա ստանում է տեքստի ցուցիչը և մեկ տողի առավելագույն երկարությունը։</p>
<pre class="prettyprint lang-c">
/* Կառուցել տրված երկորությամբ տող */
struct line* create_line( const char* str, size_t len )
{
/* կառուցել բառերի ցուցակը */
struct line* res = malloc(sizeof(struct line));
res->w = split_to_words( str, len );
/* բառերի քանակն ու տողի երկարությունը դեռ զրո են */
res->c = 0; res->l = 0;
/* սկսել առաջին բառից */
struct word* i = res->w;
/* քանի դեռ բառերը չեն վերջացել */
while( i != NULL ) {
/* ավելացնել բառերի հաշվիչը */
++res->c;
/* հաշվել տողի ընթացիկ երկարությունը */
res->l += i->l + i->s;
/* անցնել հաջորդ բառին */
i = i->n;
}
/* հաջորդ տողի ցուցիչը դեռ դատարկ է */
res->n = NULL;
/* վերադարձնել կառուցված օբյեկտը */
return res;
}
</pre>
<p>Քանի որ տողերի ցուցակի կառուցման համար էլ է օգտագործվում դինամիկ առանձնացված հիշողություն, պետք է նախատեսել նաև այդ հիշողությունը խնամքով ազատելու և համակարգին վերադարձնելու միջոցը։ Դա արվել է <code>destroy_words</code> ֆունկցիայով։</p>
<pre class="prettyprint lang-c">
/* քանդել տողերի ցուցակը */
void destroy_words( struct word* l )
{
/* եթե ցուցակը դատարկ չէ */
if( l != NULL ) {
/* ռեկուրսիվ կանչ ցուցակի պոչի համար */
destroy_words( l->n );
/* ազատել բառի տեեքստի տեղը */
free(l->w);
/* ազատել բառի ստրուկտուրայի տեղը */
free(l);
}
}
</pre>
<p>Տողերի ցուցակ կազմելու համար պետք է <code>append_line</code> ֆունկցիայով տրված տողի <code>n</code> ցուցիչին կապել ևս մի տող։ Սա մի հասարակ ռեկուրսիվ ֆունկցիա է։</p>
<pre class="prettyprint lang-c">
/* տողերի ցուցակի պոչից կցել ևս մի տող */
void append_line( struct line** dest, struct line* src )
{
if( *dest == NULL )
*dest = src;
else
append_line( &((*dest)->n), src );
}
</pre>
<p>Պարագրաֆը տողերի ցուցակ է (տողն էլ իր հերթին՝ բառերի ցուցակ է)։ <code>create_paragraph</code> ֆունկցիան ստանում է նախնական տողի ցուցիչը և ֆորմատավորվող տեքստի նպատակային լայնությունը։ Ինչպես վերևում՝ լրացուցիչ բացատրությունները ծրագրի տեքստում են։</p>
<pre class="prettyprint lang-c">
/* տրված տեքստից կառուցել տրված երկարությունը չգերազանցող տողերի ցուցակ */
struct line* create_paragraph( const char* text, size_t width )
{
/* արդյունքի ցուցիչը */
struct line* res = NULL;
/* դիտարկվողղ տողի սկիզբն ու վերջը ցույց տվող ինդեքսներ */
size_t begin = 0, end = strlen(text);
/* քանի դեռ տեքստի վերջը չէ */
while( begin < end ) {
/* հաշվել հերթական հատվածի վերջը */
size_t pos = begin + width;
/* ճշտվում է վերջին հատվածի եզրը */
if( pos > end ) pos = end;
/* հատվածի աջ եզրից հետ գալ՝ կիսատ բառը չվերցնելու համար */
while( !isspace(text[pos]) && text[pos] != '\0' ) --pos;
/* կառուցել նոր տող և կցել տողերի ցուցակի պոչից */
append_line( &res, create_line( text + begin, pos - begin ) );
/* անցնել տեքստի հաջորդ հատվածին */
begin = pos;
}
/* վերադարձնել պարագրաֆների ցուցակը */
return res;
}
</pre>
<p>Պարագրաֆի համար առանձնացված հիշողության դինամիկ տիրույթը համակարգին է վերադարձվում <code>destroy_lines</code> ֆունկցիայի օգնությամբ։</p>
<pre class="prettyprint lang-c">
/* ազատել պարագրաֆի զբաղեցրած հիշողությունը */
void destroy_lines( struct line* p )
{
/* եթե տողերի ցուցակը դատարկ չէ */
if( p != NULL ) {
/* ռեկուրսիվ կանչ պոչի համար */
destroy_lines(p->n);
/* քանդել բառերի ցուցակը */
destroy_words(p->w);
/* ազատել տողի ստրուկտուրայի տեղը */
free(p);
}
}
</pre>
<p>Պարագրաֆի տողերը արտածման ստանդարտ հոսքին են դուրս բերվում <code>print_lines</code> ֆունկցիայով։ Այն նախ տպում է բառը, իսկ հետո՝ դրան հաջորդող բացատաները՝ հարկավոր քանակով։ Արտածվելիք բացատների քանակը պահվում է <code>word</code> ստրուկտուրայի <code>s</code> դաշտում։ <code>print_lines</code> ֆունկցիայում սահմանված <code>spaces</code> ստատիկ հաստատունը պարունակում է բառից հետո արտածվող բացատների ենթադրվող առավելագույն երկարությամբ տողը, իսկ <code>scount</code> փոփոխականը՝ այդ տողի երկարությունը։ <code>k->w</code> բառն արտածելուց հետո պարզապես պետք է արտածել նաև <code>spaces</code> տողի վերջին <code>k->s</code> հատվածը։</p>
<pre class="prettyprint lang-c">
/* արտածել տողերը */
void print_lines( struct line* lines )
{
/* բացատների տող, և բացատների քանակ */
static const char* spaces = " ";
static const size_t scount = 20;
/* քանի դեռ ցուցակի վերջը չէ */
while( lines != NULL ) {
/* k-ն ցուցակի աչաջին բառն է */
struct word* k = lines->w;
/* քանի դեռ k-ն չի հասել բառերի ցուցակի վերջին */
while( k != NULL ) {
/* արտածել բառը դրան հաջորդող բացատները */
printf( "%s%s", k->w, spaces + scount - k->s );
/* անցնել հաջորդ բառին */
k = k->n;
}
/* արտածել նոր տողին անցնելու նիշը */
putchar('\n');
/* անցնել պարագրաֆի հաջորդ տողին */
lines = lines->n;
}
}
</pre>
<p>Տողի հավասարեցման մարտավարությունը հետևյալն է․ քանի դեռ հացասարեցվող տողի երկարությունը, որը ցույց է տալիս <code>line</code> ստրուկտուրայի <code>l</code> դաշտը, փոքր է պահաջվածից, տողում ընտրել պատահական մի բառ (բացի վերջինից) և դրան հաջորդող բացատների քանակն ավելացնել մեկով։</p>
<p>Տողի <code>n</code>-րդ բառի ցուցիչը վերցնելու համար է նախատեսված <code>nth</code> ֆունկցիան։ Եթե տողում բառերի քանակը ավելի քիչ է, քան տրված <code>n</code> թիվը, ապա, բնականաբար, վերադարձվում է <code>NULL</code>։</p>
<pre class="prettyprint lang-c">
/* Վերադարձնում է բառերի ցուցակի n-րդ տարրը։ */
struct word* nth( struct word* list, size_t n )
{
struct word* res = list;
while( n-- > 0 ) {
res = res->n;
if( res == NULL )
return NULL;
}
return res;
}
</pre>
<p>Վերջապես ամեն ինչ պատրաստ է՝ պարագրաֆը տրված լայնությամբ հավասարեցնելու համար։ <code>justify_paragraph</code> ֆունկցիան ստանում է տողերի ցուցակն ու տողի հարկավոր <code>width</code> լայնությունը։ Այնուհետև, անցնելով ցուցակի բոլոր տարրերով, բառերի արանքներում բացատներ է ավելացնում այնքան ժամանակ, քանի դեռ տողի երկարությունը չի հասել <code>width</code> թվին։</p>
<pre class="prettyprint lang-c">
/* տողերը հավասարեցնել՝ բառերի արանքներում
ներմուծելով լրացուցիչ բացատներ */
void justify_paragraph( struct line* par, size_t width )
{
/* քանի դեռ ցուցակի նախավերջին տողը չէ */
while( par->n != NULL ) {
/* քանի դեռ տողի երկարությունը փոքր է width-ից */
while( par->l < width ) {
/* ընտրել պատահական ինդեքս */
int po = rand() % (par->c - 1);
/* վերցնել տողի՝ այդ ինդեքսով բառը */
struct word* wd = nth( par->w, po );
/* 1-ով ավելացնել բառի բացատների քանակը */
++wd->s;
/* 1-ով ավելացնել տողի ընդհանուր երկարությունը */
++par->l;
}
/* անցնել հաջորդ տողին */
par = par->n;
}
}
</pre>
<p>Ահա այսքանը։ Մնում է միայն կազմակերպել ծրագրի մուտքի կետը՝ <code>main</code> ֆունկցիան, որպեսզի հնարավոր լինի ծրագիրն օգտագործել իր նշանակությամբ։ Նախատեսված է, որ ծրագիրը տեքստը կարդում է ներմուծման ստանդարտ հոսքից, իսկ ֆորմատավորված արդյունքը դուրս է բերում արտածման ստանդարտ հոսքին։</p>
<pre class="prettyprint lang-c">
int main( int argc, char** argv )
{
/* ծրագիրը որպես պարամետր սպասում է միայն
տեքստի լայնությունը */
if( argc != 1 && argc != 3 )
return 1;
/* եթե լայնությունը տրված չէ՝ այն համարել 40 */
size_t length = 40;
/* հրամանային տողից կարդալ length-ի նոր արժեքը */
if( argc == 3 )
if( argv[1][0] == '-' && argv[1][1] == 'w' )
sscanf(argv[2], "%d", &length);
/* արժեքավորել պատահական թվերի գեներատորը */
srand(time(0));
/* նախնական տեքստի բուֆերի չափը */
const size_t bsize = 4096;
/* դինամիկ բուֆեր՝ տեքստի համար */
char* text = calloc(bsize, sizeof(char));
/* քանի դեռ stdin-ի վրա կարդալու բան կա,
կարդալ այն text բուֆերի մեջ */
while( fgets(text, bsize, stdin ) != NULL ) {
/* կարդացած տեքստից կառուցել տողերի ցուցակ */
struct line* u = create_paragraph( text, length );
/* հավասարեցնել պարագրաֆը տրված լայնությամբ */
justify_paragraph( u, length );
/* արտածել պարագրաֆի տողերը */
print_lines( u );
/* ազատել պարագրաֆի զբաղեցրած հիշողությունը */
destroy_lines( u );
}
/* ազատել բուֆերի զբաղեցրած հիշողությունը */
free(text);
return 0;
}
</pre>
<p>Ծրագիրը կոմպիլյացնելու համար կարելի է օտագործել <code>gcc</code> կամ <code>clang</code> կոմպիլյատորները․</p>
<pre class="prettyprint lang-bash">
$ gcc -std=c11 -o splitext splitext.c
</pre>
<p>Իսկ <code>test1.txt</code> ֆայլում պարունակվող տեքստը, օրինակ, 30 նիշ լայնությամբ տողերով ֆորմատավորելու համար պետք է գրել։</p>
<pre class="prettyprint lang-bash">
$ ./splittext -w 30 < text1.txt
</pre>
<p>Նույն արդյունքը կարելի է ստանալ նաև հետևյալ հրամանով․</p>
<pre class="prettyprint lang-bash">
$ cat test1.txt | ./splittext -w 30
</pre>
armenbadalhttp://www.blogger.com/profile/01200997467308938068noreply@blogger.com0