اینم یه تجربه پراکنده دیگه!

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

مشکل با متغیر محلی بزرگ

ما یه کد سی داشتیم که یه متغیر local با حجم حدودا ۱۰ کیلو بایت تعریف شده بود و مقدار دهی اولیه شده بود(عکس بود). همیشه کامپایلر به این قسمت از کد که میرسید کلی طول میکشید تا بتونه کد رو کامپایل کنه و همیشه اعصاب ما رو خورد میکرد. تا اینکه یه روز تصمیم گرفتیم مشکل رو حل کنیم. راه حل‌هایی که پیش رومون بود اینها:

  1. متغیر رو کلا حذف کنیم. که خب نمیخواستیم صورت مساله رو حذف کنیم
  2. متغیر رو global میکردیم که خب همه میتونستن تغییرش بدن و برامون بد بود
  3. متغیر رو static کنیم. این تفاوتی برای ما نداشت فقط میخواستیم امتحان کنیم.

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

بررسی چرایی راه‌حل

سوالی که برای ما پیش اومد این بود که چرا یه تغییر اینجوری اینهمه هم توی سرعت هم حجم باینری موثره؟ برای جواب دادن به این ماجرا یکم رفتم تحقیق کردم:

  • همه باینری‌ها از بخش‌های مختلفی تشکلی شدن که اگه assembly یادتون باشه شامل اینهاست
  • متوجه شدم که متغیرهای global توی data segment تعریف میشن و متغیرهای local توی stack segment تعریف میشن.
  • متوجه شدم که متغیرهای static هم توی data segment تعریف میشن.

پس هرچی بود زیر سر data segment بود از اینجا به بعدش بازی شد حدس و گمان در مورد چرایی دوتا اتفاق:
– چرا سرعت کامپیال بیشتر؟ احتمالا به این دلیل که کامپایلر با data segment راحت‌تر کار میکنه تا stack segment. اما این حدس حجم باینری کمتر رو توجیه نمیکنه چون در این دوحالت حجم متغیر ثابت مونده
– چرا حجم باینری کمتر؟ برای اینکار یکم باید یادمون بیاد توی assembly که چطور یه متغیر local تعریف میشه منظورم هست. یه متغیر local عملا اینه که اول یه مقداری توی stack قرار میگیره و در یه مرحله دیگه اون مقدار از stack خارج میشه. که این هم نیاز به کد سمت code segment داره هم نیاز به حافظه سمت stack segment داره.

خب با این حالت ما به حدس دقیق تری از اتفاق پشت پرده رسیدیم. اونم اینه که وقتی متغیر توی data segment قرار میگیره یه بخش توسط کامپایلر تخصیص داده میشه و مقادیر هم توی اون خونه‌ها نوشته میشه. ولی وقتی قرار باشه اون متغیر توی stack segment قرار بگیره هم کد تولید میشه هم حافظه تخصیص داده میشه که منجر به طولانی تر شدن کل روند میشه.

امیدوارم به دردتون خورده باشه
همین!

اینم یه تجربه پراکنده دیگه!

چند شب پیش یکی از دوستان با این سوال به سراغ من اومد:

مشکل اینه که وقتی با malloc یا new یک حافظه تخصیص میدی و با free یا delete حافظه رو برمیگیردونی, اون حافظه شاید بلاک هاش خالی بشه اما توی تسک لیست خالی به عنوان منابع استفاده شده میاره و تا پایان پروسه پاک نمیشه. از دوستانی که آشنایی دارن پیگیری کن ببین جریان چیه. مهمه .

خدا خیرت بده. یه جایی از حافظه نمایی رشد میکه و وقتی خالیش میکنیم (با اینکه از لیست heap سیستم عامل خالی میشه) اما توی لیست پروسه‌ها(مثل نتیجه ps) هنوز به عنوان حافظه استفاده شده هست و حافظه آزاد نمیشه !!!‌ عجیبه . کلی توی اینترنت اینو سوال کردن !!

