درس ۲۱ از ۴۹ · برنامه‌های بزرگ‌تر

ترجمه‌ی شرطی

گاهی کد باید روی ویندوز، مک و لینوکس کمی فرق کند، یا نسخه‌ی اشکال‌زدایی با نسخه‌ی نهایی. سلام این انتخاب را در زمان ترجمه انجام می‌دهد؛ شاخه‌ای که به کار نمی‌آید اصلاً وارد برنامه نمی‌شود و هیچ هزینه‌ای در زمان اجرا ندارد.

اگر در زمان ترجمه

اگر در سطح بالای پرونده، با شرطی که هنگام ترجمه معلوم است، فقط شاخه‌ی درست را نگه می‌دارد. هر چه درون آن تعریف شود، از ثابت و متغیر سراسری گرفته تا بلوک درون‌داد و تابع، فقط روی سیستمی وجود دارد که آن شاخه برایش انتخاب شده است. در این نمونه هر سیستم جداکننده‌ی مسیر خودش را می‌گیرد و باقی برنامه بی‌آنکه بداند کجا اجرا می‌شود از جداکننده استفاده می‌کند:

comptime.salam
واردسازی رشته

اگر سلام سیستم ویندوز:
    پایا جداکننده := "\\"
وگرنه:
    پایا جداکننده := "/"
پایان

تابع ریشه:
    مسیر := "home" + جداکننده + "sara" + جداکننده + "notes.txt"
    تکه‌ها := رشته.تفکیک کردن(مسیر، جداکننده)
    دیرکن تکه‌ها.آزادکن()
    سرچاپ تکه‌ها.طول()، "تکه"
    سرچاپ تکه‌ها.بگیر(۲)
پایان
خروجی3 تکه notes.txt

شرط اگر سطح بالا باید چیزی باشد که مترجم خودش بتواند حساب کند. نوشتن اگر شمار > ۳: در سطح بالا خطای «top-level if condition must be a compile-time constant» می‌دهد.

پرچم‌های آماده

مترجم چند پرچم منطقی تعریف می‌کند که سیستم مقصد را توصیف می‌کنند. یک پرچم سیستم‌عامل و یک پرچم پردازنده درست است، به اضافه‌ی پرچم‌های خانواده: لینوکس و مک سلام سیستم یونیکس را هم روشن می‌کنند، اندروید سلام سیستم لینوکس را هم، و مک و بی‌اس‌دی‌ها سلام سیستم بی‌اس‌دی را. در برنامه‌ی فارسی باید نام فارسی را بنویسید؛ نام انگلیسی در پرونده‌ی فارسی شناخته نمی‌شود و پیام خطا نام فارسی‌اش را پیشنهاد می‌دهد.

پرچمنام انگلیسیدرست است وقتی برای این ساخته شود
سلام سیستم ویندوزSALAM_OS_WINDOWSویندوز، همراه با سلام سیستم ویندوز۶۴ یا سلام سیستم ویندوز۳۲
سلام سیستم لینوکسSALAM_OS_LINUXلینوکس و اندروید
سلام سیستم مکSALAM_OS_MACمک
سلام سیستم اندرویدSALAM_OS_ANDROIDاندروید
سلام سیستم فری‌بی‌اس‌دی، سلام سیستم اوپن‌بی‌اس‌دی، سلام سیستم نت‌بی‌اس‌دیSALAM_OS_FREEBSD، SALAM_OS_OPENBSD، SALAM_OS_NETBSDهر کدام از بی‌اس‌دی‌ها
سلام سیستم بی‌اس‌دیSALAM_OS_BSDمک و بی‌اس‌دی‌ها
سلام سیستم یونیکسSALAM_OS_UNIXهمه‌ی سیستم‌های شبه‌یونیکس، وب‌اسمبلی هم
سلام سیستم وب‌اسمبلیSALAM_OS_WASMوب‌اسمبلی
سلام معماری ایکس۶۴، سلام معماری ایکس۸۶SALAM_ARCH_X64، SALAM_ARCH_X86اینتل و ای‌ام‌دی ۶۴ و ۳۲ بیتی
سلام معماری آرم۶۴، سلام معماری آرمSALAM_ARCH_ARM64، SALAM_ARCH_ARMآرم ۶۴ و ۳۲ بیتی
سلام معماری ریسک‌وی۶۴SALAM_ARCH_RISCV64ریسک‌وی ۶۴ بیتی
سلام معماری وب‌اسمبلیSALAM_ARCH_WASMوب‌اسمبلی
سلام ایمنSALAM_SAFEهر ساختی که بدون --release باشد

