برنامهنویسی فارسی با زبان سلام یعنی یک برنامهی کامل، از تابع ورود تا فراخوانیهای کتابخانهی استاندارد و پیامهای خطایی که در جواب میخوانید، با خط فارسی نوشته شود و دقیقاً همان فایل اجرایی بومیای را بدهد که برنامهی انگلیسی میداد. سلام یک زبان برنامهنویسی دوزبانه است: هر واژهی کلیدی، گونهی داخلی، متد داخلی، بستهی کتابخانهی استاندارد و پیام کامپایلر یک املای انگلیسی و یک املای فارسی دارد، و هر دو املا در زیرساخت یک زباناند. نه گویش فارسی جداگانهای در کار است، نه کامپایلر جداگانه و نه پیشپردازندهای که پیش از کامپایل، متن فارسی را به انگلیسی بازنویسی کند.
این مقاله توضیح میدهد این کار در عمل چطور انجام میشود: کامپایلر دو زبان را چطور از هم تشخیص میدهد، دقیقاً چه چیزهایی نام فارسی دارند، خطاها چطور گزارش میشوند، بستهای که به یک زبان نوشته شده از زبان دیگر چطور به کار میرود و این طراحی کجا متوقف میشود.
«زبان برنامهنویسی دوزبانه» در سلام یعنی چه؟
بسیاری از پروژههایی که وعدهی «برنامهنویسی به زبان مادری» میدهند واژههای کلیدی را ترجمه میکنند و همانجا میایستند. برنامه هنوز printf یا len را صدا میزند، مستندات کتابخانه انگلیسی است و نخستین خطای کامپایل هم به انگلیسی میرسد. نوآموزی که فارسی را راحت و انگلیسی را کند میخواند، بعد از پنج دقیقه دوباره مشغول ترجمه در ذهن خودش است.
سلام راه وارونه را رفته است. شکل انگلیسی و شکل فارسی دو بستهی زبانی برای یک دستور زبان واحدند. تحلیلگر واژگانی (Lexer) فایل را با یکی از این دو بسته میخواند، واژهها را به توکنهای یکسان تبدیل میکند و از آن به بعد تجزیهگر، بررسیکنندهی گونه، تولیدکنندههای کد و زمان اجرا هیچ تفاوتی نمیبینند. اگر فارسی و if انگلیسی یک توکن میشوند؛ رشته فارسی و str انگلیسی یک گونهاند.
پیامدهای عملی این طراحی:
- یک فایل اجرایی کامپایلر،
salam، هر دو را میسازد؛ - یک کتابخانهی استاندارد به هر دو خدمت میکند و نامهای فارسی کنار نامهای انگلیسی تعریف شدهاند؛
- هر پیام کامپایلر به زبان همان فایل چاپ میشود؛
- ابزاری به نام
salam translateیک فایل را از یک املا به املای دیگر تبدیل میکند؛ - برنامهی انگلیسی میتواند از بستهای که به فارسی نوشته شده استفاده کند و برعکس.
یک برنامه به فارسی و به انگلیسی
این یک برنامهی کوچک به انگلیسی است: یک جداشمار، یک ساختار، تابعی با نام دوکلمهای، آرایهای از ساختارهای لفظی، حلقهی each و عبارت match.
enum Box: Small, Medium, Large end
struct Order:
pub item: str
pub price: int
pub count: int
end
func line total(o: Order): int:
ret o.price * o.count
end
func box for(total: int): Box:
if total >= 500:
ret Box.Large
else total >= 200:
ret Box.Medium
end
ret Box.Small
end
func main:
orders := [
Order { item = "Tea", price = 120, count = 2 },
Order { item = "Bread", price = 45, count = 3 },
Order { item = "Dates", price = 300, count = 1 },
]
mut sum := 0
each o in orders:
t := line total(o)
println o.item, t
sum += t
end
packing := match box for(sum):
Small: "a small box" end
Medium: "a medium box" end
Large: "a large box" end
end
println "Total:", sum, "in", packing
endو همین برنامه، به قلم یک برنامهنویس فارسیزبان. ساختار همان است و فقط واژهها عوض شدهاند. به رقمهای فارسی در عددها، ویرگول فارسی ، میان آرگومانها و فیلدها و گیومههای «...» دقت کنید که در فایل فارسی رشتهی لفظیاند.
جداشمار جعبه: کوچک، میانه، بزرگ پایان
ساختار سفارش:
همگانی کالا: رشته
همگانی بها: صحیح
همگانی شمار: صحیح
پایان
تابع جمع ردیف(س: سفارش): صحیح:
برگشت س.بها * س.شمار
پایان
تابع جعبه برای(جمع: صحیح): جعبه:
اگر جمع >= ۵۰۰:
برگشت جعبه.بزرگ
وگرنه جمع >= ۲۰۰:
برگشت جعبه.میانه
پایان
برگشت جعبه.کوچک
پایان
تابع ریشه:
سفارشها := [
سفارش { کالا = «چای»، بها = ۱۲۰، شمار = ۲ }،
سفارش { کالا = «نان»، بها = ۴۵، شمار = ۳ }،
سفارش { کالا = «خرما»، بها = ۳۰۰، شمار = ۱ }،
]
ناپایا کل := ۰
هر س در سفارشها:
ج := جمع ردیف(س)
سرچاپ س.کالا، ج
کل += ج
پایان
ظرف := همخوان جعبه برای(کل):
کوچک: «جعبهی کوچک» پایان
میانه: «جعبهی میانه» پایان
بزرگ: «جعبهی بزرگ» پایان
پایان
سرچاپ «جمع:»، کل، «در»، ظرف
پایانبرای خوانندهای که از سمت انگلیسی میآید، چند جفت واژه کار را آسان میکند: تابع همان func است، ریشه تابع ورود main، برگشت همان ret، ناپایا همان mut، هر ... در همان each ... in، همخوان همان match، همگانی همان pub، سرچاپ همان println و پایان همان end. عددهای خروجی در هر دو نسخه با رقم لاتین چاپ میشوند؛ سلام عدد را صرفنظر از زبان کد منبع یکجور چاپ میکند و هر وقت بخواهید، بستهی رشته آن را به رقم فارسی تبدیل میکند (نگاه کنید به برنامهنویسی راست به چپ در سلام).
کامپایلر از کجا میفهمد فایل به چه زبانی است؟
نه تنظیمی در سطح پروژه برای فارسی وجود دارد و نه پسوند فایل جداگانهای. کامپایلر برای هر فایل جداگانه و به این ترتیب تصمیم میگیرد:
- خط زبان. اگر یکی از هشت خط نخست فایل توضیح
// lang: enیا// زبان: فارسیباشد، تکلیف روشن است. شکل مخلوطی مثل// lang: faخطای کامپایل است، تا این نشانه به اشتباه نیمهترجمه نماند. - شمردن واژههای کلیدی. در غیر این صورت تحلیلگر واژگانی فایل را با هر دو بستهی زبانی توکنبندی میکند و میشمارد هر کدام چند واژهی کلیدی میشناسد. بستهای که واژهی کلیدی بیشتری پیدا کند برنده است. فایل فارسی پر از
تابعوپایانتقریباً هیچ واژهی کلیدی انگلیسی ندارد، پس در عمل این حدس هیچوقت به هم نزدیک نمیشود. - پرچم صریح.
salam build app.salam --lang=faبستهی فارسی را اجباری میکند.
تشخیص برای هر فایل جداگانه انجام میشود و همین است که ترکیب دو زبان را ممکن میکند: یک پروژه میتواند فایلهای انگلیسی و فارسی را کنار هم داشته باشد و هر فایل با بستهی زبانی خودش خوانده میشود.
salam new هم از همین ایده پیروی میکند. salam new shop --lang=fa پروژهای میسازد که main.salam آن با تابع ریشه: شروع میشود و "سلام، دنیا!" را چاپ میکند، و پیامهایی که خود salam new چاپ میکند هم فارسیاند.
در زبان سلام چه چیزهایی نام فارسی دارند؟
جدول زیر هر چیزی را که با زبان فایل عوض میشود، و آنچه عوض نمیشود، فهرست میکند.
| بخش زبان | انگلیسی | فارسی |
|---|---|---|
| واژههای کلیدی | func, if, else, until, repeat, each, match, end | تابع، اگر، وگرنه، تا، تکرار، هر، همخوان، پایان |
| تابع ورود | main | ریشه |
| مقدارهای لفظی | true, false, null, this | درست، نادرست، پوچ، این |
| گونههای پایه | int, str, f64, bool | صحیح، رشته، اعشار۶۴، منطقی |
| ظرفها | Vector<T>, HashMap<K, V> | وکتور<T>، نگاشت<K, V> |
| متدهای داخلی | push, len, free, split | بیفزا، طول، آزادکن، بشکاف |
| تابعهای داخلی | len, sizeof, spawn | طول، اندازهگونه، نخساز |
| واژههای منطق و مقایسه | and, or, not, gt, eq | و، یا، وارونه، بزرگتر، برابر |
| کتابخانهی استاندارد | str.Split, math.Sqrt | رشته.تفکیک کردن، ریاضی.جذر |
| ثابتهای زمان کامپایل | SALAM_OS_LINUX | سلام سیستم لینوکس |
| پیامهای کامپایلر | error[E001]: unknown identifier | خطا[خطا۰۰۱]: شناسهی ناشناخته |
آنچه در هر دو یکسان میماند: عملگرها و نشانهها (+، :=، ==، [ ])، نامهایی که خودتان برای متغیر و تابع و گونه انتخاب میکنید، محتوای رشتهها، و نام تابعهای C که در بلوک درونداد (extern) تعریف میشوند، چون اینها نامهایی در یک کتابخانهی C هستند.
کتابخانهی استاندارد بیشترین کار را میبرد و کامل است. هر یک از ۱۹۴ پوشهی بسته در std/ کنار نام انگلیسیاش یک نام فارسی تعریف میکند و تابعهای عمومی هم همین جفت برچسب را دارند:
@en "ToPersianDigits"
@fa "به رقم فارسی"
pub func ToPersianDigits(s: str): str: ret _convert_digits(s, _DIGIT_PERSIAN_BASE) endبرنامهی فارسی هر بسته را با نام فارسیاش وارد میکند و نامهای فارسی را صدا میزند. این برنامه با رشته (بستهی str) یک جمله را به واژههایش میشکند و بلندترین واژه را پیدا میکند:
واردسازی رشته
تابع بلندترین(واژهها: وکتور<رشته>): رشته:
ناپایا بهترین := ""
هر و۱ در واژهها:
اگر رشته.طول(و۱) > رشته.طول(بهترین):
بهترین = و۱
پایان
پایان
برگشت بهترین
پایان
تابع ریشه:
جمله := «برنامهنویسی به زبان مادری»
واژهها := رشته.تفکیک کردن(جمله، " ")
دیرکن واژهها.آزادکن()
سرچاپ «شمار واژهها:»، واژهها.طول()
سرچاپ «بلندترین:»، بلندترین(واژهها)
سرچاپ رشته.بزرگ("salam")
پایانرشته.تفکیک کردن همان str.Split است، رشته.بزرگ همان str.Upper و دیرکن همان defer. کدی که در کتابخانه اجرا میشود همان کدی است که برنامهی انگلیسی صدا میزند. نام متغیر و۱ هم تصادفی نیست: و به معنای and واژهی رزروشده است و نمیتواند بهتنهایی نام باشد.
پیامهای خطا به فارسی و انگلیسی
پیامهای خطای کامپایلر بخشی از زباناند که نوآموز بیشتر از هر چیز دیگری میخواند، پس سلام آنها را هم ترجمه کرده است. کامپایلر ۱٬۲۹۹ پیام دارد که هر کدام نسخهی فارسی دارند، و زبان پیام را از روی فایل انتخاب میکند. این همان اشتباه، یعنی متغیر تعریفنشده، در یک برنامهی انگلیسی است:
error[E001]: unknown identifier 'y'; did you mean 'x'?
--> main.salam:3:13
|
3 | println y
| ^
|
= fix: Change to 'x'و در یک برنامهی فارسی، که حتی کد خطا هم با رقم فارسی نوشته میشود:
خطا[خطا۰۰۱]: شناسهی ناشناخته 'کتابها'؛ منظورتان 'کتاب ها' بود؟
--> main.salam:5:11
|
5 | سرچاپ کتابها
| ^
|
= اصلاح: تغییر به 'کتاب ها'کدهای خطا در هر دو زبان یکیاند (E001 همان خطا۰۰۱ است)، پس فارسیزبان میتواند مستندات انگلیسی یک کد را جستوجو کند و نگهدارندهی انگلیسیزبان هم میتواند گزارش باگی را که به فارسی نوشته شده دنبال کند. --error-lang=fa پیامهای یک فایل انگلیسی را به فارسی چاپ میکند؛ این در کلاسی که نمونههای استاد انگلیسی است به کار میآید.
پیامها از وجود دو زبان هم خبر دارند. صدا زدن رشته.Len در فایل فارسی خطایی میدهد که میگوید شناسه باید فارسی باشد و نام فارسی، طول، را بهعنوان اصلاح پیشنهاد میکند؛ صدا زدن str.طول در فایل انگلیسی هم پیام قرینهاش را میگیرد. salam check --fix این اصلاحها را مستقیم در فایل اعمال میکند.
ترکیب بستههایی که به هر دو زبان نوشته شدهاند
چون زبان برای هر فایل جداگانه انتخاب میشود، یک پروژه میتواند هر دو را با هم داشته باشد. نمونهی هرروزهاش خود کتابخانهی استاندارد است: به انگلیسی نوشته شده و هر برنامهی فارسی آن را از راه نامهای @fa به کار میبرد. بستههای خودتان هم همینطور کار میکنند. این یک کتابخانهی کوچک به فارسی است، abzar.salam، که به تابع عمومیاش هر دو نام را میدهد:
بسته ابزار
@en "Quadruple"
@fa "چهاربرابر"
همگانی تابع چهاربرابر(ع: صحیح): صحیح:
برگشت ع * ۴
پایانیک برنامهی انگلیسی آن را با نام مستعاری که خودش انتخاب میکند فرا میخواند و نام انگلیسی را صدا میزند:
include tools "abzar.salam"
func main:
println tools.Quadruple(5)
endاین برنامه 20 را چاپ میکند. قاعده درون یک فایل یکدست است: نامهایی که به خود زبان یا به بستهای با دو املا تعلق دارند باید به زبان همان فایل نوشته شوند، اما نامهایی که خودتان میسازید میتوانند به هر خطی باشند. یک فایل انگلیسی میتواند متغیری به نام قیمت داشته باشد و یک فایل فارسی میتواند تابعی به نام parse را صدا بزند؛ کامپایلر هر دو را شناسهی معمولی میداند.
درس بستهها بسته، واردسازی و فراخوانی را با جزئیات توضیح میدهد.
تبدیل یک فایل با salam translate
salam translate fa app.salam فایل app.fa.salam را کنار فایل اصلی مینویسد که در آن واژههای کلیدی، گونهها، تابع ورود، متدهای داخلی و نامهای کتابخانهی استاندارد فارسی شدهاند، و salam translate en راه برگشت است. این ابزار روی رشتهی توکنها کار میکند، پس توضیحها، رشتهها، قالببندی و شناسههای خود شما دستنخورده میمانند و یک رفتوبرگشت، بهجز خط زبانی که ابزار به ابتدای فایل اضافه میکند، کد اصلی را بدون تغییر برمیگرداند. این ابزار مقالهی خودش را دارد: درون salam translate.
جزئیات ویژهی کدنویسی فارسی
چند چیز فقط به این دلیل وجود دارند که فارسی متفاوت از انگلیسی نوشته میشود:
- رقمها. رقمهای فارسی
۰۱۲۳۴۵۶۷۸۹و رقمهای عربی٠١٢٣٤٥٦٧٨٩در عددها پذیرفته میشوند و میتوانند با رقمهای لاتین مخلوط شوند. ممیز اعشار همیشه.است و جداکنندهی اعشار فارسی٫پذیرفته نمیشود. - ویرگول فارسی
،هر جا که,کار میکند کار میکند: آرگومانها، پارامترها، آرایه و ساختار لفظی، اعضای جداشمار و الگوهای همخوانی. - گیومه.
«...»در فایل فارسی رشتهی لفظی است و با"..."هیچ فرقی ندارد. - نامهای چندکلمهای. شناسهها میتوانند فاصله یا نیمفاصله (ZWNJ) داشته باشند و این دو جای هم را میگیرند:
کتاب هاوکتابهایک ناماند. حرفهای عربیيوكهمانیوکفارسی حساب میشوند، پس کدی که با صفحهکلید عربی تایپ شده باز هم با نامها جور درمیآید.
چرایی این قاعدهها موضوع مقالهی طراحی زبان برنامهنویسی برای فارسی و عربی است.
طراحی دوزبانهی سلام کجا متوقف میشود؟
محدودیتهای واقعی، در نسخهی کنونی:
- فقط دو زبان. کامپایلر دقیقاً دو بستهی زبانی دارد، انگلیسی و فارسی. مجموعه واژهی کلیدی عربی، اردو یا ترکی وجود ندارد، هرچند تحلیلگر واژگانی شکلهای عربی حرفها و رقمها را میپذیرد.
- خروجی خودکار بومیسازی نمیشود. عدد و مقدار منطقی در هر دو زبان با ASCII چاپ میشوند (
12،true). رقم فارسی در خروجی با یک فراخوانی کتابخانه به دست میآید، نه بهطور پیشفرض. - مترجم همه چیز را پوشش نمیدهد. چند متد رشتهای جدیدتر هنوز در جدول آن نیستند؛ در این حالت کامپایلر نام انگلیسی باقیمانده را همراه با اصلاح فارسی گزارش میکند، همانطور که در مقالهی ترجمه نشان داده شده.
- بیشتر کدهای منتشرشده انگلیسیاند. کامپایلر، کتابخانهی استاندارد و بیشتر نمونههای گیتهاب به انگلیسی نوشته شدهاند. نوآموز فارسیزبان دیر یا زود با کد انگلیسی روبهرو میشود و مترجم دقیقاً برای همین ساخته شده است.
- سلام هنوز پیش از نسخهی ۱٫۰ است. زبان بین نسخهها هنوز تغییر میکند، در هر دو املا.
معرفی زبان سلام بقیهی زبان را پوشش میدهد و آموزش رایگان آن را از نخستین برنامه یاد میدهد.
پرسشهای پرتکرار دربارهی برنامهنویسی فارسی با سلام
آیا میشود یک برنامهی کامل را با سلام به فارسی نوشت؟
بله. واژههای کلیدی، گونهها، تابع ورود (ریشه)، متدهای داخلی، همهی بستههای کتابخانهی استاندارد و پیامهای کامپایلر همه نام فارسی دارند. فایل فارسی به هیچ واژهی انگلیسی نیاز ندارد، جز نام تابعهای C که خودتان تعریف میکنید.
آیا نسخهی فارسی کندتر یا محدودتر از نسخهی انگلیسی است؟
نه. تحلیلگر واژگانی هر دو املا را به توکنهای یکسان تبدیل میکند، پس تجزیهگر، بررسیکنندهی گونه و تولیدکنندههای کد هرگز نمیبینند به کدام زبان نوشتهاید. فایلهای اجرایی یکیاند.
آیا باید برای کل پروژه یک زبان انتخاب کنم؟
نه. زبان برای هر فایل جداگانه تشخیص داده میشود، پس یک پروژه میتواند فایلهای انگلیسی و فارسی داشته باشد. بستهای که به فارسی نوشته شده از برنامهی انگلیسی قابل استفاده است و برعکس، به شرط آنکه نامهای عمومیاش هر دو املا را داشته باشند.
آیا سلام یک زبان برنامهنویسی عربی هم هست؟
نه به همان معنا. مجموعه واژهی کلیدی عربی وجود ندارد. فارسی و عربی یک خط مشترک دارند و سلام رقمهای عربی و شکل عربی «ی» و «ک» را در نامهای فارسی میپذیرد، و کتابخانهی استاندارد بستهای به نام arabic برای یکسانسازی متن عربینویس دارد.