من که لینوکسم. اما فکر کنم ویندوزم همینه. چون سوالای اینترنت هردوشونه

کلا بخوام سوال رو خلاصه کنم اینجوریه که چرا وقتی حافظه رو به سیستم‌عامل پس میدیم واقعا پس داده نمیشه. من یکم از دانسته‌هام استفاده کردم و یکم هم تحقیقات کردم

پیش‌نیازها

پیش نیازهایی که به نظرم باید بدونیم ایناست:

  • نکته صفر اینکه با توجه به تحقیقات من زیر کلمه کلیدی new از همون malloc استفاده میشه. پس بررسی malloc به تنهایی میتونه جوابی برای ما فراهم کنه.
  • اول اینکه باید بدونیم که وقتی malloc صدا زده میشه چه اتفاقی میفته. کاری که malloc میکنه اینه که heap پروسه رو بهت اختصاص میده. حالا heap توسط سیستم عامل مدیریت میشه و در صورت نیاز به پروسه‌ی شما حافظه بیشتری تخصیص میده.
  • دوم اینکه فضای heap توسط کرنل مدیریت میشه و به اصطلاح نگاشته میشه به یه چیزی به نام Virtual Memory
  • سوم اینکهVirtual Memory نگاشته میشن به چیزی به نام Memory Page که نزدیک به سخت افزار تره
  • چهارم اینکه Memory Page نگاشته میشن بلوک‌های واقعی حافظه. از این مطمئن نیستم اما این نزدکترین لایه به سخت افزاره که من باهاش آشنا هستم.

بررسی مساله

حالا اگه بخوایم مساله رو بررسی کنیم باید به چند‌تا سوال جواب بدیم:

  • اول اینکه malloc چطور عمل میکنه؟ در پیاده‌سازیش ساز و کارش چیه؟
  • دوم اینکه روند افزایش طول Virtual Memory و اضافه شدن Memory Pageها چطوره؟
  • سوم اینکه بعد از پس دادن Memory Pageها سیستم عامل چطور اونها رو از یک پروسه پس میگیره؟

خب جواب سوالا به ترتیب اینا به نظر میرسن:

  • پیاده‌سازی‌های متفاوتی از malloc وجود داره
    • پیاده‌سازی معمولا اون اینطوریه که پس از گرفتن حافظه وقتی free شد معمولا اونها رو به سیستم عامل پس نمیده و نگه میداره که شاید بعدا بخواد دوباره تخصیص بده.
    • دوم اینکه درخواست یک Memory Page جدید توسط دستوراتی مثل mmap و sbrk درخواست یک بلاک بزرگ حافظه میکنه و اون رو بنا به درخواست نرم‌افزار تخصیص میده.
    • بعدش هم اینکه برای اینکه یک Memory Page رو برگردونه به سیستم عامل بایستی تمام حافظه‌هایی که روی اون تخصیص داده شدن برگشت داده بشه. که این هم همیشه اتفاق نمی‌افته.
    • معمولا هم پیاده‌سازی‌ها اینجورین که حافظه‌ای که توسط sbrk افزایش پیدا کرده رو دست نمیزنن(دلیل دقیقش رو نمیدونم) و حافظه‌ای که توسط mmap اضافه شده رو به راحتی برمیگردونن.
  • تقریبا جواب سوال دوم رو هم تا الان دادم. مقدار Virtual Memory توسط glibc کنترل میشه و بوسیله malloc تخصیص داده میشه.
  • جواب سوال سوم هم اینه که کرنل لینوکس همیشه حافظه برگشت داده شده رو برنمیگردونه چون که هزینه داره فقط اون رو علامت میزنه که در صورت نیاز اون رو حذف کنه یا به swap ببره.

راه حل مساله

