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

بسته‌ها و پرونده‌ها

برنامه‌های بزرگ در چند پرونده نوشته می‌شوند. هر پرونده به یک بسته تعلق دارد و هر بسته فقط چیزهایی را با بقیه‌ی برنامه شریک می‌شود که همگانی کرده است.

بسته

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

pkg-lib.salam
بسته ابزار

همگانی پایا نسخه := "۱.۰"

تابع دوبرابر(ع: صحیح): صحیح:
    برگشت ع * ۲
پایان

همگانی تابع چهاربرابر(ع: صحیح): صحیح:
    برگشت دوبرابر(دوبرابر(ع))
پایان

این پرونده ریشه ندارد و به‌تنهایی اجرا نمی‌شود؛ قرار است برنامه‌ای آن را فراخوانی کند، همان‌طور که در بخش بعد می‌بینید.

فراخوانی پرونده‌های خودتان

فراخوانی نام "پرونده.salam" پرونده‌ای از پروژه‌ی خودتان را با یک نام کوتاه به برنامه می‌آورد و با نام.چیزی به عضوهای همگانی‌اش می‌رسید. مسیر نسبت به پرونده‌ای است که فراخوانی را نوشته.

pkg-include.salam
فراخوانی ابزار "pkg-lib.salam"

تابع ریشه:
    سرچاپ ابزار.چهاربرابر(۵)
    سرچاپ "نسخه‌ی ابزار:", ابزار.نسخه
پایان
خروجی20 نسخه‌ی ابزار: ۱.۰
  • نوشتن نام اختیاری است: فراخوانی "lib/money.salam" با نام پرونده در دسترس است، یعنی money.چیزی. هر جا نام پرونده بلند یا نامفهوم است، نام کوتاهی برایش بگذارید.
  • مسیری که با @/ شروع شود از ریشه‌ی پروژه حساب می‌شود، نه از پرونده‌ی فعلی؛ پس فراخوانی "@/lib/money.salam" از هر پوشه‌ای در پروژه به همان پرونده می‌رسد. پروژه را از پوشه‌ی خودش بسازید تا ریشه همان جایی باشد که انتظار دارید.
  • مسیر را با ./ شروع نکنید. مترجم با این پیام می‌ایستد و دو شکل درست را هم پیشنهاد می‌دهد: E008: مسیر فراخوانی './lib/money.salam' نباید با './' آغاز شود.
  • پرونده‌ای که پیدا نشود هم خطاست (E008) و هیچ‌وقت بی‌صدا نادیده گرفته نمی‌شود.

واردسازی کتابخانه

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

pkg-import.salam
واردسازی رشته
واردسازی ریاضی

تابع ریشه:
    متن := "  سلام دنیا  "
    سرچاپ رشته.پیرایش(متن)
    سرچاپ رشته.شامل است(متن، "دنیا")
    سرچاپ ریاضی.جذر(۸۱.۰)، ریاضی.توان(۲.۰، ۱۰.۰)
پایان
خروجیسلام دنیا true 9 1024
pkg-dotted.salam
واردسازی کدگذاری.جیسون

تابع ریشه:
    سفارش := `{"id": 42, "item": "دفتر", "qty": 3}`
    اگر وارونه جیسون.معتبر(سفارش):
        سرچاپ "جیسون معتبر نیست"
        برگشت ۱
    پایان
    سرچاپ "سفارش"، جیسون.دریافت عدد(سفارش، "id")
    سرچاپ جیسون.دریافت عدد(سفارش، "qty")، "عدد"، جیسون.دریافت(سفارش، "item")
    سرچاپ جیسون.دارد(سفارش، "coupon")
پایان
خروجیسفارش 42 3 عدد دفتر false

واردسازی‌ای که به کار نرود خطاست (E082)، پس فهرست واردسازی‌های بالای هر پرونده همیشه دقیقاً می‌گوید آن پرونده به چه چیزهایی وابسته است.

همگانی و خصوصی

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

pkg-bank.salam
بسته بانک

همگانی پایا سقف_واریز := ۱۰۰۰۰۰۰

ساختار حساب:
    همگانی دارنده: رشته = ""
    موجودی: صحیح = ۰

    همگانی ایستا تابع باز کن(دارنده: رشته): حساب:
        برگشت حساب { دارنده = دارنده }
    پایان

    همگانی تابع مانده(): صحیح:
        برگشت این.موجودی
    پایان

    همگانی ناپایا تابع واریز(مبلغ: صحیح): منطقی:
        اگر وارونه مبلغ معتبر(مبلغ) یا مبلغ > سقف_واریز:
            برگشت نادرست
        پایان
        این.موجودی += مبلغ
        برگشت درست
    پایان

    همگانی ناپایا تابع برداشت(مبلغ: صحیح): منطقی:
        اگر وارونه مبلغ معتبر(مبلغ) یا مبلغ > این.موجودی:
            برگشت نادرست
        پایان
        این.موجودی -= مبلغ
        برگشت درست
    پایان
