حدس میزنم همه توسعه دهندگان کنجکاو، دست کم یک بار این را پرسیده اند. طبیعی است که مجذوب نحوه کار زبان های برنامه نویسی شوید. متأسفانه، بیشتر پاسخهایی که می خوانیم بسیار آکادمیک یا نظری هستند. برخی دیگر حاوی جزئیات بیش از حد هستند. در نتیجه دقیقا متوجه عمکرد آن ها نمیشویم. بنابراین ما می خواهیم به این پرسش پاسخی ساده و قابل فهم دهیم تا متوجه روند ایجاد یک زبان کامل به همراه یک کامپایلر برای آن بشویم.
دید کلی
بیشتر افرادی که می خواهند یاد بگیرند چگونه "یک زبان برنامه نویسی خلق کنند" به طور موثر بدنبال اطلاعاتی در مورد ساخت یک کامپایلر هستند. آنها می خواهند مکانیکی را که اجازه اجرای یک زبان برنامه نویسی جدید را می دهد درک کنند.
کامپایلر یک قطعه اساسی از این معما است اما ساخت یک زبان برنامه نویسی جدید به بیش از این نیاز دارد:
1) یک زبان باید طراحی شود: سازنده زبان باید در مورد الگو های مورد استفاده و نحو (syntax) زبان تصمیمات اساسی بگیرد.
2) یک کامپایلر ایجاد شود.
3) یک کتابخانه استاندارد اجرا شود.
4) ابزارهای پشتیبانی مانند ویرایشگرها و Build System ها ارائه شود.
بیایید جزئیات بیشتری را ببینیم که هر یک از این نکات به چه معناست.
طراحی یک زبان برنامه نویسی
اگر فقط می خواهید کامپایلر خود را بنویسید تا ببینید این چیز ها چطور کار می کنند، می توانید از این مرحله صرف نظر کنید. کافی ست زیرمجموعه ای از یک زبان موجود را بگیرید و یا یک تغییر به آن بدهید و شروع به کار کنید. با این حال، اگر برنامه هایی برای ایجاد زبان برنامه نویسی خود دارید، باید کمی به آن فکر کنید.
به نظرم باید یک زبان برنامه نویسی را در دو فاز طراحی کنیم:
1) مرحله تصویر کلی
2) مرحله پالایش
در مرحله اول ما به پرسش های اساسی درباره زبان خود پاسخ می دهیم:
- می خواهیم از کدام الگوی اجرا (execution paradigm) استفاده کنیم؟ دستوری (imperative) باشد یا عملکردی (Functional)؟ یا بر اساس ماشینهای حالات (state machines) یا قوانین کسب و کار (Business Rule) ؟
- آیا سیستم نوع (Type system) ایستا می خواهیم یا پویا؟
- این زبان در چه نوع برنامه هایی بهترین خواهد بود؟ آیا برای اسکریپت های کوچک استفاده می شود یا سیستم های بزرگ؟
- چه چیزی برای ما بیشتر اهمیت دارد: عملکرد؟ خوانایی؟
- آیا می خواهیم مشابه زبان برنامه نویسی موجود باشد؟ هدفمند مثل C یا یادگیری آسان مثل پایتون؟
- آیا می خواهیم روی یک سکوی خاص کار کند؟
- می خواهیم از چه نوع قابلیت های فرا برنامهنویسی (Metaprogramming) پشتیبانی کنیم؟ اگر اصلا (if any) ؟ ماکرو؟ قالب ها؟ بازتاب؟
در مرحله دوم ما همچنان که از زبان استفاده می کنیم به تکامل خود ادامه خواهیم داد. ما با موضوعاتی روبرو خواهیم شد ، مواردی که بیان آنها بسیار دشوار یا غیرممکن است به زبان ما انجام می شود و در نهایت تکامل می یابیم. مرحله دوم ممکن است به اندازه مرحله اول پر زرق و برق نباشد ، اما این مرحله ای است که ما زبان خود را مرتباً تنظیم می کنیم تا در عمل از آن استفاده کند ، بنابراین نباید آن را دست کم بگیریم.
ساخت یک کامپایلر
ساخت یک کامپایلر هیجان انگیزترین مرحله در ایجاد زبان برنامه نویسی است. هنگامی که یک کامپایلر داریم، می توانیم زبان خود را زنده کنیم. یک کامپایلر به ما اجازه می دهد بازی را با زبان شروع کنیم، از آن استفاده کنیم و آنچه را که در طراحی اولیه از دست داده ایم شناسایی کنیم. این اجازه می دهد تا اولین نتایج را مشاهده کنید. به سختی می توان لذت اجرای اولین برنامه نوشته شده به زبان برنامه نویسی کاملاً جدید را گفت، هر چقدر هم که برنامه ساده باشد.
اما چگونه یک کامپایلر بسازیم؟
مانند هر کار پیچیده ای، ما این کار را به صورت مرحله ای انجام می دهیم:
1) ما تجزیه کننده (parser) می سازیم: تجزیه کننده بخشی از کامپایلر ما است که متن برنامه های ما را می گیرد و می فهمد کدام دستورات را بیان می کنند. این اصطلاحات ، عبارات و کلاسها را تشخیص می دهد و ساختارهای داده داخلی را برای نمایش آنها ایجاد می کند. بقیه ی تجزیه کننده با این ساختارهای داده کار خواهد کرد ، نه با متن اصلی.
2) (اختیاری) ما درخت تجزیه را به یک درخت نحو انتزاعی (Abstract Syntax Tree) ترجمه می کنیم. به طور معمول ساختارهای داده تولید شده توسط تجزیه کننده کمی سطح پایین هستند زیرا حاوی جزئیات زیادی هستند که برای کامپایلر ما خیلی مهم نیستند. به همین دلیل می خواهیم مرتباً ساختار داده ها را در سطح کمی بالاتر مرتب کنیم.
3) نمادها را حل می کنیم. در کد ما مواردی مانند این را می نویسیم a+1. کامپایلر ما باید بفهمد a به چه چیزی اشاره دارد. آیا فیلد است؟ آیا متغیر است؟ آیا پارامتر است؟ ما کد را بررسی می کنیم تا به آن پاسخ دهیم.
4) ما درخت را معتبر می کنیم. ما باید بررسی کنیم که آیا برنامه نویس خطایی مرتکب نشده است. آیا او سعی دارد یک بولی و یک int را جمع کند؟ یا دسترسی به یک رشته غیر موجود بدهد؟ ما باید پیام های خطای مناسب تولید کنیم.
5) ما کد ماشین یا زبان ماشین را تولید می کنیم. در این مرحله ما کد را در چیزی که ماشین می تواند اجرا کند ترجمه می کنیم. این می تواند کد ماشین یا کد بایت برای برخی از ماشین های مجازی باشد.
6) (اختیاری) ما پیوند را انجام می دهیم. در برخی موارد ، به منظور تولید یک اجرای واحد نیاز به ترکیب کد ماشین تولید شده برای برنامه های خود با کد کتابخانه های استاتیک که می خواهیم شامل شود را داریم.
آیا ما همیشه به یک کامپایلر نیاز داریم؟ نه! ما می توانیم برای اجرای کد، آن را با روش دیگری جایگزین کنیم:
- مفسر (Interpreter): اساساً برنامه ای است که مراحل 1 تا 4 یک کامپایلر را انجام می دهد و سپس آنچه را که توسط درخت نحو انتزاعی مشخص شده است، به طور مستقیم اجرا می کند.
- مترجم مبدأ به مبدأ (Transpiler) : آنچه را که در مراحل 1 تا 4 مشخص شده است انجام می دهد و سپس برخی از کدها را به زبانی که قبلاً برای آنها کامپایلر تهیه شده (مثلا C ++ یا Java)، خارج می کند.
این دو گزینه کاملاً معتبر هستند و معمولاً انتخاب یکی از این دو منطقی است زیرا به تلاش کمتری نیاز دارد.
اگر می خواهید برای مترجم مبدأ به مبدأ یک مثال عملی با کد را ببینید، نگاهی به این مقاله بیندازید. همچنین در این مقاله جزئیات بیشتری از تفاوت بین یک کامپایلر و یک مفسر را خواهید خواند.
یک کتابخانه استاندارد برای زبان برنامه نویسی شما
هر زبان برنامه نویسی نیاز به انجام چند کار دارد:
- چاپ روی صفحه
- دسترسی به سیستم فایلبندی (filesystem)
- استفاده از اتصالات شبکه
- ایجاد رابط های کاربری گرافیکی (GUIs)
اینها ویژگیهای اساسی برای تعامل با بقیه سیستم هستند. بدون آنها یک زبان اساساً بی فایده است. چگونه این ویژگی ها را ارائه می دهیم؟ با ایجاد یک کتابخانه استاندارد. این مجموعه ای از توابع یا کلاس ها است که می تواند در برنامه های نوشته شده به زبان برنامه نویسی ما فراخوانی شود اما به زبان دیگری نوشته شود. به عنوان مثال ، در بسیاری از زبانها کتابخانه های استانداردی وجود دارد که حداقل تا حدی با زبان C نوشته شده اند.
یک کتابخانه استاندارد می تواند حاوی مطالب بیشتری باشد. به عنوان مثال کلاس ها برای نمایش مجموعه های اصلی مانند لیست ها و نقشه ها ، یا پردازش فرمت های معمول مانند JSON یا XML . غالباً این شامل ویژگی های پیشرفته برای پردازش رشته ها و عبارات منظم می شود.
به عبارت دیگر ، نوشتن یک کتابخانه استاندارد کار زیادی است. پر زرق و برق نیست ، از نظر مفهومی به اندازه نوشتن یک کامپایلر جالب نیست اما هنوز هم یک مؤلفه اساسی برای زنده ماندن یک زبان برنامه نویسی است.
روش هایی برای جلوگیری از این نیاز وجود دارد. یکی این که زبان را در برخی از سیستم عامل ها اجرا کنید و امکان استفاده مجدد از کتابخانه استاندارد زبان دیگر را فراهم کنید. به عنوان مثال ، همه زبانهایی که روی ماشین مجازی جاوا (JVM) اجرا می شوند می توانند به سادگی از کتابخانه استاندارد جاوا استفاده مجدد کنند.
ابزارهای پشتیبانی برای یک زبان برنامه نویسی جدید
برای اینکه یک زبان در عمل قابل استفاده باشد، اغلب باید چند ابزار پشتیبانی بنویسیم.
بدیهی ترین آن، ویرایشگر است. امروزه یک ویرایشگر تخصصی با برجسته سازی نحو (Syntax highlighting) ، بررسی خطای درون خطی و تکمیل خودکار کد ها یک امر ضروری است تا هر توسعه دهنده ای را به بهره وری برساند.
اما امروزه توسعه دهندگان بد عادت شده اند و انتظار انواع ابزارهای پشتیبانی دیگر را دارند. به عنوان مثال ، یک خطایاب (debugger) برای مقابله با یک اشکال ناخوشایند می تواند واقعاً مفید باشد. یا یک سیستم ساخت (Build automation) یا یک چارچوب مدیریت پروژه (Project Management Framework) می توانند چیزهایی باشند که کاربران بعداً آن ها را میخواهند.
در همان ابتدای کار ویرایشگر می تواند کافی باشد اما با افزایش تعداد کاربران شما، پیچیدگی پروژه ها نیز افزایش می یابد و به ابزارهای پشتیبانی بیشتری نیاز خواهید داشت. امیدوارم در آن زمان جامعه ای وجود داشته باشد که مایل به کمک به ساخت آنها باشد.
خلاصه
ایجاد یک زبان برنامه نویسی روندی است که به نظر بسیاری از توسعه دهندگان مرموز است. در این مقاله سعی کردیم نشان دهیم که این فقط یک فرآیند است. جذاب و آسان نیست ، اما قابل انجام است.
به دلایل مختلف ممکن است بخواهید یک زبان برنامه نویسی بسازید. یک دلیل خوب سرگرمی است، دلیل دیگر برای یادگیری نحوه کار کامپایلرها است. بسته به بسیاری از عوامل، زبان شما در نهایت می تواند بسیار مفید باشد یا خیر. اما اگر در حین ساختن از آن لذت ببرید و یا یاد بگیرید، ارزشش را دارد که برای این کار کمی وقت بگذارید؛ بی گمان شما می توانید برای همکاران خود لاف بزنید!
همچنین در اینجا می توانید 68 منبع برای کمک در ایجاد زبان های برنامه نویسی را ببینید.
پی نوشت: این نوشتار از وبلاگ شرکت strumenta توسط محسن حاجی ولیئی (عرفان) ترجمه شده است؛ امیدوارم لذت برده باشید. همچنین کپی با ذکر منبع ترجمه (وبلاگ توی) بلامانع است.