سلام کامپایلی است یا تفسیری؟ تفاوت salam run، salam exec و salam build

سلام زبانی کامپایلی است که مفسر و REPL هم دارد. چهار فرمان check، run، build و exec دقیقاً چه می‌کنند، چه فرقی با هم دارند و هر کدام کجا به کار می‌آید.

در این مقاله
  1. چهار فرمان سلام در یک نگاه
  2. salam check: پیدا کردن خطاها بدون ساختن برنامه
  3. salam run: کامپایل، اجرا، دور انداختن
  4. salam build: فایل اجرایی ماندگار
  5. salam exec: مفسر زبان سلام
  6. REPL زبان سلام: salam cli
  7. زبان کامپایلری و زبان مفسری: تفاوت واقعی کجاست؟
  8. برای هر کار کدام فرمان سلام؟
  9. پرسش‌های پرتکرار

زبان برنامه‌نویسی سلام یک زبان کامپایلی است: فرمان salam build برنامه را به یک فایل اجرایی بومی تبدیل می‌کند و همان فایل است که روی سرور یا رایانه‌ی کاربر می‌نشیند. ابزار سلام سه راه دیگر هم برای کار با همان کد در اختیارتان می‌گذارد. salam check پیش از تولید هر کدی می‌ایستد و فقط خطاها را گزارش می‌کند. salam run برنامه را در یک فایل اجرایی موقت کامپایل می‌کند، اجرا می‌کند و دور می‌اندازد. salam exec برنامه را با مفسری اجرا می‌کند که درون خود کامپایلر است و هیچ کامپایلر C، LLVM یا پیونددهنده‌ای لازم ندارد. یک REPL هم هست، salam cli، برای امتحان کردن کد خط به خط. همه‌ی این‌ها یک کد منبع را می‌خوانند و همان بررسی‌ها را انجام می‌دهند. این نوشته توضیح می‌دهد هر فرمان در پشت صحنه چه می‌کند و کی باید سراغش رفت.

چهار فرمان سلام در یک نگاه

فرمانچه می‌کندخروجیکاربرد معمول
salam check app.salamتحلیل واژگانی، تجزیه و تحلیل کامل، همراه با بسته‌های واردشدهفقط پیام‌های خطا و هشداربعد از هر ویرایش
salam run app.salamبررسی، کامپایل، اجرا و پاک کردن فایل اجراییخروجی برنامهامتحان برنامه، اسکریپت‌ها، نمونه‌ها
salam build app.salamبررسی، کامپایل و پیوندفایل اجرایی ماندگارانتشار، سنجش سرعت، استقرار
salam exec app.salamبررسی و سپس اجرا با مفسرخروجی برنامهدستگاه بدون ابزار کامپایل، وارسی سریع منطق

راه پنجم REPL است (salam cli) که پایین‌تر به آن می‌رسیم. یک برنامه بی هیچ تغییری از همه‌ی این راه‌ها می‌گذرد. این غربال اعداد اول را در سراسر نوشته به کار می‌بریم:

سلام
تابع شمار اعداد اول(حد: صحیح): صحیح:
    ناپایا مرکب := وکتور {} برگردان وکتور<منطقی>
    دیرکن مرکب.آزادکن()
    تکرار حد + ۱:
        مرکب.بیفزا(نادرست)
    پایان
    ناپایا شمار := ۰
    تکرار ۲ تا حد در ن:
        اگر مرکب.بگیر(ن):
            گذر
        پایان
        شمار++
        ناپایا مضرب := ن * ۲
        تا مضرب <= حد:
            مرکب.بنشان(مضرب، درست)
            مضرب += ن
        پایان
    پایان
    برگشت شمار
پایان

تابع ریشه:
    هر حد در [۱۰۰، ۱۰۰۰۰، ۱۰۰۰۰۰۰]:
        سرچاپ "تعداد اعداد اول تا"، حد، "="، شمار اعداد اول(حد)
    پایان
پایان
خروجیتعداد اعداد اول تا 100 = 25 تعداد اعداد اول تا 10000 = 1229 تعداد اعداد اول تا 1000000 = 78498

salam run، salam exec و فایل اجرایی ساخته‌شده با salam build، با هر سه پشتیبان (Backend)، دقیقاً همین سه خط را چاپ می‌کنند. عددها در خروجی با رقم‌های لاتین چاپ می‌شوند، حتی وقتی در کد با رقم فارسی نوشته شده‌اند.

salam check: پیدا کردن خطاها بدون ساختن برنامه