حالا بریم سراغ مساله اصلی(چرا حافظه برگشت داده شده برگشت نخورده) و توجیهی که براش پیدا کردیم:
* مهمترین دلیل این اتفاق به نظرم malloc هست که حافظه رو نگه داشته و برنگردونده
* دومین دلیلش هم میتونه این باشه که کرنل هنوز Memory Page رو برنداشته و فقط علامت زده

حالا راه حل چیه:
* اول اینکه به کرنل و glibc اعتماد کنیم
* دوم اینکه بیایم و این پارامتر مروبط به malloc رو تنظیم کنیم۴
* سوم اینکه بیایم از یه پیاده‌سازی دیگه برای تخصیص حافظه و آزاد سازی اون استفاده کنیم
* چهارم اینکه بیایم خودمون مستقم از mmap و munmap استفاده کنیم

امیدوارم اینا راه گشای دیگران هم باشه

برای مطالعه بیشتر:

۱

۲

۳

۴

همین!

اینم یه تجربه پراکنده دیگه!

متاسفانه یه مدت میشه که سرم بیش از اندازه شلوغ شده و نرسیدم وبلاگ بنویسم. قبلا در مورد cmake نشوتم و حالا خواستم یکم بیشتر بنویسم.

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

  1. یه سری سورس که اصل منطق برنامه اونجا پیاده سازی شده
  2. یه فایل اصلی یا main که تابع اصلی نرم افزار اونجا قرار گرفته
  3. احتمالا یه سری تست که نرم افزار رو بصورت اتوماتیک مورد تست قرار میدن
  4. احتمالا یه سری کتابخانه که بصورت static یا dynamic به کد اضافه میشن.

یه خورده توضیح بدم که توی مcmake معمولا یه چیزی داریم به عنوان خروجی که میتونه از جنس فایل اجرایی باشه یا از جنس کتابخانه. ما با ترکیب کتابخانه‌ها و سورس کدهای موجود فایل اجرایی نهایی رو درست میکنیم. روند کار هم معمولا به این صورته که:
۱٫ سورسهای اصلی بصورت یه کتابخانه static کامپایل میشه
۲٫ فایل اصلی برنامه بصورت یک فایل اجرایی کامپایل شده و به کتابخانه سورس‌های اصلی لینک میشه
۳٫ تست کیسها بصوت یک فایل اجرایی کامپایل شده و به کتابخانه سورس‌هاس اصلی لینک میشه.
۴٫ کتابخانه ها هم به فایل اجرایی اضافه میشن.

حالا اگه مثال بخوایم یه پروژه رو مثال بزنم که ساختار کدش به شکل زیره

فایل CMakeLists.txt این پروژه یه چیزی شبیه این خواهد بود

شامل یک کتابخانه static و دو فایل اجرایی یکی اصل برنامه و دیگری تست برنامه.

همین!

اینم یه تجربه پراکنده دیگه!

من قبلا هم در مورد ابزارهایی مثل makefile و cmake که کامپیال کردن نرم‌افزارها بالاخص در زبان‌های C/C++ رو راحت می‌کنن نوشتم. البته باید بگم که هیچوقت بصورت کامل و درستی یاد نگرفتم که makefile چطوره و چطور میشه باهاش سرو کله زد تا اینکه در یکی از پروژه‌ها مجبور به استفاده از makefile شدم و با کمک یکی از دوستان یه پروژه متن‌باز پیدا کرد که یه مدل آماده makefile که توش تقریبا همه کارهای معمول انجام شده بود رو آورده بود.

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

  1. اینکه بدونید سورس پروژه کجاست.
  2. اینکه فولدرهایی که header ها توش قرار گرفته کجاست
  3. اینکه بدونید برنامه‌تون به چه کتابخانه‌هایی نیاز داره
  4. اینکه بدونید برنامه‌تون برای کامپیال شدن به کمک GCC به چه flagهایی نیاز داره.

