برنامه نویسی

چگونه زبان برنامه نویسی خود را بسازیم

زبان های برنامه نویسی مرا مجذوب خود می کنند.

آنها ابزاری هستند که از طریق آنها خواسته های انسان را به دستورالعمل های قابل استفاده ماشینی منتقل می کنیم که می تواند هنر شگفت انگیزی ایجاد کند، پیشرفت های علمی ایجاد کند و تقریباً هر نوع تجارت روی کره زمین را انجام دهد.

ما این کلمات و عبارات قابل خواندن برای انسان را به تکانه های الکتریکی تقسیم می کنیم که کلیدهای کوچک را روشن و خاموش می کنند و کل دنیای مدرن بر اساس حرکت آن کلیدهای کوچک است.

برنامه نویسی یک عمل ایجاد است و زبان های برنامه نویسی ابزاری هستند که ما برنامه نویسان جهان خود را به وسیله آن ایجاد می کنیم.

در این سری از پست ها، زبان برنامه نویسی خودمان را با هم می سازیم.

چرا زبان خود را بسازید

در حال حاضر صدها زبان برنامه نویسی وجود دارد. چرا خود را ایجاد کنید؟

خوب، شاید کاری وجود داشته باشد که بخواهید انجام دهید که زبان‌های موجود به اندازه کافی برای شما خوب عمل نمی‌کنند.

شاید اشتیاق شما تجسم داده ها باشد و زبانی می خواهید که به طور خاص برای آن کار طراحی شده باشد.