salam check هر کاری را که salam build پیش از تولید کد انجام می‌دهد اجرا می‌کند: تحلیل واژگانی، تجزیه، بررسی گونه‌ها، تحلیل بازه‌ها و مالکیت، و همین کارها برای هر بسته‌ای که وارد شده است. سپس پیام‌ها را چاپ می‌کند و اگر خطایی باشد با کد خروج ۱ تمام می‌شود؛ وگرنه می‌نویسد app.salam: no errors، یا برای یک پرونده‌ی فارسی app.salam: بدون خطا. هیچ‌وقت کامپایلر C یا LLVM را صدا نمی‌زند، پس سریع‌ترین بازخوردی است که می‌توانید بگیرید، و هم روی یک پرونده کار می‌کند و هم روی یک پوشه‌ی کامل:

ترمینال
salam check app.salam
salam check src/
salam check app.salam --error-format=json

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

salam run: کامپایل، اجرا، دور انداختن

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

ترمینال
salam run tool.salam -- input.txt --verbose

چون run یک ساخت کامپایلی واقعی است، همان گزینه‌های build را می‌پذیرد: --backend=native، --release، --asan و بقیه. درون پوشه‌ی یک پروژه، salam run بدون نام پرونده نقطه‌ی ورود (main.salam) را خودش پیدا می‌کند. کد خروج برنامه همان مقداری است که تابع ریشه برمی‌گرداند: برگشت ۳ در تابع ریشه باعث می‌شود salam run با کد ۳ تمام شود، و یک اسکریپت سلام شکست را این‌گونه به پوسته یا به کار CI خبر می‌دهد.

هزینه‌ی run مرحله‌ی کامپایل در هر بار اجراست. پشتیبان C پرونده‌های شیء (Object) ماژول‌های تغییرنکرده را بین ساخت‌ها نگه می‌دارد و اجرای دوباره‌ی همان برنامه را ارزان‌تر می‌کند، ولی هر اجرا باز هم کامپایل و پیوند دارد.

salam build: فایل اجرایی ماندگار

salam build app.salam یک فایل اجرایی به نام همان پرونده می‌سازد (app، یا app.exe برای ویندوز)، یا به نامی که با --output=FILE بدهید. بدون ورودی، پروژه‌ی پوشه‌ی جاری را می‌سازد. اگر از ساخت قبلی چیزی تغییر نکرده باشد، می‌نویسد up to date و کاری نمی‌کند.

مهم‌ترین گزینه‌ها:

  • ساخت توسعه و ساخت انتشار. ساخت پیش‌فرض بررسی‌های ایمنی را نگه می‌دارد: اندیس آرایه‌ها و وکتورها هنگام اجرا بررسی می‌شود و حساب اعداد علامت‌دار حتماً چرخشی (Wrap) است. --release بررسی مرز اندیس‌ها را برمی‌دارد، به بهینه‌ساز اجازه می‌دهد فرض کند حساب علامت‌دار هرگز سرریز نمی‌کند، و سطح بهینه‌سازی LLVM را از -O2 به -O3 می‌برد. با حالت پیش‌فرض آزمایش کنید و هر جا سرعت لازم بود با --release منتشر کنید.
  • خطایابی. -g اطلاعات خطایابی DWARF را در فایل می‌گذارد و salam debug app.salam برنامه را همین‌طور می‌سازد و GDB یا LLDB را باز می‌کند. salam memcheck برنامه را می‌سازد و زیر Valgrind یا AddressSanitizer اجرا می‌کند.
  • پشتیبان‌ها. --backend=c، llvm یا native تولیدکننده‌ی کد را انتخاب می‌کند؛ نوشته‌ی سه راه تولید کد سود و زیان هر کدام را توضیح می‌دهد.
  • هدف‌ها. --target=TRIPLE برای سیستم دیگری کامپایل می‌کند، مثلاً از لینوکس برای ویندوز. نوشته‌ی کامپایل متقابل در سلام را ببینید.
  • کتابخانه‌ها. --lib یک کتابخانه‌ی اشتراکی (.so، .dylib، .dll) و با --lib=static یک بایگانی ایستا می‌سازد، همراه با یک سرآیند C.

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

salam exec: مفسر زبان سلام

salam exec app.salam برنامه را بدون تولید کد ماشین اجرا می‌کند. پس از همان بخش جلویی و همان بررسی‌هایی که همه‌ی فرمان‌ها دارند، درخت نحوی بررسی‌شده به مفسری سپرده می‌شود که درون خود فایل salam است (compiler/interp/). با این‌که salam help آن را «مفسر درخت‌پیما» می‌نامد، مفسر کنونی کمی بیشتر کار می‌کند: هر تابع بار اولی که صدا زده می‌شود به بایت‌کد ثباتی (Register Bytecode) برای یک ماشین مجازی کوچک (interp_vm.salam) ترجمه می‌شود، و هر چیزی که کامپایلر بایت‌کد مستقیم پشتیبانی نکند به ارزیابی مستقیم درخت برمی‌گردد تا نتیجه دقیق بماند. با SALAM_VM=0 مسیر بایت‌کد خاموش می‌شود. مقدارهای مفسر در هیپ خودش زندگی می‌کنند و یک زباله‌روب (Garbage Collector) از نوع نشانه‌گذاری و جاروب آن‌ها را پاک می‌کند؛ برخلاف سلام کامپایل‌شده که هیچ زباله‌روبی ندارد.