بعد از اینکه این موارد رو دونستید و اونها رو پیدا کردید باید برید و بخشها مرتبط با این موارد رو توی makefile تغییر بدید به عنوان Customizable Section مشخص شده.

خب هر یک از این بخش‌های قابل تغییر برای خودش معنایی داره

  1. بخش MY_CFLAGS مقدار flag هایی هست که برای کامپیال کردن فایلها با پسوند c به کار میره. لازه به ذکره که بدونید flagهای C با C++ می‌تونن تفاوت اساسی داشته باشند
  2. بخش MY_LIBS نشون‌دهنده flag هایی هست که در فاز link قرار به linker داده بشه و با استفاده از اون کتابخانه ها شناسونده بشن. یه مثال برای این مقدار -lpthread هست که میگه کتابخانه pthread مورد نیاز این نرم‌افزار هست
  3. بخش CPPFLAGS بخشی هست که نشون میده flagهای کامپایل c++ چیا هستن. مثلا -std=gnu++11 که نشون میده میخوایم از استاندارد C++ 2011 استفاده کنیم. فولدر headerها رو هم توی این بخش اضافه می‌کنیم
  4. اگه به هر دلیل بخوایم flagهای دیگه ای به linker بدیم از LDFLAGS استفاده می‌کنیم.
  5. سورس نرم‌افزار رو با SRCDIRS جاهایی که سورس قرار گرفته رو نشون میده.
  6. و در نهایت PROGRAM نام نرم‌افزار رو نشون میده.

امیدوارم با این توضیحات کوتاه دستتون اومده باشه که چه کاری رو به چه صورت باید انجام بدید و در صورت هرگونه سوالی کامنت بگذارید و یادتون نره که من هنوز makefile رو بخوبی بلد نیستم! 😉

اینم یه تجربه پراکنده دیگه!

من این چند روز بخاطر یه کاری نیاز داشتم یه مساله تئوری گراف رو حل کنم. من معمولا سعی میکنم که توی کارها یه چیز جدید رو یاد بگیرم. به همین خاطر با استفاده از ipython notebook یا jupyter notebook مساله رو حل کردم. اتفاقا جادی هم یه ویدئوی آموزشی خوب در موردش ساخته.

چرا Jupyter Notebook

خب اول از همه میخوام یه چیزایی رو توضیح بدم که به نظر من چرا شاید کسی بخواد از این سیستم‌ها استفاده کنه. مهم‌ترین استفاده‌ای که من براش داشتم اینه که هر جا که به نظرم میخواستم از matlab استفاده کنم میتونستم از jupyter notebook استفاده کنم. بدین شکل که شما برای خیلی کارهای علمی یه کارهایی رو توی matlab پیاده‌سازی میکنی و تست می‌کنی و وقتی لازم شد واقعا در محیط عملیاتی ازش استفاده کنی اون‌ها رو در محیط عملیاتی دوباره پیاده‌سازی می‌کنی.

خب اولین زبانی که jupyter notebook پشتیبانی میکرده python بوده که مثل زبان matlab ساده بوده و بخاطر پکیج‌های عملی که هر دو زبان دارن پیاده‌سازی کارهای علمی به شدت ساده می‌شه.

دوم اینکه هر دو از مفهوم به نام متغیرهای مشترک بین همه برنامه‌های مختلف پشتیبانی می‌کنن که باعث میشه شما یه کار پر هزینه رو یه بار انجام بدی و نتیجه رو داشته باشی و مراحل بعدی رو براساس اون و بدون نیاز به اجرای از اول اون بخش پر هزینه انجام بدی

سوم اینکه هر دو ابزارهای بسیار خوب و قوی برای کشیدن نمودارهای علمی دارن که باعث میشه گزارش ساختن با اونها راحت باشه.