پایان

تابع مبلغ معتبر(مبلغ: صحیح): منطقی:
    برگشت مبلغ > ۰
پایان
pkg-private.salam
فراخوانی بانک "pkg-bank.salam"

تابع ریشه:
    ناپایا ح := بانک.حساب.باز کن("سارا")
    سرچاپ ح.واریز(۵۰۰)، ح.برداشت(۸۰۰)، ح.برداشت(۱۲۰)
    سرچاپ ح.دارنده، ح.مانده()
    سرچاپ ح.واریز(بانک.سقف_واریز + ۱)
پایان
خروجیtrue false true سارا 380 false

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

بسته‌ای در یک پوشه

وقتی بسته‌ای بزرگ شد، آن را در چند پرونده درون یک پوشه بنویسید. پوشه را هم‌نام بسته بگذارید و پرونده‌ای با همان نام در آن بسازید، مثل shop/shop.salam. فراخوانی همین یک پرونده همه‌ی پرونده‌های .salam دیگر آن پوشه را هم جزو همان بسته می‌آورد.

پروندهمحتوا
pkg-folder.salamبرنامه؛ shop/shop.salam را فراخوانی می‌کند
shop/shop.salamبسته فروشگاه، واردسازی رشته، تابع خصوصی برچسب قیمت و تابع همگانی خوشامد
shop/cart.salamبسته فروشگاه و تابع همگانی جمع سبد
pkg-folder.salam
فراخوانی فروشگاه "shop/shop.salam"

تابع ریشه:
    سرچاپ فروشگاه.خوشامد("سارا")
    سرچاپ فروشگاه.جمع سبد([۱۲۵۰، ۳۹۹، ۲۰۵])
پایان
خروجیخوش برگشتی، سارا 3 قلم، 18.54

پرونده‌های یک بسته همه چیز را با هم شریک‌اند: cart.salam تابع خصوصی برچسب قیمت را از shop.salam صدا می‌زند و رشته را بدون واردسازی به کار می‌برد، چون بسته یک بار آن را وارد کرده است.

نام‌های دوزبانه

در پرونده‌ی فارسی باید نام‌های فارسی را به کار ببرید و نام انگلیسی مثل رشته.Len خطاست؛ پرونده‌ی انگلیسی هم نام انگلیسی می‌خواهد. هر تابع کتابخانه‌ی استاندارد هر دو نام را دارد: کتابخانه به انگلیسی نوشته شده و نام فارسی هر تابع با @fa کنارش آمده است، و همین است که پرونده‌های فارسی می‌توانند صدایش بزنند. تابع‌های همگانی خودتان را هم می‌توانید همین‌طور با @en و @fa دو نام بدهید:

pkg-bilingual.salam
@en "Twice"
@fa "دوبار"
همگانی تابع دوبار(ع: صحیح): صحیح:
    برگشت ع * ۲
پایان

تابع ریشه:
    سرچاپ دوبار(۲۱)
پایان
خروجی42

کتابخانه‌ای که به انگلیسی نوشته شده و تابع‌هایش @fa دارند، از برنامه‌ی فارسی با نام فارسی صدا زده می‌شود؛ مثلاً تابع Twice با برچسب @fa "دوبار" در پرونده‌ی فارسی با دوبار(۲۱) صدا زده می‌شود و ۴۲ برمی‌گرداند.

ترتیب تعریف‌ها

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

  1. بسته
  2. واردسازی‌ها، سپس فراخوانی‌ها (واردسازی پس از فراخوانی خطای E108 است)
  3. پایاها و متغیرهای سراسری (اگر پس از تابع یا گونه بیایند، E084 و E085)
  4. گونه‌ها: ساختار، جداشمار، میانجی و کاربست (اگر پس از تابعی بیایند، E087)
  5. تابع‌ها، اول خصوصی‌ها و در پایان تابع‌های همگانی

فرمان salam format کد را مرتب می‌کند و خیلی از این جابه‌جایی‌ها را خودش انجام می‌دهد.

چیدمان یک پروژه