چند نام دیگر به جای درست و نادرست مقدار دارند. سلام سیستم متنی است مثل "windows"، "linux"، "mac"، "android" یا "freebsd"، و سلام معماری یکی از "x64"، "x86"، "arm64"، "arm"، "riscv64" یا "wasm". این مقدارها همیشه انگلیسی‌اند. برنامه‌ای که به جاوااسکریپت ترجمه می‌شود (salam js و اسکریپت‌های صفحه) برای هر دو "js" می‌گیرد و همه‌ی پرچم‌های سیستم و معماری در آن نادرست است، چون همان کد در هر مرورگری یا در Node اجرا می‌شود. سلام نگارش نگارش مترجم به شکل متن است، مثلاً "0.5.0"، و سلام کد نگارش همان نگارش به شکل عدد، 500. فهرست کامل این نام‌ها در واژه‌نامه آمده است.

درون تابع

همین اگر درون بدنه‌ی تابع هم کار می‌کند. وقتی شرط فقط از این پرچم‌ها ساخته شده باشد، مترجم شاخه‌ی درست را نگه می‌دارد و بقیه را دور می‌ریزد، درست مثل سطح بالا. با وگرنهی شرط‌دار می‌شود سیستم‌ها را پشت هم آورد:

comptime-inline.salam
تابع پوشه تنظیمات(برنامه: رشته): رشته:
    اگر سلام سیستم ویندوز:
        برگشت "C:\\Users\\Public\\AppData\\" + برنامه
    وگرنه سلام سیستم مک:
        برگشت "/Users/Shared/Library/Application Support/" + برنامه
    وگرنه:
        برگشت "/etc/" + برنامه
    پایان
پایان

تابع ریشه:
    سرچاپ پوشه تنظیمات("salam")
    اگر سلام سیستم یونیکس:
        سرچاپ "دسترسی: chmod 600"
    پایان
پایان

شاخه‌های سیستم‌عامل را همیشه با وگرنه ببندید. تابعی که اگر سلام سیستم ویندوز: برگشت ... پایان می‌نویسد و بعد کد یونیکس را ادامه می‌دهد روی لینوکس بی‌مشکل ساخته می‌شود، اما روی ویندوز آن اگر به «همیشه برگرد» تبدیل می‌شود و کد بعدش خطای E070، کد دست‌نیافتنی، می‌گیرد. با وگرنه هر دو ساخت تمیزند.

شاخه‌ای که نگه داشته می‌شود بلوک جداگانه‌ای نیست و هر چه درونش تعریف شود پس از اگر هم دیده می‌شود. می‌توانید در شاخه‌ی ویندوز کاربر := "USERNAME" و در شاخه‌ی دیگر کاربر := "USER" بنویسید و پایین‌تر از کاربر استفاده کنید.

پرچم‌ها به‌عنوان مقدار معمولی

بیرون از اگر این نام‌ها ثابت‌های معمولی‌اند. می‌شود چاپشان کرد، در متغیر نگهشان داشت، در ترابرد به کارشان برد یا با شرطی که در زمان اجرا سنجیده می‌شود ترکیبشان کرد، مثل اگر سلام سیستم ویندوز و تلاش‌ها > ۰: که فقط نیمه‌ی دومش برای زمان اجرا می‌ماند:

comptime-value.salam
تابع خانواده(): رشته:
    ترابرد سلام سیستم:
        "linux"، "android":
            برگشت "لینوکس"
        پایان
        "mac"، "freebsd"، "openbsd"، "netbsd":
            برگشت "بی‌اس‌دی"
        پایان
        "windows":
            برگشت "ویندوز"
        پایان
        وگرنه:
            برگشت "دیگر"
        پایان
    پایان
پایان

تابع ریشه:
    سرچاپ "ساخته‌شده برای"، سلام سیستم، "روی"، سلام معماری
    سرچاپ "خانواده:"، خانواده()
    پهن := سلام معماری ایکس۶۴ یا سلام معماری آرم۶۴
    سرچاپ "۶۴ بیتی:"، پهن
    سرچاپ "سلام"، سلام نگارش
پایان

خروجی این برنامه به سیستمی بستگی دارد که برایش ساخته می‌شود، برای همین اینجا بدون خروجی آمده است. روی یک رایانه‌ی لینوکس ۶۴ بیتی چاپ می‌کند ساخته‌شده برای linux روی x64، بعد خانواده: لینوکس، ۶۴ بیتی: true و نگارش مترجم.

پرچم‌های خودتان با ‎-D

پرچم‌های خودتان را هم می‌توانید از خط فرمان بدهید. -Dنام پرچمی می‌سازد که درست است و -Dنام=مقدار پرچمی که متن نگه می‌دارد. نام پرچم می‌تواند فارسی باشد. تعریف‌شده(نام) می‌پرسد آیا پرچم اصلاً داده شده است یا نه:

comptime-define.salam
اگر تعریف‌شده(حالت) و حالت == "سریع":
    پایا نمونه‌ها := ۱۰
وگرنه:
    پایا نمونه‌ها := ۱۰۰۰
پایان

تابع ریشه:
    سرچاپ "نمونه‌ها:"، نمونه‌ها
    اگر تعریف‌شده(پرحرف):
        سرچاپ "خروجی مفصل روشن است"
    وگرنه:
        سرچاپ "برای جزئیات بیشتر -Dپرحرف را بدهید"
    پایان
پایان
خروجینمونه‌ها: 1000 برای جزئیات بیشتر -Dپرحرف را بدهید

این خروجی یک salam run ساده است. با salam run app.salam -Dحالت=سریع -Dپرحرف همین برنامه نمونه‌ها: 10 و خروجی مفصل روشن است را چاپ می‌کند. راه معمول برای بیرون نگه داشتن یک امکان، یک قلاب آزمایشی یا گزارش‌های اضافه از ساخت عادی همین است.

دو نکته مهم است. در سطح بالا پرچمی که کسی تعریفش نکرده نادرست حساب می‌شود، اما درون تابع نام ناشناخته همچنان ناشناخته است: اگر پرحرف: بدون -Dپرحرف خطای E001 می‌دهد. مقایسه‌ی پرچمِ تعریف‌نشده با متن، مثل حالت == "سریع"، هم اصلاً شرط زمان ترجمه نیست. پرسیدن تعریف‌شده(...) پیش از آن، مثل نمونه‌ی بالا، در هر دو جا درست کار می‌کند.

ثابت‌ها از خط فرمان

پرچم تعیین می‌کند کدام کد ترجمه شود. اگر فقط یک مقدار می‌خواهید، مثل شماره‌ی درگاه یا برچسب ساخت، از -d استفاده کنید: salam build app.salam -dPORT=9090 -dLABEL=beta ثابت‌های PORT و LABEL را به برنامه می‌دهد، انگار در پرونده نوشته باشید پایا PORT := 9090. مقدار اگر شبیه عدد یا true و false باشد همان خوانده می‌شود و در غیر این صورت متن است؛ -dDEBUG تنها یعنی درست. نام این ثابت‌ها باید با حروف بزرگ لاتین نوشته شود، حتی در برنامه‌ی فارسی، و ثابتی که خود پرونده تعریف کرده بر مقدار -d مقدم است.

بررسی‌های مخصوص اشکال‌زدایی

