ادیتور/ویرایشگر تحت کنسول خود را با C بسازید. (2)

قسمت اول { vrgl.ir | virgool.io } (قسمت قبلی)قسمت دوم { vrgl.ir | virgool.io } (این قسمت)نکته : ادیتور نوشته می شود اما ادیتر خوانده می شودلطفا : من برای این مطلب خیلی این ور آن ور رفتم ، بنابراین خیلی از چیز های درون مطلب در سایت مرجع نیست! چون که نوسینده این مطلب در سایت مرجع برایش اینکه «چگونه کار می کند» مهم نبوده ، برعکس من . بنابراین با دقت بخوانید چون خودم سودش رو بردم :) ولی زمان بیشتری نسبت به شما از دست دادم .همینطور اگر مشکلی پیدا کردید ، لطفا بگویید تا اصلاح کنم .همانطور که در قسمت قبل گفته شده. است ما یک ادیتور ساده با کد زیر داریم :#include <unistd.h> int main(){ char ch; while(read(STDIN_FILENO , &ch , 1) == 1 && ch != 'q'); return 0; } اما می خواهم با دقت به while نگاه کنید ، سوالی که پیش می آید این است که تابع read چه بر می گرداندread(STDIN_FILENO , &ch , 1) == 1و چرا این تابع 1 را بر می گرداند ؟؟ ( چون دوست دارد ? {استیکر خنده بود ، ویرگول دوربین نشان داد} ) ، این تابع تعداد بایت هایی که خوانده ، یا پارامتر سوم خودش را در قالب یک متغییر ssize_t بر می گرداند.قبل از اینکه دست به آزمایش بزنیم تا بهمیم تابع read چه بر می گرداند ،‌باید بفهمیم ssize_t چیست . که این سایت تعریف خوبی داره .ssize_t Include: <sys/types.h>. Alternatively, <aio.h>, <monetary.h>, <mqueue.h>, <stdio.h>, <sys/msg.h>, <sys/socket.h>, <sys/uio.h>, or <unistd.h>. Used for a count of bytes or an error indication. According to POSIX, it shall be a signed integer type capable of storing values at least in the range [-1, SSIZE_MAX], and the implementation shall support one or more programming environments where the width of ssize_t is no greater than the width of the type long. Glibc and most other implementations provide a length modifier for ssize_t for the printf(3) and the scanf(3) families of functions, which is z; resulting commonly in %zd or %zi for printing ssize_t values. Although z works for ssize_t on most implementations, portable POSIX programs should avoid using it—for example, by converting the value to intmax_t and using its length modifier (j). Conforming to: POSIX.1-2001 and later.ترجمه ssize_t :موجود در هدر <sys/stat.h> ، همچنین می توانید از طریق هدر های پایین به این تایپ دسترسی پیدا کنید :<aio.h><monetary.h><mqueue.h><stdio.h><sys/msg.h><sys/socket.h><sys/uio.h<unistd.h>که خوب ما هدر <unistd.h> رو وارد برنامه کردیمدر ادامه می گه : تایپ ssize_t استفاده می شود برای شماردن تعداد بایت ها یا نشانه ی خطا با توجه به استاندارد POSIX ، این باید signed باشد ( یعنی اعداد منفی را هم بتواند در نظر بگیرد ) و قادر به ذخیره مقدار هایی قوی با حداقل -۱ و حداکثر SSIZE_MAX . پیاده سازی باید از یک محیط برنامه نویسی یا بیشتر پشتیبانی کند که سایز ssize_t کمتر از سایز long باشد.کتابخانه سی گنو ( GLibC ) و اغلب پیاده سازی ها یک تعدیل کننده طول (همون format specifier خودمون هست برای تابع printf) برای ssize_t و برای توابع خانواده printf و scanf که z٪ هست. در نتیجه معمولا برای چاپ مقدارهای ssize_t از %zi یا %zd استفاده می شود. با اینکه z٪ در بسیاری از پیاده سازی ها برای ssize_t کار می کند ، برنامه های قابل حمل ( portable ) استاندارد POSIX باید از استفاده از آن پرهیز کنند ، برای مثال با وسیله تبدیل به intmax_t و استفاده از length modifier خودش (%j تعدیل کننده طول intmaxt_t هست . )به زبان آدمی زاد : مقداری که از -۱ شروع می شود و تا کمتر از long ادامه پیدا می کند.یادتان هست راجع به مقداری که read از unistd.h بر گشت می دهد گفتم ؟؟ خوب بیایید آزماش کنیم ( let's test ) source codeدر کد ، ما تابع read را فراخوانی کرده و نتیجه آن را با تابع printf چاپ می کنیم .هر دوی %zi و ٪zd (تعدیل کننده طول|length modifier) برای نوع ssize_t هستند.می بینیم که تابع read قرار هست ۳ بایت از ورودی بخواند و آن را در آرایه s بگذارد و تابع write قرار هست همین رشته s را بعد از این که تابع read محتویاتش را درون آرایه s گذاشت چاپ کند.تابع read زمانی ۰ را بر می گرداند که چیزی برای خواندن نباشد - برای ساخت ۰ باید Ctrl+D را فشار دهید تا به read() بگویید اینجا چیزی برای خواندن نیستو اما نتیجه :خروجیهمانطور که می بینید ، من برنامه را کامپایل کردم و آن را اجرا کردم .به عنوان ورودی hello world را به تابع read دادم و تابع read تعداد بایت هایی که خوانده که برابر با سه بایت hel بوده را بر می گرداند ، یعنی عدد ۳ {سه بایت خوانده} که می بینید در خروجی چاپ شده. دیگر چیزی را نیازمند توضیح نمی دانمTerminal modesCooked modeوقتی شما یک برنامه ای را که خودتان نوشتید را در ترمینال اجرا می کنید ، ترمینال در حالت canonical mode یا cooked mode هست ، در این حالت اطلاعات هنگامی به برنامه داده می شود که کاربر کلید enter را فشار بدهد. این حالت برای بسیار از برنامه ها کابردی هست ، اما این برای همه برنامه ها که قرار هست رابط کاربری بهتر و پیچیده تری داشته باشند (مثل ادیتور های متن ) مناسب نیست . ما می خواهیم به محض فشرده شدن یک کلید روی آن پردازش انجام دهیم ، چون ما می خواهیم فورا به آن جواب دهیم . ( کاملا قابل درک ) ما برای اینکار به raw mode نیازمند هستیم.Raw modeدر دیکشنری raw به معنی خام هست . متاسفانه تغییر دادن حالت (mode) ترمینال ( و یا درست تر - کنسول) کار ساده ای نیست . برای این کار باید flag های زیادی را در ترمینال تغییر داد (مانند c_lflag )‌ که آرام آرام توضیح می دهم.تغییر حالت کنسول/ترمینالما برای ارتباط با ترمینال از هدر termios.h استاندارد glibc برای استاندارد POSIX استفاده می کنیم (البته من با کامپایلر clang هم امتحان کردم و درست کار کرد و همچنین کامپایلر pcc {بعید می دونم تا حالا اسمش رو هم شنیده باشد} اما برعکس clang کامپایلر pcc کار نکرد.)هدر termios.h چیست ؟؟ بر طبق راهنمای لینوکس "Linux Manual Page" هر termios.h :The termios functions describe a general terminal interface that isprovided to control asynchronous communications ports.که ترجمه کنم می شود:توابع موجود در هدر termios.h یک رابط عمومی بین ترمینال را توضیح می دهد که فراهم کرده برای کنترل ناهمزمان پورت های ارتباطی .به زبان آدمیزاد :(برای ارتباط با ترمینال هست تا از این طریق وضعیت ترمینال را عوض کنید)termios structure(اگر نمی دانید structure در زبان های c/c++ چیست لطفا دست به دامان گوگل/duckduckgo شوید)هدر termisos.h دارای یک structure هست که اطلاعات مربوط به ترمینال در آن هست . برای مقدار دهی یک instance از این struct از تابع tcgetattr ( described below) استفاده می شود.طبق همیشه لینوکس منوال پیج به داد ما می رسه :) The termios structure Many of the functions described here have a termios_p argument that is a pointer to a termios structure. This structure contains at least the following members: tcflag_t c_iflag; /* input modes */ tcflag_t c_oflag; /* output modes */ tcflag_t c_cflag; /* control modes */ tcflag_t c_lflag; /* local modes */ cc_t c_cc[NCCS]; /* special characters */ The values that may be assigned to these fields are described below. In the case of the first four bit-mask fields, the definitions of some of the associated flags that may be set are exposed only if a specific feature test macro (see feature_test_macros(7)) is defined, as noted in brackets ("[]").ترجمه:خیلی از توابعی که اینجا توضیح داده شده ، یک آرگومنت *termios_p که یک پوینتر {اشاره گر} به یک ساختار (ترجمه تحت الفظی !ممنون) termios هست.این struct حداقل دارای عضو های زیر هست.tcflag_t c_iflag; /* input modes */ tcflag_t c_oflag; /* output modes */ tcflag_t c_cflag; /* control modes */ tcflag_t c_lflag; /* local modes */ cc_t c_cc[NCCS]; /* special characters */پیش پی نوشت : دیگر کامنت های را که می توانید انگلیسی بخوانید . مگه نه ؟؟پیش پی۲ نوشت : tcfalg_t رو نتونستم پیدا کنم ، بنابراین اطلاعات کمی اینجا هست. (خدا رو شکر که پیدا کردم )پس پی۳ نوشت : خوشبختانه گوگل ، ما را به گنج رساند :) پس یعنی اطلاعات کافی داریم .طبق اطلاعات این سایت : The <termios.h> header shall define the following data types through typedef: cc_t Used for terminal special characters. speed_t Used for terminal baud rates. tcflag_t Used for terminal modes. The above types shall be all unsigned integer types. The implementation shall support one or more programming environments in which the widths of cc_t, speed_t, and tcflag_t are no greater than the width of type long. The names of these programming environments can be obtained using the confstr() function or the getconf utility.هدر termios.h باید type های زیر را قرار دهد.cc_t : استفاده می شود برای کاراکار های خاص ترمینالspeed_t : استفاده می شود برای نرخ !علامت در ثانیه! ترمینال tcflag_t : استفاده می شود برای حالت های ترمینالهمه type های بالا ، باید unsigned int باشند. یعنی از ۰ تا maxintپیاده سازی زبان C باید در نظر داشته باشد که طول tcflag_t , speed_t و cc_t بیشتر از طول long نباشدبقیه اش رو نیازمند توضیح نمی دونم ، اگر فضولی مجالی برایتان نگذاشته بخوانید.این دو لینک زیر که مال سوس کد اندروید گوگل هستند صفحه های خیلی خیلی مهمی اند . (استفاده از می باش{ن|ی}{ن}د !ممنون = ممنوع){با سبک کدنویسی من آشنا شدید؟}فایل اصلی termios.h در سیستم عامل اندورید گوگل ، خلاصه و مفید :)https://android.googlesource.com/platform/prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.7-4.6/+/refs/heads/jb-dev/sysroot/usr/include/bits/termios.hدر فایل بالا ، لطفا کامنت خط ۳۸ تا ۴۰ را بخوانید ./* Get the system-dependent definitions of `struct termios', `tcflag_t', `cc_t', `speed_t', and all the macros specifying the flag bits. */ #include <bits/termios.h>که دارد می گوید : گرفتن تعاریف (sdefinition) وابسته به سیتم که شامل , 'struct termios' , 'tcflag_t' ، 'cc_t , 'speed_t' و همه ماکرو های تشخیص دهنده بایت های flag که یعنی در فایل bits/termios.h دنبال این سه تایپ و ماکرو های بگردید .اما typedef های ذکر شده ، در این هدر ، در هدر bits/termios.h وابسته هستند ، از آنجایی که من نتوانستم در سیستم گنو/لینوکس خودم فایل bits/termios.h رو پیدا کنم ، اما termios.h رو پیدا کردم ، از لینک زیر که باز هم سیستم عامل اندروید گوگل هست ، استفاده می کنم.https://android.googlesource.com/platform/prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.7-4.6/+/refs/heads/jb-dev/sysroot/usr/include/bits/termios.h لطفا خطوط ۲۴ تا ۴۳ را با دقت بخوانید.typedef unsigned char cc_t;typedef unsigned int speed_t;typedef unsigned int tcflag_t;#define NCCS 32struct termios { tcflag_t c_iflag; /* input mode flags */ tcflag_t c_oflag; /* output mode flags */ tcflag_t c_cflag; /* control mode flags */ tcflag_t c_lflag; /* local mode flags */ cc_t c_line; /* line discipline */ cc_t c_cc[NCCS]; /* control characters */ speed_t c_ispeed; /* input speed */ speed_t c_ospeed; /* output speed */#define _HAVE_STRUCT_TERMIOS_C_ISPEED 1#define _HAVE_STRUCT_TERMIOS_C_OSPEED 1 };ما برای تغییر و گرفتن وضعیت کنسول از توابع tcsetattr و tcgetattr استفاده می کنیم.الان به توضیح هر دو آن ها می پردازمتابع tcgetattr برای خوانایی بیشتر tc get attrاین تابع وضعیت فعلی ترمینال را می خواند (مثلا mode ترمیال را تشخیص می دهد و ... ) و چیزی را که تشخیص داده در یک struct termios می گذارد . int tcgetattr(int fd, struct termios *termios_p);Gnu/Linux Manual Pagetcgetattr() gets the parameters associated with the object referred by fd and storesthem in the termios structure referenced by termios_p. This function may be invokedfrom a background process; however, the terminal attributes may be subsequently changedby a foreground process.ترجمه متن بالا :تابع tcgetattr() پارامتری های مرتبط با آبجکت (شی) ارجاع شده توسط fd می گیرد و آنها را در struct termios که توسط termios_p رجوع کرده (به تابع ما رجوع کرده) ذخیره می کند/نگه می داردبه زبان آدمیزاد: وضعیت فعلی ترمینال را در struct termios که توسط termios_p به آن پاس داده شده می ریزد. تابع tcsetattr که برای خانوایی بیشتر tc set attrپایین هم declaration این تابع هست. (یک خط پایین تر)int tcsetattr(int fd, int optional_actions, const struct termios *termios_p);بر طبق راهنمای لینوکس Gnu/Linux Manual Page :tcsetattr() sets the parameters associated with the terminal (unlesssupport is required from the underlying hardware that is not available) from the termiosstructure referred to by termios_p.optional_actions specifies when the changes take effect:اینجا مثلا چند متغییر هست که توی نقل قول باکس (نمی دونم چیه) بعدی توضیح می دهمترجمه:تابع tcsetattr تنظیم می کند پارامتر های مرتبط با ترمینال را (مگر اینکه نیازمند تغییرات خیلی اساس سخت افزار باشد این تابع کمک نمی کند) بر طبق struct termios که اشاره شده توسط termios_p .ترجمه به زبان آدمیزاد:یک struct termios را توسط termios_p می گیرد و تنظیمات ترمینال را بر طبق این struct termios تغییر می دهد.خوب یادتون هست پایینش گفته بودم چند تا متغییر؟؟ اینجا تعریف آن چند متغییر رو گذاشتم optional_actions specifies when the changes take effect: TCSANOW the change occurs immediately.TCSADRAIN the change occurs after all output written to fd has been transmitted. This option should be used when changing parameters that affect output. TCSAFLUSH the change occurs after all output written to the object referred by fd has been transmitted, and all input that has been received but not read will be discarded before the change is made.پارامتر optional_action مشخص می کند که تغییرات چه زمانی اثر گذار بشوند.TCSANOWتغییر بلافاصله رخ می دهد (می بینید که آخرش هم NOW داره که "now" یعنی «الان» )TCSADRAINتغییر زمانی رخ می دهد که همه خروجی نوشته شده در fd منتقل شده. بهتر هست زمانی استفاده بشود که تغییر پارامتر ها روی خروجی تاثیر می گذارد.TCSAFLUSHتغییر بعد از منتقل شدن همه خروجی نوشته شده به objectای که با fd پاس داده شده ، و همه ورودی که گرفته شده ولی خوانده نشده قبل از ایجاد تغییرات کنار گذاشته می شوند . (ورودی خوانده نشده کنار گذاشته می شود)به پایان آمد این دفتر حکایت همچنان باقی استمگر نگفتم حکایت همچنان باقی هست ؟؟این قسمت خیلی تئوری بود ، قسمت بعدی هم کم تئوری نیست ، ولی خوب تئوری های این قسمت رو قسمت بعد آزمایش می کنیم. تو این قسمت و قسمت بعد ، من man لینوکس رو ترجمه کردم ، می تونید با دستورات زیر این صفحه ای که ترجمه کردم را کامل بخوانید.man termios man tcsetattr man tcgetattr # and so more thingsمن در این قسمت ، تا صفحه ۴۰۵ ام از ۶۶۸ صفحه ترجمه کردم . هورا پس یعنی ۲۶۳ صفحه دیگر مونده {هورا پت و متی :) }به نظرتون چند دقیقه شد ؟؟این مطلب الان ۲۵۰۰ کلمه داره! یا خداآخر ویرگول چظوری مطلب به این بلندی رو ۳ دقیقه در نظر گرفته ؟؟