salam new shop پوشه‌ای به نام shop با پرونده‌ی main.salam که نقطه‌ی شروع برنامه است و یک salam.toml که نام پروژه را نگه می‌دارد می‌سازد. هر پروژه از main.salam شروع می‌شود: درون آن پوشه، فرمان salam build بدون هیچ نام پرونده‌ای همین پرونده را به برنامه‌ای هم‌نام پوشه تبدیل می‌کند، و پوشه‌ای که آن را ندارد ساخته نمی‌شود. از این‌جا پروژه معمولاً این‌طور رشد می‌کند:

  • main.salam کوتاه می‌ماند: ورودی‌ها را می‌خواند، بسته‌ها را صدا می‌زند و نتیجه را چاپ می‌کند.
  • هر بخش برنامه پوشه‌ای می‌شود که یک بسته است، مثل cart/cart.salam یا store/store.salam، و با بزرگ شدن به چند پرونده تقسیم می‌شود.
  • بسته‌ها یکدیگر را با مسیرهای @/ فراخوانی می‌کنند تا جابه‌جا کردن یک پرونده، فراخوانی‌های درونش را خراب نکند.
  • هر بسته رابط همگانی‌اش را کوچک نگه می‌دارد و نام‌هایش را با @en و @fa دوزبانه می‌کند.

همه‌چیز همراه سلام است

کل کتابخانه همراه مترجم نصب می‌شود، از جمله کارخواه‌های پایگاه داده مانند db.sqlite، db.postgres و db.redis، یادگیری ماشین (tensor، nn، llm)، صدا، سرویس‌های شبکه مانند grpc، ssh و net.websocket، و gui.webview، opencv و excel. این‌ها مانند هر کتابخانه‌ی دیگری وارد می‌شوند و پیش از آن چیزی برای افزودن نیست. کتابخانه‌ای که به یک کتابخانه‌ی سیستمی نیاز دارد، مانند db.sqlite، آن را نخستین باری که برنامه‌ای با آن را ترجمه می‌کنید پیدا یا ساخته می‌کند.

بسته‌هایی از گیت‌هاب

بسته‌هایی که دیگران نوشته‌اند در مخزن خودشان هستند. بسته‌ای که در کتابخانه‌ی استاندارد نیست با مخزنش نام برده و با یک رشته وارد می‌شود: import "github.com/owner/repo" نام repo را می‌سازد، و import "github.com/owner/repo/sub" به بسته‌ی پوشه‌ی sub همان مخزن می‌رسد. چنین واردسازی‌ای فقط درون یک پروژه کار می‌کند، چون salam.toml پروژه می‌گوید کدام نسخه به کار برود و salam.lock دقیقاً همان چیزی را که بارگیری شده نگه می‌دارد، تا همه‌ی رایانه‌ها یک کد را بسازند.

فرمانکارش
salam add github.com/owner/repoتازه‌ترین انتشار (یا @v1.2.0) را به salam.toml می‌افزاید، بارگیری‌اش می‌کند و salam.lock را به‌روز می‌کند
salam remove github.com/owner/repoآن را از هر دو پرونده برمی‌دارد
salam updateهمه‌ی بسته‌ها، یا آن‌هایی را که نام برده شده‌اند، به تازه‌ترین انتشارِ همان نسخه‌ی اصلی می‌برد؛ salam update pkg@latest به نسخه‌ی اصلی تازه می‌رود
salam depsهر چه در salam.lock آمده بارگیری می‌کند و چک‌سام هر کدام را می‌سنجد؛ پس از کلون کردن یک پروژه اجرایش کنید
salam install github.com/owner/toolmain.salam یک برنامه را می‌سازد و در ~/.salam/bin می‌گذارد؛ بدون نام، پروژه‌ای را که در آن هستید نصب می‌کند
salam uninstall helloبرنامه‌ای را که salam install گذاشته برمی‌دارد؛ بدون نام، فهرستشان را نشان می‌دهد
salam search websocketبسته‌ها را در کتابخانه‌ی استاندارد و در فهرست بسته‌ها پیدا می‌کند

هر نسخه از هر بسته یک بار در ~/.salam/pkg نگه داشته می‌شود. اگر git نصب باشد بارگیری با git انجام می‌شود؛ بی git، بسته‌های گیت‌هاب به شکل بایگانی دریافت و با خود سلام باز می‌شوند. وابستگی‌های یک بسته از salam.toml خود آن بسته پیدا می‌شوند، و اگر دو بسته نسخه‌های گوناگونی بخواهند، نسخه‌ی تازه‌تر به کار می‌رود.

افزودن، به‌روزرسانی، نوشتن و انتشار بسته صفحه‌ی جداگانه دارد: مدیر بسته.

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