سلام ایمن در هر ساختی که بدون --release باشد درست است. پس جای مناسبی است برای بررسی‌هایی که هنگام نوشتن برنامه لازمشان دارید ولی نمی‌خواهید هزینه‌شان را در برنامه‌ی نهایی بپردازید:

comptime-safe.salam
اگر سلام ایمن:
    پایا ساخت := "اشکال‌زدایی"
وگرنه:
    پایا ساخت := "انتشار"
پایان

تابع جستجو(عددها: صحیح[]، خواسته: صحیح): صحیح:
    اگر سلام ایمن:
        تکرار طول(عددها) - ۱ در ای:
            اگر عددها[ای] > عددها[ای + ۱]:
                نادرست‌سرچاپ "جستجو: ورودی مرتب نیست"
                بشکن
            پایان
        پایان
    پایان
    ناپایا پایین := ۰
    ناپایا بالا := طول(عددها) - ۱
    تا پایین <= بالا:
        وسط := (پایین + بالا) / ۲
        اگر عددها[وسط] == خواسته:
            برگشت وسط
        وگرنه عددها[وسط] < خواسته:
            پایین = وسط + ۱
        وگرنه:
            بالا = وسط - ۱
        پایان
    پایان
    برگشت -۱
پایان

تابع ریشه:
    سرچاپ "ساخت:"، ساخت
    سرچاپ جستجو([۲، ۵، ۸، ۱۳، ۲۱]، ۱۳)
    سرچاپ جستجو([۹، ۴، ۷، ۱]، ۹)
پایان
خروجیساخت: اشکال‌زدایی 3 جستجو: ورودی مرتب نیست -1

جستجوی دودویی فقط روی ورودی مرتب درست کار می‌کند. در ساخت اشکال‌زدایی حلقه‌ی اضافه آرایه‌ی نامرتب را می‌بیند و خبر می‌دهد، و همین روشن می‌کند چرا جستجوی دوم عددی را که در آرایه هست پیدا نکرد. با --release این حلقه اصلاً ترجمه نمی‌شود و برنامه فقط دو نتیجه را چاپ می‌کند.

آزمودن سیستم‌های دیگر

شاخه‌هایی که به کار نمی‌آیند پیش از بررسی گونه‌ها دور ریخته می‌شوند، پس اشتباهِ درون آن‌ها روی رایانه‌ی شما پنهان می‌ماند: صدا زدن تابعی که وجود ندارد، اگر در شاخه‌ی ویندوز باشد، روی لینوکس بی‌هیچ پیامی ترجمه می‌شود. برای آزمودن سیستمی که در دسترس ندارید، برایش با --target ترجمه کنید:

  • salam obj app.salam --target=x86_64-windows-gnu شاخه‌های ویندوز را بررسی می‌کند
  • salam obj app.salam --target=aarch64-apple-darwin شاخه‌های مک را بررسی می‌کند

فرمان obj پس از ساختن پرونده‌ی شیء می‌ایستد، پس به پیونددهنده و کتابخانه‌های سیستم مقصد نیازی ندارد و هر خطایی که نشان دهد خطای واقعی است.

چند قاعده‌ی سرانگشتی

  • تفاوت سیستم‌ها را در یک جای کوچک نگه دارید، مثل یک ثابت یا یک تابع کوتاه، و باقی برنامه را همه‌جا یکسان.
  • هر اگر سیستم‌عامل را با وگرنه ببندید، به‌خصوص وقتی شاخه‌ای برمی‌گردد.
  • پرچم‌های خودتان را با تعریف‌شده(نام) بسنجید تا ساخت بدون آن‌ها هم ترجمه شود.
  • پیش از هم‌رسانی کد ویژه‌ی سیستم‌ها، آن را یک بار با --target برای هر سیستمی که پشتیبانی می‌کنید بسازید.

برای صدا زدن کتابخانه‌ی C متفاوت روی هر سیستم، خود اعلان‌های درون‌داد را درون یک اگر سطح بالا بگذارید. درس بعد، همکاری با C، از همین‌جا شروع می‌کند.