زبانی برای به چالش کشیدن برنامهنویسان
من قبلن در دو پست (اینو این که درواقع مقالاتی برای هفتهنامه عصر ارتباط بودند) دوازده زبان برنامهنویسی (یا تقریبن برنامهنویسی) جدی را که کاربردهای بسیاری در دنیای واقعی دارند، معرفی کرده بودم. طبیعی است که یادگیری هر یک از این زبانها نیازمند وقت گذاشتن زیاد، خواندن منابع آموزشی درست حسابی و نوشتن برنامههای کوچک و بزرگ متعدد با آنها است. اما حالا قصد دارم زبانی را معرفی کنم که خیلی هم کاربرد واقعی در دنیای کامپیوتری که ما میشناسیم ندارد. نه به این خاطر که زبان قدرتمندی نیست (که اتفاقن هر کاری با آن میتوان کرد)، بلکه به این خاطر که یکی از ویژگیهای مهم زبانهای برنامهنویسی که برای هر کاری مورد استفاده قرار میگیرند را ندارد، یعنی مناسب بودن برای آن کاربرد. در واقع این زبان برنامهنویسی برای هیچ کاربردی مناسب نیست، یا دقیقتر بگویم حتی برای نحوه کارکردن مغز انسان نیز مناسب نیست، به همین خاطر اسمش را گذاشتهاند زبان برنامهنویسی BrainFuck که ترجمهاش را بیخیال میشویم.
زبان BrainFuck یک زبان بغایت مینیمالیستی است با دستورزبانی (Syntax) به شدت مرموز و عجیب. کل زبان فقط از هشت دستور ساده (یا بهتر بگویم تنها از ۸ کاراکتر) و یک اشارهگر راهنمای دستورات (Instruction Pointer) ساخته شده است و احتمالن تنها هدف از ساختن آن به چالش کشیدن برنامهنویسان مغرور خودپسندی بوده است که از رابطها (Interfaces) و ساختمان دادههای (Data Structures) پیشرفتهای که در کتابخانههای استاندارد زبانهای برنامهنویسی پیشرفته مثل پایتون وجود دارد استفاده میکنند و به خود غره میشوند که برنامهنویس هستند. هر چند به نظر نمیرسد کاربرد جدیای داشته باشد، ولی یادگیری آنکه خیلی هم زبان نمیبرد خالی از لطف نیست، و شاید چیزهای بسیار زیادی از نحوه اجرای برنامهها به برنامهنویسان بیاموزد.
واقعیت این است که این زبان خیلی هم جدید نیست، هر چند یک دفعه سر زبانها افتاده است (شاید همه برنامهنویسان یک دفعه با هم انقدر قت خالی پیدا کردهاند که مغزشان را به فنا دهند) اما درواقع در سال ۱۹۹۳ توسط اوربان مولر (urban Muler) نوشته شد. و علیرغم سادگی وصف ناپذیر آن زبانی ساخت یافته (Structured) و دستوری (Imperative) و یک زبان ماشین کامل تورینگ (Turing-Complete Programming Language) است. احتمالن هدف مولر غیر از چیز کردن مغز برنامهنویسان، ساختن زبانی با کوچکترین کامپایلر ممکن بوده (و این یعنی زبان کامپایلری است و نه مفسری در آن مزایای بسیار است برای آنان که سطح پایین میاندیشند) که از کامپایلر ۱۰۲۴ بایتی زبان FALSE (بله زبانی به این اسم هم وجود دارد) الهام گرفته شده است.
در حقیقت مولر تا حدی در ساخت این زبان و کامپایلرش وسواس به خرح داد که کامپایلرهای آن حدود ۲۰۰ بایت و حتی یک مورد کامپایلر ۱۰۰ بایت حجم دارند و این شاید به ما یادآوری کند در دنیای فناوری، نه دنیای پر زرق و برق گجتهای رنگارنگ و بازار مکاره تبلیغ و فروش سختافزارهای پر ابهت، هنوز هم تکتک بایتها ارزشمند هستند. از این جهت میتوان BrainFuck را به نوعی Raspberry Pi دنیای زبانهای برنامهنویسی حساب کرد.
خوب گفتم که این زبان تنها هشت دستور دارد و البته فایلهای آن با پسوند نه چندان دور از انتظار bf ذخیره میشوند. بگذارید نگاهی به این دستورات و معادلهای آنها در C بیاندازیم
دستورات < و > برای حرکت اشارهگر به سلولهای قبلی و بعدی به کار میروند (به ترتیب معادل --P و ++P در زبان C هستند). دستورات + و – برای کم و زیاد کردن محتوای یک بایت داده که اشارهگر به آن اشاره میکند به کار میروند (به ترتیب معادل --P* و ++P* در زبان C هستند). دستورات [ و ] برای ساختن یک حلقه روی مقدار اشارهگر به کار میروند (معادل while در زبان C هستند). دستور . خروجی بایت دادهای که اشارهگر به آن اشاره میکند را به خروجی میبرد (معادل putchar در زبان C) و دستور , یک بایت داده را از ورودی گرفته و در محلی که اشارهگر به آن اشاره میکند قرار میدهد (معادل getchar در زبان C) نیز دستورات ورودی و خروجی آن هستند
به همین سادگی. خوب چه انتظار دیگری داشتید، با کامپایلری حدودن ۲۰۰ بایتی و زبانی که برای مینمال بودن طراحی شد، جز کار با اشارهگرها و به لعنتی دادن مغز چه کار دیگری میتوان کرد :) خوب حالا بیاید چندتا برنامه ساده با این زبان بنویسیم تا ببینیم چطور کار میکند.
اولین برنامه چیزی شبیه این خواهد بود. یادم رفت بگویم خبری از کامنت و فرورفتگی کد (آخر یک کاراکتر دستور نیاز به فرو رفتگی دارد؟) و این چیزها نیست. برنامههای این زبان دنبالهای بی فاصله از دستورات ساده تک کاراکتری آن است.
++++++[>++++++++++<-]>+++++.
این برنامه ساده قرار است یک حرف A را در خروجی چاپ کند. نحوه کار آن به این صورت است که ابتدا سلول اول حافظه (یک بایت) را به اندازه ۶ واحد افزایش میدهد که به عنوان شمارنده حلقه مورد استفاده قرار خواهد گرفت. بعد با ] وارد حلقه میشود و اشارهگر به سلول دوم میرود، ده بار سلول دوم را افزایش میدهد و به سلول اول باز میگردد و یک واحد از آن کم میکند. این حلقه شش بار انجام میشود تا سلول اول صفر شود و کنترل به بعد از [ منتقل میشود. در این نقطه ما در سلول یک هستیم که مقدار آن صفر است و مقدار سلول دو برابر ۶۰ میباشد. پس به سلول دوم میرویم و آن را ۵ بار دیگر افزایش میدهیم که به مقدار ۶۵ یعنی کد ASCII معادل کاراکتر A برسد و سپس آن را با . به خروجی میبریم. به همین سادگی :) خوب حالا بگذارید بروم سراغ یک مثال یک مقدار مشکلتر. نگاهی به کد زیر بیاندازید، قصد داریم به زودی از آن استفاده درست حسابیتری کنیم.
,[>+<-]>.
این برنامه ورودی دریافتی از کاربر را گرفته و آن را در سلول اول قرار میدهد، بعد یک حلقه را شروع میکند و به سلول دوم میرود و مقدار آن را افزایش میدهد، به سلول اول بر میگردد و از آن یک واحد کم میکند و این عمل را تا صفر کردن سلول اول تکرار میکند. در نتیجه در انتها سلول دوم مقدار اولیه سلول اول را خواهد داشت. انگار مقدار سلول اول را در دومی کپی کرده است. از آنجایی که ما در انتهای کار در سلول اول قرار داریم، بعد از حلقه ابتدا به سلول دوم میرویم و سپس مقدار آن را به خروجی میبریم. حالا که با این زبان بیشتر آشنا شدید، بگذارید با استفاده از قطعه کد قبلی یک برنامه ساده بنویسیم که دو عدد را از ورودی گرفته و آنها را در هم ضرب میکند :)
,>,<[>[>+>+<<-]>>[-<<+>>]<<<-]>>.
عملکرد برنامه به این صورت است که در ابتدا دو مقدار را از ورودی میخواند، سپس حلقه بیرونی را شروع میکند (بله حلقههای Nested هم داریم) و شرط آن را روی متغیر سلول اول قرار میدهد. بعد به سلول دوم میرود و حلقه درونی را شروع میکند که شرط آن روی سلول دوم قرار دارد. سپس سلول سوم را در هر بار تکرار حلقه درونی یک واحد افزایش میدهد. اما یک مشکلی وجود دارد، چون در انتهای حلقه درونی، مقدار سلول دوم برابر صفر خواهد بود، برای دور بعد اجرای حلقه بیرونی، حلقه درونی اجرا نمیشود. پس در همان حلقه درونی وقتی سلول سوم را یک واحد افزایش دادیم، سلول چهارم را نیز افزایش میدهیم و در انتهای حلقه مقدار سلول چهارم را در سلول دوم (شمارنده حلقه درونی) کپی میکنیم. در نهایت مقدار جواب (که طبعن باید کمتر از ۲۵۶ باشد تا منطقن جواب درست را داشته باشیم) در سلول سوم قرار دارد.
حالا برای مثال سوم، بگذارید یک شمارنده باینری بسازیم:
-[>[->]++[-<+]-]
عملکرد این برنامه نیز به صورت مشابه با مثالهای قبلی به سادگی قابل تحلیل است.اولین دستور، مقدار سلول اول را یک واحد کم میکند که چون مقدار آن خودش در ابتدا صفر بوده، کم کردن یک واحد از آن مقدارش را به ۲۵۵ تغییر میدهد. سپس حلقه روی مقدار سلول اول شروع میشود. سپس هر بار بیت کمارزشتر را تغییر میدهد، اگر صفر شد، آن را یک و اگر یک شد آن را صفر میکند و بقیه بیتها را به سمت چپ شیفت میدهد تا زمانی که به سلولی با مقدار ۲۵۵ برسد. و این حلقه تا ابد ادامه مییابد.
خوب حالا حتمن میتوانید درک کنید که چرا من برخلاف رویه مرسوم آموزش زبانهای برنامهنویسی از دوران انسانهای غارنشین تا امروز، آموزش خودم را با Hello World شروع نکردم. البته الان هم دیر نشده، برنامه ساده Hello World به زبان BrainFuck یک همچین چیزی میشود. بد نیست به عنوان تمرین یک جدول کد ASCII جلوی خودتان بگذارید (یا برنامهای با BrainFuck بنویسید که جدول ASCII را برایتان نمایش دهد) و سعی کنید نحوه کار این برنامه را تحلیل کنید.
++++++++[>++++[>++>+++>+++>+<<<<-]>+>+>->>+[<]<-]>>.>---.+++++++..+++.>>.<-.<.+++.------.--------.>>+.>++.
گمانم خیلی هم سخت نبود ;) حالا شما میتوانید برای تفریح هم شده، برنامههای دلخواه خود را به این زبان بنویسید، مثلن یک رمزگذار/رمزگشای سزار را با استفاده از زبان BrainFuck بنویسید یا اصلن مفسر این زبان را به یک زبان سطح بالا مانند Ruby یا Rust خلق کنید. یا حتی مثل این مورد با استفاده از جاوا اسکریپت. مهم این است که اگر برنامهنویس باشید میتوانید از این زبان لذت ببرید :)