چهارم اینکه در jupyter notebook شما می‌تونید همزمان که کد می‌نویسید گزارش هم بنویسید. یعنی بلوک‌هایی داریم که توش میشه با استفاده از markdown متن هم بنویسید که در نهایت گزارش کاری که دارید انجام می‌دید هم آماده باشه. لازم نیست تکرار کنم که markdown رو من خیلی دوست دارم(۱ و ۲ و ۳)

یه مثال ساده

حالا کار من این بود که بتونم یه گراف جهت‌دار بدون دور بسازم و بتونم اون رو بکشم. خب اول از هم نحوه ساختن اون. کلا در ریاضی اثبات میشه که اگه بخواید گراف جهت‌دار بدون دور بسازید تنها چیزی که نیاز دارید اینه که توی ماتریس مجاورت اون گراف تنها درایه‌های زیر قطر اصلی ۱ باشن. این تضمین می‌کنه که گراف تولید شده گراف جهت‌دار بدون دور باشه.

اول از همه باید نیازمندی ها رو وارد کرد

بعد نوبت به کد ساختن DAG میرسه که کد این کار اینه

بعدش هم میشه نمایش دادنش که به سادگی میشه این

نتیجه کار هم توی github قرار گرفته و از اینجا قابل مشاهده است.
همین!

اینم یه تجربه پراکنده دیگه!

خب من به مسائل مرتبط با کارایی و اجرای غیر همزمان کدها یا همون async رو دوست دارم. یکم هم تجربه کد نویسی رو باهاشون دارم. توی این پست میخوام بصورت ساده یکی از روشهای این پیاده‌سازی رو براتون توضیح بدم. خیلی دنبال این نیستم که بگم دقیقا این روش کجاها به درد میخوره و چرا به درد میخوره بلکه هدف یه معرفی اولیه است. انشالا در یه پست مفصل بیشتر توضیح میدم. کل موضوع حول دوتا واژه promise و future میگرده که در c++0x11 معرفی شده.

داستان از این قراره که در مدل sync شما یه تابع رو صدا میزنی و منتظر میمونی که جواب به شما برگرده. اما در این مدل بعد از صدا زدن یک تابع بجای منتظر موندن برای جواب همون موقع یک مقدار از نوع future برمیگرده که تا زمانی که تابع صدا زده شده به اتمام نرسیده مقداری نداره. بعد از تموم شدن تابع مقدار future همون مقداری هست که نتیجه محاسباته. حالا اگه از منظر تابعی که صدا زده میشه نگاه کنیم اون تابع خروجیش از نوع promise هست که بعد از اتمام اون رو پر میکنه و برمیگردونه. همین!

حالا این نمونه کد رو هم ببنید که یه مثال ساده از future و promise هست.

مثالها از اینجا گرفته شده. اولین مثال از این قرار که ما یه تابع که خروجیش void هست رو با استفاده future مورد استفاده قرار می‌دیم.

اتفاقی که اینجا میفته اینه که با استفاده از ‍std::async ما سعی میکنیم که اون تابع رو در یک thread دیگه اجرا کنیم و نتیجه رو بصورت یک future از اون بگیریم. با اجرای result.get() مطمئن می‌شیم که اجرای اون thread به اتمام رسیده.

مثال دوم یه محاسبه بصورت غیر همزمان اجرا میشه و نتیجش به کمک future برگردونده میشه

امیدوارم که این بدردتون خورده باشه.
همین!

اینم یه تجربه پراکنده دیگه!

اول از همه بگم که این پست ۱۰۰ امین پست وبلاگ من هست که در نزدیک به سه سال منتشر شده! اولایی که تصیمم به نوشتن گرفتم فکر نمی‌کردم اینقدر بتونم دوام بیارم که ۱۰۰ تا پست بنویسم اما به نظر میرسه نوشتم! پس خوشحالم!

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