این مفسر کجا به کار می‌آید؟

  • نبود ابزار کامپایل. فایل salam بدون LLVM معمولاً برای ساختن هر چیزی به یک کامپایلر C نیاز دارد. exec به هیچ چیز دیگری نیاز ندارد، و این روی دستگاهی با دسترسی محدود، در یک کانتینر کمینه یا وقتی تازه شروع کرده‌اید اهمیت دارد.
  • وارسی سریع منطق. برای یک برنامه‌ی کوچک، تفسیر کردن می‌تواند از کامپایل کردن سریع‌تر باشد، و --timeout=MS برنامه‌ای را که در حلقه‌ی بی‌پایان افتاده متوقف می‌کند.
  • آزمون‌ها. salam test --interp آزمون‌های پروژه را به جای کامپایل، با مفسر اجرا می‌کند.

و محدودیت‌هایش. مفسر روی هر کار محاسباتی سنگین بسیار کندتر از کد کامپایل‌شده است، پس راهی برای اجرای بار کاری واقعی در محیط عملیاتی نیست. راهنمای خود مخزن این است که ساخت کامپایلی مرجع درستی باشد: مفسر زبان را پوشش می‌دهد، ولی برخی ویژگی‌های سطح پایین (بخش‌هایی از همکاری با C و نخ‌ها) فقط در پشتیبان‌های کامپایلی کامل پشتیبانی می‌شوند. اگر روزی exec و build نتیجه‌ی متفاوتی دادند، به build اعتماد کنید و تفاوت را گزارش کنید.

REPL زبان سلام: salam cli

salam cli یک خط فرمان تعاملی باز می‌کند. هر خط یا یک تعریف است (تابع، ساختار، جداشمار) یا یک دستور، و REPL آن‌ها را به شکل یک جلسه نگه می‌دارد: تعریفی با نامی که قبلاً به کار برده‌اید جای تعریف قبلی را می‌گیرد و دستورها روی هم انباشته می‌شوند. برای نوشتن به فارسی، REPL را با salam cli --lang=fa باز کنید؛ بدون این گزینه زبان جلسه انگلیسی است. یک جلسه‌ی فارسی چنین است:

متن
Salam general REPL (lang=fa). ':help' for commands.
salam> ع := ۶
salam> سرچاپ ع * ۷
42
salam> تابع دوبار(ن: صحیح): صحیح:
  ...     برگشت ن * ۲
  ... پایان
defined دوبار.
salam> سرچاپ دوبار(ع)
12
salam> :quit
bye.

REPL در پشت صحنه مفسر نیست. در هر نوبت، کل جلسه را به شکل یک برنامه می‌نویسد و دستورهای شما را درون یک تابع ریشه‌ی ساختگی می‌گذارد، آن را با کامپایلر معمولی کامپایل و اجرا می‌کند و فقط خروجی تازه را نشان می‌دهد. :defs همان برنامه‌ی ساخته‌شده را چاپ می‌کند، :undo آخرین دستور را کنار می‌گذارد، :save FILE و :load FILE جلسه را ذخیره و بازیابی می‌کنند و :clear از نو شروع می‌کند. چون هر نوبت یک کامپایل واقعی است، خطاهای REPL همان خطاهای کامپایلری‌اند که از salam check می‌گیرید. salam page هم REPL هم‌تای آن برای زبان صفحه است که HTML و CSS می‌سازد.

زبان کامپایلری و زبان مفسری: تفاوت واقعی کجاست؟

تعریف کلاسیک این است که زبان کامپایلری پیش از اجرا به کد ماشین ترجمه می‌شود و زبان مفسری را برنامه‌ی دیگری در حین اجرا پیش می‌برد. در عمل بیشتر زبان‌ها این دو را با هم دارند: جاوا به بایت‌کد کامپایل می‌شود و سپس با JIT به کد ماشین می‌رسد، CPython به بایت‌کد کامپایل می‌کند و آن را تفسیر می‌کند، و موتورهای جاوااسکریپت چند کار از این‌ها را هم‌زمان انجام می‌دهند. «کامپایلی» و «تفسیری» بیشتر وصف پیاده‌سازی‌اند تا وصف زبان.

