برنامهنویسی چندزبانه یعنی نوشتن کدی که واژههای کلیدی، نامهای کتابخانه و پیامهای کامپایلرش به زبانی انسانی غیر از انگلیسیاند، و با این حال به همان برنامههایی کامپایل میشود که دیگران میسازند. زبان برنامهنویسی سلام یک نمونهی کارا از این ایده است: هر واژهی کلیدی، گونهی داخلی، نام کتابخانهی استاندارد و پیام خطای سلام یک املای انگلیسی و یک املای فارسی دارد و هر دو از یک بخش آغازین کامپایلر میگذرند و به یک فایل اجرایی میرسند. پرسش جالب برای آینده این است که آیا این مدل قابل تعمیم است. آیا همین کامپایلر میتواند املای عربی، ترکی یا هندی هم پیدا کند؟ این نوشته اول توضیح میدهد طراحی دوزبانهی سلام (که در یک زبان، دو خط معرفی شده) در خود کامپایلر چطور کار میکند و سپس بررسی میکند که یک زبان سوم چه نیازهایی دارد. هر چه اینجا دربارهی زبان سوم آمده تحلیل است؛ هیچ بستهی زبانی سومی در مخزن وجود ندارد و برنامهای هم برایش اعلام نشده است.
زبانهای برنامهنویسی بومیشده پیش از سلام
سلام نخستین زبانی نیست که این پیشفرض را کنار میگذارد که کد به انگلیسی نوشته میشود. چند پیشینهی شناختهشده گسترهی رویکردها را نشان میدهند:
- فرمولهای صفحهگسترده. مایکروسافت اکسل نام تابعهایش را برای هر زبان ترجمه میکند؛ کاربر آلمانی
SUMMEمینویسد و کاربر انگلیسیزبانSUM. احتمالاً پرکاربردترین نشانهگذاری برنامهنویسی بومیشده در جهان همین است. - Scratch بلوکهایش را به زبانهای بسیاری ترجمه میکند. چون برنامه مجموعهای از بلوکهاست نه متن، یک پروژه را میتوان بدون بازنویسی به هر زبانی نمایش داد.
- 1C:Enterprise که در روسیه برای نرمافزارهای تجاری بسیار به کار میرود، واژههای کلیدی روسی با معادلهای انگلیسی دارد.
- Hedy، زبانی آموزشی و تدریجی از دانشگاه لایدن، واژههای کلیدیاش را برای استفاده در کلاس به زبانهای طبیعی بسیاری عرضه میکند.
- پروژههای هنری و پژوهشی مانند Qalb (یک لیسپ به عربی) و Wenyan-lang (زبانی به چینی کلاسیک) نشان دادند که این ایده با خط، جهت نوشتار و فرهنگ درگیر میشود، نه فقط با واژگان.
بیشتر اینها یا سطح محدودی را ترجمه میکنند (نام فرمولها، بلوکها) یا زبانی جداگانه میسازند. رویکرد سلام میان این دو است: یک زبان کامل همهمنظوره، با دو املای کامل که همهچیزِ پایینتر از تحلیلگر واژگانی را با هم شریکاند.
مدل دوزبانهی زبان سلام چطور کار میکند
ایدهی اصلی این است که دستور زبان مشترک است و فقط املاها فرق دارند. یک دستور اگر فارسی همان ساختار دستور if انگلیسی را دارد؛ فقط واژه عوض میشود. این یک برنامهی شمارش واژه به فارسی است که از نامهای فارسی کتابخانهی استاندارد استفاده میکند:
واردسازی رشته
تابع ریشه:
واژهها := رشته.تفکیک کردن(«سیب انار سیب توت سیب»، « »)
دیرکن واژهها.آزادکن()
ناپایا شمار := نگاشت {} برگردان نگاشت<رشته، صحیح>
دیرکن شمار.آزادکن()
هر واژه در واژهها:
شمار.درج(واژه، شمار.دریافت با پیش فرض(واژه، ۰) + ۱)
پایان
سرچاپ «سیب:»، شمار.بگیر(«سیب»)
سرچاپ «انار:»، شمار.بگیر(«انار»)
پایانو همان برنامه به انگلیسی:
import str
func main:
words := str.Split("apple pear apple fig apple", " ")
defer words.free()
mut counts := HashMap {} as HashMap<str, int>
defer counts.free()
each word in words:
counts.put(word, counts.get_or(word, 0) + 1)
end
println "apple:", counts.get("apple")
println "pear:", counts.get("pear")
endاین دو خط به خط یک برنامهاند. واردسازی همان import است، رشته بستهی str، تفکیک کردن همان Split، دیرکن همان defer، نگاشت همان HashMap، هر ... در همان each ... in و دریافت با پیش فرض همان get_or. درون کامپایلر، این نگاشت در چند لایه انجام میشود.
لایهی ۱: جدولهای واژههای کلیدی
واژههای کلیدی در compiler/langpack.salam به شکل جدولهای موازی نگه داشته میشوند: فهرستی از گونههای توکن، و برای هر بستهی زبانی املای هر گونه. هر بسته ۶۰ املای واژهی کلیدی دارد، از func و ret تا try، catch و throws؛ درس واژههای کلیدی آنها را کنار هم فهرست کرده است. تحلیلگر واژگانی (Lexer) هر شناسه را در جدول بستهی فعال جستوجو میکند و در هر دو حالت آن را به یک گونهی توکن تبدیل میکند؛ پس تحلیلگر نحوی (Parser) هرگز نمیبیند که توکن از کدام زبان آمده است.
یک جزئیات نشان میدهد چرا این کار از یک فهرست واژه سختتر است. فارسی برای هر واژهی کلیدی انگلیسی یک واژهی جدا ندارد. تا هم نقش until (حلقهی تا وقتی) را دارد و هم نقش to (در تکرار ۱ تا ۵)، و هر هم نقش each را دارد و هم by (گام در تکرار ۰ تا ۲۰ هر ۲). تحلیلگر واژگانی اینها را از روی بافت تشخیص میدهد: درون سرآیند تکرار، تا یعنی to و در جاهای دیگر یعنی until. املای یک زبان طبیعی همیشه یکبهیک با املای انگلیسی نیست و کامپایلر باید این بافت را با خود حمل کند.
لایهی ۲: نامها در کتابخانهی استاندارد
واژههای کلیدی بخش آسان کارند. یک برنامهی واقعی بیشتر واژههایش را صرف نامهای کتابخانه میکند: str.Split، HashMap.put، os.Args. سلام اینها را با برچسبهایی روی هر اعلان همگانی مدیریت میکند. این نمونه از خود کد کتابخانه است، که به انگلیسی نوشته شده:
@en "Split"
@fa "تفکیک کردن"
pub pure func Split(s: str, delim: str): Vector<str>: ret s.split(delim) endکتابخانهی استاندارد بیش از ۲۱ هزار برچسب @fa دارد، یکی برای هر بسته، تابع، گونه، فیلد و ثابت همگانی. این عدد هزینهی واقعی یک بستهی زبانی است. ترجمهی ۶۰ واژهی کلیدی یک بعدازظهر کار است؛ نامگذاری یکدست همهی نمادهای همگانی یک کتابخانهی استاندارد بزرگ یک پروژهی کامل است. سلام حتی یک سند طراحی دارد، docs/design/naming-standard.md، با قاعدههایی برای اینکه نامهای فارسی چطور نوشته شوند (فاصله میان کلمهها، هرگز خط زیر) و وقتی یک نام انگلیسی تغییر میکند چطور همگام بمانند.
لایهی ۳: پیامهای خطا
فهرست پیامهای کامپایلر در compiler/i18n.salam بیش از ۱٬۳۰۰ پیام خطا و پیام ابزار دارد که هرکدام معادل فارسی دارند. کد هر خطا در هر دو زبان یکی است و در فارسی با رقمهای فارسی نوشته میشود؛ پس E013 و خطا۰۱۳ یک خطا هستند. بهطور پیشفرض زبان پیامها از زبان کد منبع پیروی میکند و --error-lang=fa برای یک برنامهی انگلیسی خطاهای فارسی چاپ میکند.
لایهی ۴: قاعدههای خط در تحلیلگر واژگانی
فارسی به قاعدههایی هم نیاز دارد که هیچ ربطی به واژگان ندارند. تحلیلگر واژگانی رقمهای فارسی ۰ تا ۹ را در عددها و ویرگول فارسی ، را بهعنوان جداکننده میپذیرد. گیومههای «» در فایلهای فارسی رشته را مشخص میکنند. نامها میتوانند فاصله داشته باشند و کامپایلر فاصله، نیمفاصله یا نبودِ هر دو میان بخشهای یک نام فارسی را یک نام میداند. همچنین شکل عربی و فارسی حرفهای «ی» (ي/ی) و «ک» (ك/ک) را یکسان میداند، چون صفحهکلیدها و متنهای رونوشتشده دائم آنها را قاطی میکنند. همین قاعدهها نوشتن کد فارسی را در عمل ممکن میکنند و هر خطی نسخهی خودش را از آنها لازم دارد.
تشخیص زبان و جابهجایی میان بستههای زبانی
هر فایل میتواند زبانش را با یک توضیح در سرآیند اعلام کند (// lang: en یا // زبان: فارسی)؛ اگر چنین توضیحی نباشد، کامپایلر بستهای را برمیگزیند که واژههای کلیدی فایل با آن بیشتر میخوانند. فرمان salam translate سپس فایل را میان بستهها بازنویسی میکند: واژههای کلیدی، true/false/null/this، نام تابع ورودی، نام گونههای پایه در جایگاه گونه، و متدهای داخلی ظرفها. شناسهها، توضیحها و محتوای رشتهها دقیقاً همانطور که نوشته شدهاند میمانند.
مترجم محدودیتهایی دارد که دانستنشان ارزش دارد. فهرست ثابتی از نامهای داخلی را جابهجا میکند؛ پس متدی از کتابخانهی استاندارد که در این فهرست نیست ممکن است به زبان مبدأ بماند. برای نمونه، ترجمهی برنامهی شمارش واژهی فارسی بالا به انگلیسی دریافت با پیش فرض را سر جایش گذاشت و کامپایلر با پیام «identifier must be English in an English file» (شناسه در فایل انگلیسی باید انگلیسی باشد) و راهنمای «the English name is 'get_or'» متوقف شد. اصلاح یک کلمه است و کامپایلر خودش آن را نام میبرد، اما رفت و برگشت هنوز کاملاً خودکار نیست. نوشتهی از کد فارسی به انگلیسی و برعکس این ابزار را عمیقتر بررسی میکند.
بستهها راحتتر از مرز زبان عبور میکنند. چون هر نام همگانی هر دو املا را دارد، یک برنامهی فارسی میتواند بستهای را که به انگلیسی نوشته شده وارد کند و آن را با نامهای فارسیاش فراخوانی کند، و عکس آن هم برای بستههایی که نام @en اعلام کردهاند کار میکند. همین است که دو جامعه را در یک زیستبوم نگه میدارد، نه دو زیستبوم.
یک بستهی زبانی سوم به چه نیاز دارد؟
فرض کنید کسی بخواهد سلام را به عربی، ترکی یا هندی داشته باشد. با خواندن کد کنونی، کار به هفت بخش تقسیم میشود:
| بخش | اندازهی کار | توضیح |
|---|---|---|
| جدول واژههای کلیدی | ۶۰ املا | بهعلاوهی قاعدههای بافتی جایی که یک واژه دو نقش دارد |
| تابعها و متدهای داخلی | چند ده نام | len، push، free و مانند آنها، در یک جدول کامپایلر |
| نامهای کتابخانهی استاندارد | بیش از ۲۱ هزار برچسب | بزرگترین هزینه؛ اول به یک استاندارد نامگذاری نیاز دارد |
| پیامهای خطا | بیش از ۱٬۳۰۰ پیام | بهعلاوهی رقمهای بومی در کدهای خطا |
| قاعدههای خط در تحلیلگر واژگانی | کوچک اما ظریف | رقمها، نشانهگذاری، گیومهی رشته، شکلهای مختلف حروف |
| ویرایشگرها و دستور زبانها | ۱۱ افزونهی ویرایشگر، tree-sitter | از جدولهای کامپایلر ساخته میشوند، که کمک میکند |
| دوره و مستندات | ۴۷ درس، همهی صفحههای کتابخانه | فارسی هر دو را دارد؛ بستهی تازه از صفر شروع میکند |
پیش از همهی اینها یک گام مهندسی هم هست. سازوکار بستهها جدولمحور است، اما کد کنونی برای دقیقاً دو بسته نوشته شده: کامپایلر LP_EN و LP_FA را با نام بررسی میکند و برچسبها @en و @fa هستند. زبان سوم اول نیاز دارد که کامپایلر از «انگلیسی بهعلاوهی فارسی» به «انگلیسی بهعلاوهی N زبان» تعمیم یابد؛ بازسازیای محدود، نه طراحی دوباره.
هر زبان نامزد مشکلهای خودش را دارد:
- عربی نزدیکترین گزینه است، چون خطش با فارسی یکی است. تحلیلگر واژگانی از قبل رقمهای عربی-هندی را در عددها میپذیرد و «ي» و «ك» عربی را یکسانسازی میکند. کار تازه واژگان و دستور زبان است: عربی جنس و صیغهی مثنی دارد و همین نامگذاری یکدست تابعها را از فارسی سختتر میکند.
- ترکی خط لاتین دارد؛ پس کار تحلیلگر واژگانی کم است، اما قاعدههای حروف بزرگ و کوچکش با انگلیسی فرق دارد: i نقطهدار و بینقطه کوچکسازی ساده را خراب میکنند، و این برای هر مقایسهی نام بدون حساسیت به بزرگی و کوچکی حروف اهمیت دارد.
- چینی و ژاپنی میان واژهها فاصله نمیگذارند. نامهای چندکلمهای سلام به فاصله بهعنوان جداکننده تکیه دارند؛ پس قاعدهی اینکه یک نام کجا تمام میشود باید از نو اندیشیده شود.
- هندی و دیگر خطهای هندی نویسهها را در خوشههایی با هم ترکیب میکنند، و این بر نحوهی شمردن ستونها در ویرایشگرها برای نشان دادن محل خطا اثر میگذارد.
پرسش دشوار: آیا یک دستور زبان کافی است؟
مدل سلام برای همهی زبانها یک دستور زبان نگه میدارد. همین است که ترجمه را به یک عملیات در سطح توکن تبدیل میکند و یک کامپایلر را کافی نگه میدارد. اما معنایش این هم هست که دستور زبان از ترتیب واژههای انگلیسی پیروی میکند: واژهی کلیدی اول میآید، مانند if condition: و repeat 3:. فارسی زبانی با ترتیب فاعل، مفعول، فعل است و سلام باز هم اگر را در ابتدا میگذارد، که جواب میدهد چون جملههای شرطی فارسی هم با «اگر» شروع میشوند. زبانهایی که ترتیب طبیعیشان بیشتر فرق دارد، زیر یک دستور زبان مشترک کمتر طبیعی خوانده میشوند.
جایگزین، یعنی یک دستور زبان برای هر زبان، کد را طبیعیتر میکند و ویژگیای را میشکند که از همه مهمتر است: اینکه هر برنامهای را بتوان بهطور ماشینی ترجمه کرد و همهی برنامهها در یک زیستبوم شریک باشند. سلام دستور زبان مشترک را برگزید، و برای زبان دومی که جملهی شرطیاش با «اگر» شروع میشود این انتخاب هزینهی کمی دارد. برای زبان سوم یا چهارم، این مهمترین تنش طراحی است که باید پیش از نوشتن حتی یک واژهی کلیدی حل شود.
آیندهی برنامهنویسی چندزبانه و زبان سلام
این بخش نظر و چشمانداز است. سه جهت واقعبینانه به نظر میرسد:
- ابزارهای دوزبانهی عمیقتر پیش از زبانهای بیشتر. بستن شکافهای مترجم، تا یک فایل فارسی بدون اصلاح دستی به انگلیسی و دوباره به فارسی برگردد، مدل موجود را بیش از یک بستهی زبانی سوم تقویت میکند.
- اول پیامهای خطا به زبانهای بیشتر. پیامهای خطا بخشی از زباناند که مبتدیان بیش از همه میخوانند، و فهرست پیامها از دستور زبان جداست. ترجمهی پیامهای خطا از یک بستهی کامل بسیار ارزانتر است و به یادگیرندگانی کمک میکند که هنوز با واژههای کلیدی انگلیسی مینویسند.
- روشی مشترک برای افزودن بستههای زبانی. اگر کامپایلر به N بسته تعمیم یابد و یک فهرست کار مستند داشته باشد، جامعهای که املای خودش را میخواهد میتواند خودش کار را انجام دهد. بومیسازی در بیشتر پروژههای متنباز موفق همینطور کار میکند: هستهی پروژه قالب را تعیین میکند و گویشوران بومی محتوا را فراهم میکنند.
هیچکدام از اینها در نقشهی راه منتشرشدهای نیست. آنچه سلام نشان داده محدودتر و همچنان سودمند است: یک زبان کامل، خودمیزبان و همهمنظوره میتواند دو املای کامل داشته باشد، بیآنکه کامپایلر یا کتابخانهاش دو شاخه شود. نوشتههای طراحی زبان برنامهنویسی برای فارسی و عربی و برنامهنویسی راست به چپ به جزئیات سطح خط میپردازند و برنامهنویسی به زبان مادری بررسی میکند که اصلاً چرا کسی چنین چیزی میخواهد.
پرسشهای متداول دربارهی برنامهنویسی چندزبانه و زبان فارسی
زبان برنامهنویسی بومیشده چیست؟
زبان برنامهنویسی بومیشده به شما اجازه میدهد کد را با واژههای کلیدی و نامهای کتابخانه به زبانی انسانی غیر از انگلیسی بنویسید. سلام این کار را برای فارسی بهطور کامل انجام میدهد: واژههای کلیدی، گونههای داخلی، کتابخانهی استاندارد و خطاهای کامپایلر همه املای فارسی دارند و به همان برنامهای کامپایل میشوند که نسخهی انگلیسی.
آیا میتوان در یک فایل سلام انگلیسی و فارسی را با هم نوشت؟
واژههای کلیدی و نامهای کتابخانه در هر فایل باید به زبان همان فایل باشند؛ استفاده از نام انگلیسی کتابخانه در فایل فارسی خطایی است که راهنمایش املای فارسی را میگوید. اما فایلها و بستههای مختلف میتوانند به زبانهای مختلف باشند و باز هم در یک برنامه با هم کار کنند.
آیا زبان سلام از عربی پشتیبانی میکند؟
نه بهعنوان زبان واژههای کلیدی. دو بستهی زبانی سلام انگلیسی و فارسیاند. چون فارسی خط عربی را به کار میبرد، کامپایلر از قبل شکلهای عربی حروف و رقمها را مدیریت میکند و کتابخانهی استاندارد یک بستهی arabic برای یکسانسازی متنهای با خط عربی دارد، اما واژهی کلیدی عربی وجود ندارد.
افزودن یک زبان انسانی دیگر به سلام چقدر سخت است؟
واژههای کلیدی کار کوچکی است؛ نامهای کتابخانهی استاندارد (بیش از ۲۱ هزار برای فارسی) و بیش از ۱٬۳۰۰ پیام خطا کار بزرگاند، بهعلاوهی پشتیبانی ویرایشگرها و یک دورهی آموزشی. کامپایلر هم باید پیش از افزودن زبان سوم از دو بسته به چند بسته تعمیم یابد.