شما با دوتا مساله روبرو هستید.
– اول اینکه یه فایل رو باز کنید و با اون به عنوان فایل اجرایی برخورد کنید! یعنی بصورت دقیقتر بگم که با محتوای اون کد به شکل Code Segment برخورد کنید.
– دوم اینکه بتونید حالا که فایل رو باز کردید بتونید توابع درون اون فایل رو به صورتی تشخیص بدید و صدا بزنید!

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

همین!

برای مطالعهی بیشتر

QR Code

اینم یه تجربه پراکنده دیگه!

همه ما با مفهوم QR Code کم و بیش آشنا هستیم. یک نوع بارکد دو بعدی هست که درون خودش اطلاعاتی رو مخفی می‌کنه. استانداردهای متفاوتی هم داره. نسخه‌های متفاوتی هم داره. همچنین افزونگی اطلاعات هم توش در نظر گرفته شده که میشه در صورت خراب شدن QR Code اون رو تا حدی پوشش داد.

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

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

راه اندازی QR Code در فدورا و یک نمونه کد ساده

تنهای کاری که توی فدورا باید بکنید اینه که

رو نصب کنید.
نمونه کد استفاده از این کتابخانه هم ساده است:

همین!
پ.ن: نتیجه کار هم در عکس این پست قابل مشاهده است.

cmake diagram

اینم یه تجربه پراکنده دیگه!

متاسفانه یه مدت میشه که سرم بیش از اندازه شلوغ شده و نرسیدم خوب وبلاگ بنویسم. اما این هفته یه کار خوبی انجام دادم که گفتم اینجا مستندش کنم.

خب همونطور که ممکنه بدونید من توسعه اصلی کارهام در زبان‌های c و c++ هست. یکی از ویژگی‌هایی که این زبان‌ها دارن این هست که شما بایستی برای کامپایل شدن برنامه همیشه از یک IDE استفاده کنی یا از ابزارهایی مثل make برای لینوکس یا مثلا nmake در ویندوز. خب وقتی شما بخوای یه کد رو بین پلتفروم‌های مختلف و برنامه نویس‌های مختلف به اشتراک بگذاری همیشه این سوال وجود داره که دقیقا چطور میشه روند build رو یه جوری از وابستگی به سیستم عامل و حتی پلتفرم آزاد کرد. یه موضوع دیگه‌هم اینه که چطور میشه برای برنامه نویسان این آزادی رو ایجاد کرد که از IDE مورد علاقه‌شون استفاده کنند.

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

ویژگی‌های cmake

خب اگه بگم ویژگی‌های cmake رو بصورت خلاصه بگم اینها هستن

  • امکان کامپایل‌کردن کد بدون در نظر گرفتن محل کد
  • امکان کامپایل‌کردن کد روی سیستم‌عاملهای مختلف
  • امکان تشخیص محل فایل‌ها، کتابخانه‌ها
  • مدیریت وابستگی ها
  • امکان تولید فایل‌های make برای سیستم‌های مختلف و تولید پروژه برای IDEهای مختلف

کاری که من با cmake انجام دادم

کاری که من با کم cmake انجام دادم رو سعی میکنم براتون توضیح بدم. خب ما یه کد داشتیم که برای سه تا پلتفرم سخت افزاری مختلف با سه تا کامپایلر مختلف به سه دسته کتابخانه مختلف کامپایل و لینک میشد. کد تمام این پروژه‌ها یکسان بود اما فقط توسط کامپایلرهای مختلف توی محیط netbeans کامپایل میشد. کاری که انجام دادم این بود:

  • ابتدا پروژه فعلی رو تنها برای یک پلتفرم به cmake تبدیل کردم
    • کامپایلر استاندارد رو به کامپایلر دلخواه تبدیل کردم
    • آدرس کتابخانه‌ها و header فایلهای استاندارد رو عوض کردم
    • بصورت اتوماتیک لیست تمام فایلهای پروژه استخراج میشه
  • با استفاده از پارامترها و ساختار منطقی که cmake ساپورت میکنه دو تا پلتفرم دیگه رو اضافه کردم
    • یه متغیر کنترلی تعریف کردم
    • با استفاده از متغیر کنترلی و if همه موارد بالا رو برای هر پلتفرم تعریف کردم
    • اسم خروجی باینری و فولدر اون رو بر اساس متغیر کنترلی تعیین کردم.