با این حال، وضعیت سلام روشن است. زبان برای کامپایل پیش از اجرا (AOT) طراحی شده است: گونه‌های ایستا، بی‌نیازی کد معمولی از بازتاب (Reflection) هنگام اجرا و نبود زباله‌روب در خروجی کامپایل‌شده. مفسر ابزاری کمکی است که روی همان بخش جلویی ساخته شده، نه یک محیط اجرای دوم با معناشناسی جداگانه. برای برنامه‌های سلام، تفاوت‌های عملی این‌هاست:

کامپایلی (build، run)تفسیری (exec)
نیازمندییک ابزار پشتیبان (درون نسخه‌های رسمی هست، یا یک کامپایلر C)فقط فایل salam
سرعت برنامهسرعت بومی، در رده‌ی C و Rustروی محاسبه‌ی سنگین بسیار کندتر
حافظهخود برنامه آزاد می‌کند، بدون زباله‌روبهیپ مفسر با زباله‌روب
پوشش ویژگی‌هاکل زبانتقریباً همه؛ ساخت کامپایلی مرجع است
خروجییک فایل اجرایی (که build نگه می‌دارد)فقط خروجی خود برنامه

نوشته‌ی کامپایل بومی بدون ماشین مجازی و زباله‌روب توضیح می‌دهد چرا سلام بر پایه‌ی شکل کامپایلی ساخته شده است.

برای هر کار کدام فرمان سلام؟

  • بعد از هر ویرایش: salam check. سریع‌ترین چرخه است و هر خطایی را که کامپایلر بدون ساختن می‌تواند پیدا کند نشان می‌دهد.
  • هنگام توسعه و برای اسکریپت‌ها: salam run. یک فرمان، رفتار واقعی کامپایلی، و چیزی هم پشت سر نمی‌ماند.
  • برای هر چیزی که منتشر می‌کنید، زمانش را می‌سنجید یا مستقر می‌کنید: salam build، و پس از آزمایش با --release. سرعت را فقط روی فایل‌های اجرایی ساخته‌شده بسنجید.
  • روی دستگاهی بدون ابزار کامپایل، یا برای نگاهی سریع به منطق خالص: salam exec.
  • برای امتحان یک خط یا گشتن در یک API: salam cli، و برای کد فارسی salam cli --lang=fa.

اگر تازه با سلام آشنا شده‌اید، درس نخست آموزش از همان ابتدا salam run و salam exec را به کار می‌برد، صفحه‌ی ابزارها همه‌ی فرمان‌ها را فهرست کرده و صفحه‌ی نصب کامپایلر را با یک فرمان در اختیارتان می‌گذارد.

پرسش‌های پرتکرار

زبان سلام کامپایلی است یا تفسیری؟

کامپایلی. salam build از راه C، LLVM یا پشتیبان بومی خود سلام کد ماشین بومی تولید می‌کند. ابزار سلام یک مفسر هم دارد، salam exec، تا برنامه‌ها بدون کامپایلر C هم اجرا شوند، ولی ساخت کامپایلی مرجع است.

تفاوت salam run و salam build چیست؟

هر دو برنامه را به یک شکل کامپایل می‌کنند. salam run نتیجه را بی‌درنگ اجرا و سپس پاک می‌کند؛ salam build فایل اجرایی را نگه می‌دارد تا بعداً اجرا، کپی یا منتشرش کنید.

آیا salam run هر بار برنامه را از نو کامپایل می‌کند؟

بله، هر بار یک ساخت واقعی انجام می‌دهد، هرچند پشتیبان C پرونده‌های شیء ذخیره‌شده‌ی ماژول‌های تغییرنکرده را دوباره به کار می‌برد. اگر یک برنامه را بارها اجرا می‌کنید، یک بار با salam build بسازید و خود فایل اجرایی را اجرا کنید.

آیا زبان سلام REPL دارد؟

بله. salam cli آن را باز می‌کند و salam cli --lang=fa جلسه‌ای به فارسی. REPL جلسه‌ای از تعریف‌ها و دستورها نگه می‌دارد، در هر نوبت جلسه را دوباره کامپایل می‌کند و فقط خروجی تازه را چاپ می‌کند، با فرمان‌هایی مانند :defs، :undo، :save و :load.

کامپایلر و سرعت

کامپایل متقابل در زبان سلام: ساخت برنامه برای لینوکس، مک، ویندوز و ARM از یک دستگاه

گزینه‌ی --target در سلام، سه تولیدکننده‌ی کد پشت آن، sysrootها و پیونددهنده‌ی درون کامپایلر، و فهرستی صادقانه از هدف‌هایی که در CI اجرا می‌شوند و هدف‌هایی که فقط ساخته می‌شوند.

خواندن مقاله
کامپایلر و سرعت

از کد بومی تا WebAssembly و جاوااسکریپت: برنامه‌های سلام کجاها اجرا می‌شوند؟

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

خواندن مقاله