زبان ها نیز برای آزمایش فوق العاده هستند. برخی از محبوب ترین زبان های امروزی ریشه در آزمایش های زبانی دارند. به عنوان مثال، خانواده زبان های ML (که شامل OCaml و F# می شود) به عنوان یک سیستم نوع نظری زندگی خود را آغاز کردند. لیسپ در اصل از مقاله ای در مورد نظریه های مربوط به توابع بازگشتی به دست آمد. ایده هایی از این دو خانواده زبانی راه خود را به چندین زبان مدرن که در حال استفاده گسترده هستند، از جمله Rust و JavaScript باز کرده است.

شاید شما فقط از یک چالش برنامه نویسی خوب لذت ببرید. برخی از سخت‌ترین مشکلات برنامه‌نویسی که تا به حال حل کرده‌ام، هنگام ساخت مفسر و کامپایلر به وجود آمده‌اند. ایجاد یک زبان برنامه نویسی مهارت های طیف گسترده ای از موضوعات برنامه نویسی و علوم کامپیوتر، از جمله ساختار داده ها و الگوریتم ها، نظریه محاسباتی و غیره را گرد هم می آورد.

در مورد من؟ من فقط فکر می کنم ایجاد زبان سرگرم کننده است. من از آن لذت می برم و امیدوارم برخی از کسانی که این سریال را می خوانند نیز از آن لذت ببرند. من به دلیل پروژه هایی که در حین یادگیری و ایجاد زبان های جدید ساخته ام، برنامه نویس بهتری هستم و مطمئن هستم که شما نیز از آن چیزهای زیادی یاد خواهید گرفت.

من از متخصص طراحی و پیاده سازی زبان فاصله زیادی دارم. من فقط از انجام آن بسیار لذت می برم، و اینجا هستم تا آنچه را که یاد می گیرم مستند کنم در صورتی که برای دیگران مفید باشد.

زبان برنامه نویسی چیست؟

آیا یک زبان همان نمادهایی است که برای اینکه به رایانه بگویید چه کاری انجام دهد استفاده می کنید؟ آیا این معناشناسی (معنای آن نمادها) است؟ آیا چیز دیگری است؟ کامپایلر یا مفسر؟ همه موارد بالا؟

من می خواهم بگویم که کمی از همه آنها است. بدیهی است که معناشناسی مهمترین بخش است. شما می توانید از یک نحو برای پیاده سازی مدل های معنایی بسیار متفاوت استفاده کنید. برای نمونه ای از این موضوع به “ارزشگر متا دایره ای” در فصل 4 ساختار و تفسیر برنامه های کامپیوتری مراجعه کنید. با استفاده از همان نحو مبتنی بر بیان s، نویسندگان 4 معناشناسی ارزیاب کاملاً متفاوت را پیاده سازی می کنند.

اما نحو نیز مهم است. یک زبان خوب احتمالاً برای نوشتن کد احساس خوبی دارد، و واضح است که نحو در آن نقش دارد. البته، آنچه که “احساس خوبی دارد” ممکن است برای برنامه نویسان مختلف بسیار متفاوت باشد. برخی از افراد به طور مطلق از حداقل نحو زبان های خانواده Lisp با تمام پرانتزهای آن متنفرند، اما برخی دیگر تقارن بین کد و ساختار داده را می یابند که نحو اجازه می دهد تا زیباترین چیز در جهان باشد.

در غیاب مشخصات رسمی، کامپایلر و/یا مفسر ممکن است به عنوان منبع حقیقت برای رفتار و معناشناسی زبان عمل کنند. زبانی که ما در این مجموعه می سازیم، چنین خواهد بود. ما قصد داریم کامپایلری بسازیم که زبان مبدأ ما را به جاوا اسکریپت ترجمه کند. ممکن است ترجیح دهید آن را به جای کامپایلر، ترانسپایلر صدا کنید، زیرا کد اسمبلی یا فایل های باینری را منتشر نمی کند. اشکالی ندارد، می توانید آن را هر چه می خواهید بنامید – این کارکرد را تغییر نمی دهد.

چگونه یک کامپایلر کار می کند

یک کامپایلر یک رشته کد را در یک زبان مبدأ می گیرد و آن را به زبان دیگری تبدیل می کند که می تواند توسط یک ماشین اجرا شود. برخی از کامپایلرها باینری ها را مستقیماً خروجی می دهند که مستقیماً توسط سخت افزار رایانه تفسیر می شوند. ما قصد داریم زبان خود را به جاوا اسکریپت کامپایل کنیم، که می تواند در یک مرورگر یا توسط یک زمان اجرا سمت سرور مانند Node.js اجرا شود.

یک کامپایلر چندین مرحله دارد که از طریق آنها کد منبع را به خروجی خود تبدیل می کند، که به طور کلی می توان آن را به عنوان قسمت جلویی و انتهایی آن در نظر گرفت.

در قسمت جلویی کامپایلر، کد خوانده می شود و به ساختار داده ای تبدیل می شود که به صورت داخلی توسط کامپایلر استفاده می شود. معمولاً نوعی درخت است. این کار معمولاً با تحلیل واژگانی و نحوی کد منبع یا واژگان و تجزیه انجام می شود.

در طول lexing، کامپایلر متن ورودی را به یک سری نشانه تبدیل می کند که می تواند توسط تجزیه کننده بیشتر تجزیه و تحلیل شود.

تجزیه کننده سپس جریان نشانه ها را می گیرد و آنها را به یک درخت تبدیل می کند. این ممکن است یک درخت تجزیه باشد که گاهی به عنوان درخت نحو بتن نیز شناخته می شود، که شامل تمام داده های مرحله تجزیه است، یا ممکن است یک درخت نحو انتزاعی (AST) تولید کند که فقط حاوی اطلاعات لازم برای پردازش بیشتر است.

سپس قسمت جلویی کامپایلر وارد مرحله تحلیل معنایی می شود که بررسی های مختلفی را روی AST انجام می دهد تا مطمئن شود برنامه ورودی به خوبی شکل گرفته است.

تجزیه و تحلیل معنایی شامل مواردی مانند وضوح متغیر، برای اطمینان از عدم استفاده از متغیرهای اولیه در برنامه، و همچنین بررسی نوع و استنتاج (اگر زبانی با تایپ ایستا باشد) می شود. برخی از زبان ها حتی بررسی های معنایی بیشتری را انجام می دهند.

در طول مراحل جلویی کامپایلر، پیچیدگی زبانی تمایل به افزایش دارد. ما از متن مسطح به یک ساختار داده می‌رویم و در طول فرآیند بررسی معنایی، حاشیه‌نویسی را به ساختار داده اضافه می‌کنیم.

سپس در طول مراحل انتهایی کامپایلر، ساختار داده ایجاد شده توسط قسمت جلویی کاهش یافته و پیچیدگی زبانی کاهش می‌یابد.

فرم‌های سطح بالاتر که قند نحوی هستند، به شکل‌های زبان اصلی خود قند زدایی می‌شوند.

درختان به نمایش های میانی تبدیل می شوند که امکان بهینه سازی را فراهم می کنند.

در نهایت، کامپایلر محصول نهایی را تولید می‌کند و آن را در یک فایل خروجی می‌دهد یا برای اجرا به ارزیاب می‌دهد.

زبان برنامه نویسی جدید ما: Wanda

زبانی که قرار است در طول این مجموعه بسازیم واندا نام دارد. Wanda یک زبان مبتنی بر بیان نسبتاً ساده با چند فرم اصلی است، اما باید به اندازه کافی دارای ویژگی‌های کامل باشد تا برنامه‌های جالبی با آن بنویسد.

نحو واندا مبتنی بر s-expression است، بنابراین شبیه Lisp است. کد Wanda به مقادیر جاوا اسکریپت کامپایل می شود، و بنابراین مانند جاوا اسکریپت دارای توابع درجه یک است (یعنی توابعی که می توانند مانند هر مقدار دیگری استفاده شوند، از جمله به عنوان آرگومان به و مقادیر برگرداندن مقادیر از توابع دیگر).

با این حال، برخلاف جاوا اسکریپت، ما ایجاد شی را به یک سیستم مبتنی بر کلاس محدود می کنیم. در زیر کاپوت، کلاس‌ها بر روی نمونه اولیه نمایندگی جاوا اسکریپت ساخته شده‌اند، اما در خود Wanda باید یک کلاس برای ساخت اشیاء تعریف کنید.

واندا چند نوع ابتدایی دارد:

; numbers are simply JavaScript numbers, i.e. 64 bit floats
15
; strings are JavaScript strings
"hello"
; booleans are JavaScript booleans
true
; nil combines JavaScript null and undefined
nil
; keywords are symbols with a colon at the beginning followed by a valid Wanda identifier
:keyword
; identifiers can have several characters not allowed in JavaScript
this-is+a*valid&identifier
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

استفاده کنید def کلمه کلیدی برای تعریف یک متغیر یا تابع:

(def name "Jason")
(def (inc x)
  (+ x 1))
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

شما می توانید لیست و نقشه های واقعی را تعریف کنید:

[1, 2, 3] ; note that commas are treated as whitespace
{:a "hi" :b 14}
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

واندا دارای یک for فرمی که یک عبارت است:

; for expression through the loop
; produces a list [1, 2, 3, 4, 5]
(for map (i) ((range 5))
  ((println i)
  (inc i)))
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

شرط ها عباراتی هستند، بنابراین می توانید از آنها در انتساب متغیر استفاده کنید:

(def status (if (equal? state "Loading")
                "Loading"
                "Finished"))
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

برخلاف سایر زبان‌های سبک Lisp، در Wanda می‌توانید به ویژگی‌های شی با علامت نقطه دسترسی داشته باشید:

(def me (new Person "Jason" 42))
(println me.name) ; prints "Jason"
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

ما ویژگی‌های زبان اضافی، از جمله کلاس‌ها و اشیاء، تطبیق الگو، و ماکروها را در حین پیاده‌سازی پوشش خواهیم داد. من به خصوص برای رسیدن به ماکروها با شما هیجان‌زده هستم – پیاده‌سازی آن‌ها یک مغز واقعی است، اما زمانی که آنها را به کار انداختید بسیار شگفت‌انگیز هستند.

مانند سایر زبان‌های سبک Lisp، نحو s-expression مستقیماً بر روی یک ساختار داده درون زبانی در خواننده نگاشت می‌شود. سپس Wanda یک مرحله تجزیه اضافی خواهد داشت که در آن درخت کاملاً منبسط شده تولید شده توسط خواننده (شامل بسط هر ماکرو) به یک AST مبتنی بر شی تبدیل می‌شود که می‌توانیم از آن برای تحلیل معنایی استفاده کنیم و کامپایلر اضافی در انتهای پشتی عبور کند. ما می‌توانیم به همین راحتی کد را بر اساس درخت خواننده خروجی بگیریم، اما من از یک AST کامل برای اهداف یادگیری استفاده می‌کنم و به این دلیل که انجام عملیات خاص روی AST آسان‌تر است.

زبان پیاده سازی

جاوا اسکریپت زبان پیاده سازی Wanda است. من تصمیم گرفتم کامپایلر را در جاوا اسکریپت بنویسم، زیرا زبانی آسان برای یادگیری است و اکثر افرادی که با این مجموعه روبرو می شوند احتمالاً حداقل کمی جاوا اسکریپت می دانند. هک کردن پروژه‌های ساده در جاوا اسکریپت نسبتاً آسان است، و من می‌خواستم این مجموعه را تا حد امکان برای افراد زیادی که می‌توانم در دسترس قرار دهم.

یک رویکرد افزایشی

من این مجموعه را بر اساس مقاله اصلی عبدالعزیز غلوم “رویکردی افزایشی در ساخت کامپایلر” بنا کرده ام که هر مقاله با یک کامپایلر کاملاً کاربردی به پایان می رسد. ما کار را با کامپایل و ارزیابی اعداد شروع خواهیم کرد و در نهایت به یک زبان کاملاً کاربردی خواهیم رسید که امیدواریم بتواند طیف گسترده ای از برنامه های جالب را پشتیبانی کند. این دقیقاً از همه جنبه ها درجه تولید نخواهد داشت، اما هدف من این است که آن را برای نوشتن برنامه ها و برنامه های واقعی جالب و مفید کنم تا بتوانید ببینید پیاده سازی یک زبان برنامه نویسی کامل که چیزی بیش از یک اسباب بازی است، چگونه است.

نتیجه

امیدوارم شما هم مثل من از این تلاش برای پیاده سازی زبان هیجان زده باشید! ورودی بعدی این سری، اولین کامپایلر ما، اعداد پشتیبان را ارائه خواهد کرد. همچنین حجم زیادی از دیگ بخار را ایجاد می کند که برای جمع آوری عبارات جالب تر نیاز داریم. گوش به زنگ باشید!

نوشته های مشابه

دیدگاهتان را بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *

همچنین ببینید
بستن
دکمه بازگشت به بالا