حالا با این حال من تنها یک فایل دارم که باهاش کار سه تا پروژه رو انجام میدم که خب این خیلی جالب و جذابه

همین!

pandoc-filter

اینم یه تجربه پراکنده دیگه!

همنطور که اخیرا زیاد نوشتم فرمت مورد علاقه تولید مستندات من markdown شده و برای تبدیل کردنش به یه متن تر و تمیز pdf از pandoc به همراه xepersian استفاده می‌کنم. حالا این وسط من یه مشکلی داشتم و اونم این بود که متون انگلیسی توی فارسی به درستی به فرمت xepersian تبدیل نمی‌شدند و هی مجبور بودم بصورت دستی متن latex بدست آمده رو ویرایش کنم. به همین خاطر علاقه مند شدم که ببینم چطور می‌شه این مشکل رو توی pandoc حل کرد. راه حل از این قراره که شما بایستی یه فیلتر برای pandoc تعریف کنی و اون pandoc filter اون کاری که ما دوست داریم رو برای ما انجام میده!

فیلترهای pandoc

زبان برنامه نویسی که pandoc با اون توسعه یافته haskell هست که خب به نظر من زبان عجیب غریبی هست. شما می‌تونید با استفاده از این زبان فیلترها رو توسعه بدید. کل ماجرای فیلترها هم از این قراره که ابتدا pandoc متن ورودی رو میخونه و به ساختار سلسله مراتبی داخلی خودش تبدیل می‌کنه. حالا این ساختار رو بصورت اصلی (که فقط در زبان haskell در دسترسه) یا بصورت json در اختیار فیلتر شما قرار می‌ده و فیلتر شما بر اساس فرمت نهایی، نوع المنت و متا دیتاهای مربوط به اون المنت تصمیم می‌گیره که چه کاری انجام بده. تقریبا هیچ مستندات درست حسابی من پیدا نکردم که همه چیز رو توضیح داده باشه، اما سعی کردم به کمک آزمایش و خطا و همچنین دیدن فیلترهای مشابهی که برای کارهای مختلف توسعه پیدا کرده بود گلیم خودم رو از آب بیرون بکشم.

همچنین میشه این فیلترها رو به زبانی غیر از haskell توسعه داد. این زبان‌های پایتون، جاوا اسکریپت(در محیط nodejs)، پرل و php هست. من به علت آشنایی بیشترم با پایتون از اون استفاده کردم

نحوه توسعه

کل روند از این قراره که شما فایل پایتون رو بصورت یک فیلتر با دسترسی اجرایی در اختیار pandoc قرار میدید و اون تابع main رو صدا میزنه. پس از اون اطلاعات از stdin به برنامه داده شده و خروجی‌ها از stdout گرفته می‌شه. خود توسعه دهنده pandoc هم پکیجی به نام pandocfilters توسعه داده که با استفاده از pip یا ابزارهای مشابه قابل نصب و استفاده است.

تنها کاری که باید انجام بشه اینه که تابعی نوشته بشه و به عنوان آرگومان به تابع ‍‍toJSONFilter پاس داده بشه. این تابع بایستی چهارتا ورودی داشته باشه که به ترتیبت ‍‍def behead(key, value, format, meta): هستن و نوع، مقدار، فرمت خروجی و متادیتاهاست. همین من با استفاده از همین‌‌ها فیلترم رو توسعه دادم و گذاشتم روی گیت‌هاب که اگه دیگران هم خواستن ازش استفاده کنن.

همین!
پ.ن.: برای مطالعه بیشتر به اینجا مراجعه کنید.