برنامهنویسی راست به چپ یعنی نوشتن کد منبع با خطی مثل فارسی، عربی یا عبری که متن در آن از لبهی راست سطر به سمت چپ پیش میرود. برای یک زبان برنامهنویسی این کار سه مسئلهی جدا دارد: کامپایلر باید خط را درست بخواند (حرف، رقم، نشانهگذاری)، کتابخانهی استاندارد باید متن آن خط را درست پردازش کند (طول، برش، یکسانسازی، جهت) و ویرایشگر باید سطری را که واژههای راست به چپ و نمادهای چپ به راست را با هم دارد به ترتیبی خوانا نشان دهد. زبان برنامهنویسی سلام، که میشود آن را سراسر به فارسی نوشت، ناچار بوده به هر سه پاسخ بدهد.
این مقاله هر لایه را با برنامههای قابل اجرا مرور میکند: تحلیلگر واژگانی سلام با کد فارسی چه میکند، رقمهای فارسی در عددها و در خروجی چطور کار میکنند، چرا طول یک رشته برابر شمار حرفهایش نیست و بستههای دوسویه (bidi)، یونیکد و عربی برای برنامههایی که با متن راست به چپ سروکار دارند چه میکنند. در پایان هم مسئلههایی را که حل نشدهاند میآورد.
کد راست به چپ چه فرقی دارد؟
یک سطر کد فارسی همان سطر انگلیسی نیست که وارونه نوشته شده باشد. دستوری مثل اگر ک > ۱۰: واژههای فارسی، یک دنبالهی رقم فارسی، یک عملگر ASCII و یک دونقطهی ASCII دارد. یونیکد این نویسهها را به ترتیبی که تایپ شدهاند (ترتیب منطقی) ذخیره میکند و ترتیب نمایش روی صفحه را به الگوریتم دوسویهی یونیکد (Unicode Bidirectional Algorithm یا bidi) میسپارد. این الگوریتم به هر نویسه یک ردهی جهت میدهد: حرفهای فارسی قویاً راست به چپاند، حرفهای لاتین قویاً چپ به راست، رقمها جهت ضعیف دارند و فاصله و بیشتر نشانهها خنثیاند و جهت اطرافشان را میگیرند.
کامپایلر همیشه فقط ترتیب منطقی را میبیند، پس برای کامپایلر کد راست به چپ موضوع مجموعه نویسهها و قاعدههاست. انسان ترتیب نمایش را میبیند، پس برای انسان موضوع نمایش است. بیشتر سردرگمی دربارهی «برنامهنویسی راست به چپ» از قاطی شدن این دو نگاه میآید.
فایلهای کد منبع و شناسهها
فایلهای کد سلام UTF-8 هستند. تحلیلگر واژگانی هر بایت بیرون از ASCII را بخشی از یک شناسه میداند، پس نامها را میشود به فارسی، عربی یا خطهای دیگر نوشت، و نام فارسی میتواند فاصله یا نیمفاصله (ZWNJ، نویسهی U+200C) داشته باشد:
جمع ردیفبا فاصله وجمعردیفبا نیمفاصله یک ناماند؛جمعردیفبه شکل چسبیده نام دیگری است و اگر اشتباه تایپ کنید، کامپایلر شکل فاصلهدار را پیشنهاد میدهد؛- حرفهای عربی
ي(U+064A) وك(U+0643) در نامها بای(U+06CC) وک(U+06A9) فارسی برابر حساب میشوند، پس متنی که با صفحهکلید عربی تایپ شده جور درمیآید؛ - اعراب اختیاری (حرکتها، مثل کسره) در نامها نادیده گرفته میشوند، پس
کِتابوکتابیک شناسهاند.
تحلیلگر واژگانی این قاعدهها را هنگام خواندن هر نام اعمال میکند: نیمفاصله به فاصله تبدیل میشود، «ی» و «ک» عربی به حرف فارسی و اعراب حذف میشود. تابع کوچکی است، اما بدون آن برنامهنویس فارسیزبان تمام روز با صفحهکلیدش کشتی میگرفت، و چون در تحلیلگر واژگانی جا دارد، هر ابزاری که روی آن ساخته شده (قالببند، مترجم، سرور زبان) از همین قاعدهها پیروی میکند. دلیل هر کدام از این قاعدهها در مقالهی طراحی زبان برنامهنویسی برای فارسی و عربی آمده است.
رقمهای فارسی و رقمهای عربی در زبان سلام
فارسی رقمهای خودش را دارد، ۰۱۲۳۴۵۶۷۸۹ (از U+06F0 تا U+06F9)، و عربی مجموعهی دیگری به کار میبرد، ٠١٢٣٤٥٦٧٨٩ (از U+0660 تا U+0669). این دو مجموعه برای بیشتر مقدارها شبیه هماند اما نویسههای متفاوتیاند. سلام هر دو را در عددها میپذیرد و هر دو را میشود با رقمهای ASCII مخلوط کرد. قاعدهها:
۱۲۵۰۰،٤٠٠و12500همه عدد صحیح معتبرند، در فایل فارسی و انگلیسی.- ممیز اعشار همیشه
.ASCII است:۳.۵معتبر است. جداکنندهی اعشار فارسی٫(U+066B) پذیرفته نمیشود، پس۱٫۵خطای کامپایل است، نه عددی که بیصدا چیز دیگری معنا بدهد. - عددها همیشه با رقم ASCII چاپ میشوند. تبدیل خروجی یک فراخوانی صریح است:
رشته.به رقم فارسی(str.ToPersianDigits) ورشته.به رقم انگلیسی(str.ToEnglishDigits). - تبدیل رشته به عدد صحیح،
به صحیح(to_int)، رقم فارسی را میپذیرد، پس ورودیای که با صفحهکلید فارسی تایپ شده درست تجزیه میشود.
این برنامهی فارسی همهی اینها را به کار میبرد:
واردسازی رشته
تابع ریشه:
بها := ۱۲۵۰۰
شمار := 3
جمع := بها * شمار + ٤٠٠
سرچاپ جمع
سرچاپ رشته.به رقم فارسی(«جمع: » + جمع + « ریال»)
سرچاپ "۲۵۰۰۰".به صحیح() + ۱
سرچاپ ۳.۵ * ۲
پایانسطر اول رقم فارسی، ASCII و عربی را در یک عبارت مخلوط میکند. سطر دوم متن نهایی را برای نمایش به رقم فارسی تبدیل میکند. چاپ پیشفرض با ASCII انتخابی عمدی است: خروجی برنامه اغلب به گزارشها، فایلها، برنامههای دیگر و پایانه میرود و همهی اینها رقم ASCII انتظار دارند.
جداکنندهها، گیومه و واژههای مقایسه
سه نشانهی دیگر مخصوص فایلهای فارسیاند:
- ویرگول فارسی
،(U+060C) هر جا که ویرگول ASCII کار میکند کار میکند: میان آرگومانها، پارامترها، عنصرهای آرایه، فیلدهای ساختار، اعضای جداشمار و الگوهای همخوانی. برنامهنویس فارسی برای نوشتن یک فراخوانی ناچار نیست چیدمان صفحهکلید را عوض کند. - گیومهی
«...»در فایل فارسی رشتهی لفظی است و همان گریزهای"..."را دارد. در فایل انگلیسی جداکننده نیست. - واژههای مقایسه. فایل فارسی میتواند مقایسهها را با واژه بنویسد:
بزرگتر(>)،کوچکتر(<)،برابر(==)،نابرابر(!=)،بزرگتربرابروکوچکتربرابر. انگلیسی هم همین واژهها را دارد (gt،lt،eq،neq،gte،lte).
واژههای مقایسه از آنچه به نظر میرسد مهمترند. یونیکد <، >، (، )، [، ] و نویسههای مشابه را آینهای علامت زده است: در یک بخش راست به چپ، نمایشگر قرینهی شکل نویسه را میکشد تا ( در خواندن راست به چپ هم پرانتز را باز کند. این برای نثر درست است و برای کد گیجکننده، چون خواننده دیگر مطمئن نیست یک علامت کوچکتر و بزرگتر به کدام سو اشاره میکند. واژه جهتی ندارد که قرینه شود. salam translate fa مقایسهها را به شکل واژه مینویسد و salam translate en آنها را به نماد برمیگرداند.
رشتهها: بایت در برابر حرف
رشته در سلام دنبالهای از بایتهای UTF-8 است. هر حرف فارسی دو بایت جا میگیرد، پس طول بایتی یک رشتهی فارسی تقریباً دو برابر شمار حرفهایش است. سلام طول بایتی را پیشفرض نگه میدارد (طول، زیررشته، اندیسگذاری)، چون فایل، سوکت و کتابخانههای C با بایت کار میکنند، و عملیات حرفآگاه را کنارش فراهم میکند:
واردسازی رشته
تابع ریشه:
واژه := "سلامِ"
سرچاپ "بایت:"، واژه.طول()
سرچاپ "نقطهکد:"، واژه.شمارنویسه()
سرچاپ "نویسهی دیداری:"، رشته.شمار خوشه(واژه)
هر ن در "سلام":
چاپ "[" + ن + "]"
پایان
سرچاپ ""
سرچاپ واژه.زیررشته نویسه(۰، ۲)، رشته.برعکس("سلام")
پایانسلامِ با کسره تمام میشود، اعرابی که نقطهکد جداگانهی خودش را دارد اما روی حرف پیش از خود کشیده میشود. پس سه پاسخ درست برای «این واژه چقدر طول دارد؟» وجود دارد: ۱۰ بایت، ۵ نقطهکد و ۴ نویسهای که کاربر میبیند (خوشهی نویسه یا Grapheme). حلقهی هر روی رشته نقطهکد به نقطهکد جلو میرود، زیررشته نویسه (char_substr) بر پایهی نقطهکد برش میزند، رشته.برعکس (str.Reverse) نقطهکدها را وارونه میکند و رشته.خوشه ها (str.Graphemes) متن را در همان سطحی که خواننده میبیند جدا میکند.
دام اصلی تابعهای بایتیاند: زیررشته (substr) با جایگاههای بایتی میتواند یک حرف فارسی را از وسط نصف کند و UTF-8 نامعتبر بسازد. وقتی متنی را که آدمها نوشتهاند برش میزنید، از متدهای نویسهای یا تابعهای نقطهکدی بستهی رشته استفاده کنید. درس رشتهها همهی این ابزارها را پوشش میدهد.
متن دوسویهی مخلوط و بستهی bidi
برنامههایی که متن نمایش میدهند، نه فقط ویرایشگر، با الگوریتم دوسویه روبهرو میشوند. یک برنامهی گفتوگو باید برای هر پیام تصمیم بگیرد چپچین شود یا راستچین؛ برنامهای که متن را روی تصویر یا در پایانه میکشد باید سطرهای مخلوط را خودش بازچینی کند، چون هیچ چیز دیگری این کار را نمیکند. کتابخانهی استاندارد سلام برای هر گام بستهای دارد:
یونیکد(Unicode) ردهی دوسویه، خط و دستهی هر نقطهکد را از پایگاه دادهی یونیکد ۱۶ میدهد؛دوسویه(bidi) الگوریتم دوسویهی کامل را اجرا میکند: تراز بند، تراز جاسازی هر نویسه، ترتیب دیداری و رشتهی بازچینیشده؛عربی(arabic) متن عربینویس را برای مقایسه و جستوجو یکسانسازی میکند.
رایجترین پرسش این است: «این سطر به کدام سو میرود؟» و پاسخ معمول، همان که dir="auto" در HTML به کار میبرد، جهت نخستین نویسهی قوی است:
واردسازی دوسویه
تابع ریشه:
سطرها := [
"func main:"،
"تابع ریشه:"،
"1405 سال خوبی است"،
"Salam یعنی سلام"،
"x := 42"،
]
هر سطر در سطرها:
سو := دوسویه.تراز بند(سطر، دوسویه.جهت خودکار) == ۱ ? "rtl" : "ltr"
سرچاپ سو، "|"، سطر
پایان
پایانسطر سوم با رقم شروع میشود که جهت ضعیفی دارد، پس واژهی فارسی بعد از آن تعیینکننده است. سطر چهارم با یک واژهی لاتین شروع میشود و برای همین چپ به راست است، هرچند بیشترش فارسی است. یک مرورگر هم با dir="auto" دقیقاً همینطور هر سطر را میچیند، و به همین دلیل یک سطر کد فارسی که با شناسهای انگلیسی شروع شود ممکن است ناگهان در جهت نادرست نمایش داده شود.
بستهی دوسویه آنچه را انجام نمیدهد هم مستند کرده است، که خودش فهرست بازبینی خوبی است: کروشهها را در خروجی بازچینیشده قرینه نمیکند (این کار را با یونیکد.کروشه جفت خودتان انجام میدهید)، حرفهای عربی را شکلدهی (Shaping) نمیکند و سطرشکنی هم انجام نمیدهد.
یکسانسازی متن فارسی و عربی
متن راست به چپ با بیش از یک املا از راه میرسد. یک واژهی فارسی ممکن است با «ی» و «ک» عربی، با یا بدون نیمفاصله، با کشیده یا با اعراب تایپ شده باشد. کامپایلر «ی» و «ک» را در شناسهها یکی میکند؛ برای داده، بستهی عربی یک کلید مقایسه میسازد:
واردسازی عربی
تابع ریشه:
ذخیرهشده := "برنامهنویسی کاربردی"
تایپشدهها := ["برنامهنويسي كاربردي"، "برنامهنویسی کاربردی"، "برنامهٔ نویسی"]
کلید := عربی.کلید جستجو(ذخیرهشده)
هر ت در تایپشدهها:
سرچاپ عربی.کلید جستجو(ت) == کلید، "|"، ت
پایان
پایانجستوجوی اول با حرفهای عربی تایپ شده و باز هم جور درمیآید. سومی عبارت دیگری است و جور درنمیآید. کلید را کنار متن اصلی ذخیره کنید، نه بهجای آن؛ این کلید عمداً بخشی از اطلاعات را دور میریزد.
ویرایشگرها کد راست به چپ را چطور نمایش میدهند
برای کامپایلر مهم نیست سطر چه شکلی دارد، اما برای کسی که آن را میخواند مهم است. برای نمایش کد فارسی دو رویکرد وجود دارد:
- بندهای راستچین و راست به چپ. کل ویرایشگر راست به چپ کار میکند و هر سطر یک بند راست به چپ است. واژههای کلیدی فارسی طبیعی خوانده میشوند و نامها و عددهای انگلیسی درون سطر را الگوریتم دوسویه چپ به راست میچیند. ویرایشگر آنلاین سلام وقتی زبانش فارسی باشد همین کار را میکند: صفحه، بخش کد و لایهی رنگآمیزی همه به
dir="rtl"میروند. بلوکهای کد همین سایت هم برای کد سلامی که فارسی دارد همینطورند. - سطرهای چپچین با الگوریتم دوسویه درون هر سطر. بسیاری از ویرایشگرهای کد رومیزی، از جمله VS Code، همهی سطرها را چپچین نگه میدارند و الگوریتم دوسویه را درون هر سطر اعمال میکنند. کد فارسی اینطور هم خواناست، اما سطرهایی که با شناسهی لاتین یا یک نماد شروع میشوند ممکن است درهمریخته به نظر برسند.
افزونههای سلام برای Visual Studio Code و ده ویرایشگر دیگر هر دو مجموعه واژهی کلیدی انگلیسی و فارسی را رنگآمیزی میکنند و از روی جدولهای خود کامپایلر تولید میشوند؛ صفحهی ابزارها را ببینید. اینکه متن چطور چیده شود به خود هر ویرایشگر بستگی دارد.
چه چیزهایی هنوز حل نشده است
- نویسههای کنترلی نامرئی دوسویه. یونیکد نویسههای کنترلیای دارد (مثل U+202E یا RIGHT-TO-LEFT OVERRIDE) که بی آنکه دیده شوند ترتیب نمایش را عوض میکنند. حملهی «Trojan Source» که در ۲۰۲۱ منتشر شد (CVE-2021-42574) از این نویسهها درون توضیحها و رشتهها استفاده میکرد تا کد طوری نمایش داده شود که با آنچه کامپایل میشود فرق کند. در سلام یک نویسهی کنترلی سرگردان در کد، شناسهی ناشناخته است و کامپایل نمیشود، اما کامپایلر کنونی آن را درون توضیحها و رشتههای لفظی بدون هشدار میپذیرد. امروز دفاع اصلی ویرایشگری است که این نویسهها را نشان بدهد.
- رقمهای خروجی ASCIIاند. برای ماشین درست است، اما هر برنامهای که عدد را به خوانندهی فارسیزبان نشان میدهد باید آن را تبدیل کند.
- جداکنندهی اعشار.
٫رد میشود؛ این کار از ابهام جلوگیری میکند، اما کسی را که آن را تایپ میکند غافلگیر میکند. - پشتیبانی ویرایشگرهای رومیزی یکسان نیست. تجربهی ویرایش کد راست به چپ به ویرایشگر بستگی دارد و بیشتر ویرایشگرها برای کد چپ به راست طراحی شدهاند.
برای جنبهی زبانی این داستان، یک زبان، دو خط را بخوانید و برای ابزاری که میان دو املا تبدیل میکند، درون salam translate را.
پرسشهای پرتکرار دربارهی برنامهنویسی راست به چپ
آیا میشود در کد سلام از رقم فارسی استفاده کرد؟
بله. رقمهای فارسی (۰ تا ۹) و رقمهای عربی (٠ تا ٩) در عددها معتبرند و میتوانند با رقم ASCII مخلوط شوند، چه در فایل فارسی و چه انگلیسی. ممیز اعشار باید . ASCII باشد و عددها با رقم ASCII چاپ میشوند، مگر آنکه با رشته.به رقم فارسی تبدیلشان کنید.
چرا طول یک رشتهی فارسی دو برابر درمیآید؟
طول بایتهای UTF-8 را میشمارد و هر حرف فارسی دو بایت است. برای شمار نقطهکدها از شمارنویسه و برای شمار نویسههایی که خواننده میبیند از رشته.شمار خوشه استفاده کنید.
از کجا بفهمم یک سطر متن راست به چپ است؟
نخستین نویسهی قوی آن را پیدا کنید، یعنی حرفی که جهت خودش را دارد. دوسویه.تراز بند(سطر، دوسویه.جهت خودکار) برای راست به چپ ۱ و برای چپ به راست ۰ برمیگرداند و همان قاعدهی dir="auto" در HTML را دنبال میکند.
کدام ویرایشگرها از کد راست به چپ سلام پشتیبانی میکنند؟
ویرایشگر آنلاین برای فارسی به چیدمان کامل راست به چپ میرود. ویرایشگرهای رومیزی با افزونهی سلام واژههای کلیدی فارسی را رنگآمیزی میکنند و کد فارسی را نشان میدهند، اما چیدمان سطرهای مخلوط به خود ویرایشگر بستگی دارد.