موضوع مقاله: نحو (زبان‌‌های برنامه نویسی) | مخاطب: عمومی | لحن: دانشنامه‌ای | ساختار: عنوان، مقدمه، 3 بخش، جمع‌بندی | مقاله: | # نحو (زبان‌‌های برنامه نویسی) | مقدمه: در علوم کامپیوتر، نحو یک زبان رایانه‌ای مجموعه‌ای از قواعد است که ترکیب‌های مجاز از نمادها را برای تشکیل دستورات یا عبارات به درستی ساختار یافته در آن زبان تعریف می‌کند. | ## بخش 1: نحو (زبان‌‌های برنامه نویسی) | این مفهوم هم در زبان‌های برنامه‌نویسی (که سند نشان‌دهنده کد منبع است) و هم در زبان‌های نشانه‌گذاری (که سند نشان‌دهنده داده‌ها است) کاربرد دارد. زبان‌های کامپیوتری مبتنی بر متن بر اساس توالی کاراکترها هستند، در حالی که زبان‌های برنامه‌نویسی بصری بر اساس چیدمان فضایی و ارتباط بین نمادها (که می‌توانند متنی یا گرافیکی باشند) ساخته می‌شوند. در طراحی نحو یک زبان، طراح ممکن است با نوشتن مثال‌هایی از رشته‌های مجاز و غیرمجاز شروع کند و سپس سعی کند قواعد کلی را از این مثال‌ها استخراج نماید. بنابراین، نحو به فرم کد اشاره دارد و در مقابل آن، معناشناسی یا سمانتیک به معنای کد مربوط می‌شود. در پردازش زبان‌های کامپیوتری، پردازش معنایی معمولاً پس از پردازش نحوی انجام می‌شود؛ با این حال، در برخی موارد، پردازش معنایی برای تکمیل تحلیل نحوی ضروری است و این دو به صورت هم‌زمان یا موازی انجام می‌شوند. در یک کامپایلر، تحلیل نحوی بخش فرانت‌اند را تشکیل می‌دهد، در حالی که تحلیل معنایی بخش بک‌اند (و در صورت تمایز، بخش میانی) را شامل می‌شود. سطوح نحو نحو در زبان‌های برنامه‌نویسی معمولاً به سه سطح تقسیم می‌شود: واژه‌ها – سطح واژگانی (lexical)، که تعیین می‌کند چگونه نویسه‌ها (کاراکترها) توکن‌ها را تشکیل می‌دهند؛ عبارات – سطح دستوری (grammar)، به‌معنای محدود، که تعیین می‌کند چگونه توکن‌ها عبارات را تشکیل می‌دهند؛ مفهوم – که تعیین می‌کند اشیا یا نام متغیرها به چه چیزی اشاره دارند، آیا انواع داده معتبر هستند و… این تفکیک باعث ایجاد ماژولاریتی (ساختار پیمانه‌ای) می‌شود، به‌طوری که امکان توصیف و پردازش هر سطح به‌صورت جداگانه و اغلب مستقل فراهم می‌گردد. ابتدا، یک لکسر (lexer) توالی خطی کاراکترها را به توالی خطی توکن‌ها تبدیل می‌کند؛ این فرایند با عنوان «تحلیل واژگانی» یا لکسینگ (lexing) شناخته می‌شود. این مرحله اطمینان حاصل می‌کند که توالی توکن‌ها با گرامرهای رسمی زبان برنامه‌نویسی مطابقت دارد. مراحل AST و تحلیل زمینه‌ای می‌توانند به عنوان نوعی تحلیل معنایی در نظر گرفته شوند، زیرا آن‌ها به نحو معنا و تفسیر می‌افزایند؛ یا می‌توان آن‌ها را به عنوان پیاده‌سازی‌های غیررسمی و دستی از قواعد نحوی دانست که توصیف یا پیاده‌سازی رسمی‌شان دشوار یا ناخوشایند است. این ساختار پیمانه‌ای گاهی امکان‌پذیر است، اما در بسیاری از زبان‌های برنامه‌نویسی در دنیای واقعی، یک مرحلهٔ قبل به مرحله بعدی وابسته است – برای مثال، در زبان C، چیزی به‌نام «lexer hack» وجود دارد، چون توکنیزاسیون بسته به زمینه (context) متفاوت است. حتی در چنین مواردی نیز تحلیل نحوی غالباً به‌عنوان تقریبی از این مدل ایده‌آل در نظر گرفته می‌شود. گرامرهای عبارتی معمولاً در قالب گرامرهایی بسیار محدودتر از گرامرهای مستقل از زمینهٔ کامل تعریف می‌شوند، تا تحلیل آن‌ها ساده‌تر گردد؛ در حالی‌که پارسرهای LR می‌توانند هر زبان DCFL را در زمان خطی پارس کنند، پارسرهای ساده‌تر مانند LALR و حتی LL کارایی بیشتری دارند، اما تنها گرامرهایی را می‌توانند پردازش کنند که قواعد تولیدی آن‌ها محدودتر باشد. این ابزارها به صورت خودکار یک درخت نحو صوری تولید می‌کنند؛ سپس نویسندهٔ پارسر باید به‌صورت دستی کدی بنویسد که نحوه تبدیل این ساختار به درخت نحو انتزاعی را مشخص کند. | ## بخش 2: نحو (زبان‌‌های برنامه نویسی) | با وجود این ابزارهای خودکار، در بسیاری از موارد، تحلیل نحوی به‌صورت دستی پیاده‌سازی می‌شود، به دلایل مختلف – مثلاً ممکن است ساختار عبارتی مستقل از زمینه نباشد، یا پیاده‌سازی جایگزین عملکرد بهتری داشته باشد یا پیام‌های خطا را بهتر مدیریت کند، یا امکان تغییر گرامر را ساده‌تر سازد. با این حال، مثال‌های زیر نامعتبر هستند: (_ 1 1) خطای واژگانی (lexical): نماد '_' معتبر نیست (add 1 1 خطای نحوی (parsing): پرانتز بسته ')' فراموش شده است لکسر قادر به شناسایی خطای اول نیست – تنها چیزی که می‌داند این است که پس از تولید توکن LEFT_PAREN یعنی '('، باقی‌ماندهٔ برنامه نامعتبر است، زیرا هیچ قاعدهٔ واژگانی با '_' شروع نمی‌شود. خطای دوم در مرحلهٔ پارسینگ شناسایی می‌شود: پارسر، بر اساس توکن '('، قاعدهٔ تولیدی مربوط به «فهرست» (list) را شناسایی کرده (به عنوان تنها تطبیق موجود)، و بنابراین می‌تواند پیام خطا ارائه کند؛ البته به طور کلی این ممکن است مبهم باشد. خطاهای نوع (type errors) و خطاهای متغیرهای تعریف نشده گاهی وقتی در زمان کامپایل شناسایی می‌شوند، به‌عنوان خطای نحوی در نظر گرفته می‌شوند (که معمولاً در زبان‌های با نوع دهی قوی اتفاق می‌افتد)، هرچند معمول است که این نوع خطاها به‌جای آن به عنوان خطای معنایی دسته‌بندی شوند. برای مثال، کد زیر در زبان Python: 'a' + 1 دارای خطای نوع است زیرا یک رشتهٔ متنی را با یک عدد صحیح جمع می‌کند. چنین خطاهای نوعی را می‌توان در زمان کامپایل شناسایی کرد: اگر کامپایلر قواعد مجزایی داشته باشد که اجازه دهد عدد + عدد ولی نه رشته + عدد، در مرحلهٔ پارسینگ (تحلیل عبارتی) شناسایی می‌شوند. در برخی موارد این اعتبارسنجی اصلاً توسط کامپایلر انجام نمی‌شود و چنین خطاهایی تنها در زمان اجرا آشکار می‌شوند. در حالی که دربارهٔ اینکه آیا خطای نوعی که توسط کامپایلر شناسایی می‌شود باید خطای نحوی نامیده شود (یا بهتر است آن را خطای معنایی ایستا دانست) اختلاف نظر وجود دارد، اما خطاهای نوعی که فقط در زمان اجرای برنامه قابل شناسایی هستند، همواره به عنوان خطاهای معنایی در نظر گرفته می‌شوند، نه نحوی. تعریف نحو نحو در زبان‌های برنامه‌نویسی متنی معمولاً با استفاده از ترکیبی از عبارات منظم (برای ساختار واژگانی) و فرم بکوس–نائر (Backus–Naur form) (که یک فرامل برای ساختار دستوری است) تعریف می‌شود تا به‌صورت استقرایی، دسته‌های نحوی (غیرنهایی‌ها) و نمادهای نهایی را مشخص کند. این قواعد تحت تأثیر زمینهٔ اطراف یا ارجاع‌دهنده به آن بخش از نحو نیستند، در حالی که نحو وابسته به زمینه چنین محدودیت‌هایی را دارد. یک زبان می‌تواند گرامرهای معادل مختلفی داشته باشد، مانند عبارات منظم معادل (در سطح واژگانی) یا قواعد عبارتی متفاوت که همان زبان را تولید می‌کنند. استفاده از دستهٔ گسترده‌تری از گرامرها – مانند گرامرهای LR – می‌تواند منجر به گرامرهایی کوتاه‌تر یا ساده‌تر نسبت به دسته‌های محدودتر (مانند گرامرهای LL) شود که ممکن است نیازمند گرامرهای طولانی‌تری با قواعد بیشتر باشند. گرامرهای عبارتی مختلف ولی معادل، ممکن است درخت‌های نحوی متفاوتی تولید کنند، گرچه زبان زیربنایی (مجموعه اسناد معتبر) یکسان باقی می‌ماند. در اینجا، رقم‌های ده‌دهی، حروف بزرگ و کوچک انگلیسی و پرانتزها جزو نمادهای نهایی (ترمینال) محسوب می‌شوند. | ## بخش 3: نحو (زبان‌‌های برنامه نویسی) | نمونه‌هایی از توالی‌های توکن که طبق این گرامر به درستی شکل گرفته‌اند عبارت‌اند از: '12345', ' ', '(A B C232 (1 ' گرامرهای پیچیده گرامری که برای تعریف یک زبان برنامه‌نویسی لازم است، را می‌توان با جایگاه آن در سلسله مراتب چامسکی (Chomsky hierarchy) طبقه‌بندی کرد. در برخی زبان‌ها مانند Perl و Lisp، تعریف (یا پیاده‌سازی) زبان به گونه‌ای است که امکان اجرای سازه‌هایی در مرحلهٔ پارسینگ را فراهم می‌کند. علاوه بر این، این زبان‌ها دارای سازه‌هایی هستند که به برنامه‌نویس اجازه می‌دهند رفتار پارسر را تغییر دهد. این ترکیب به طور مؤثری مرز میان پارس کردن و اجرا را محو می‌کند و موجب می‌شود تحلیل نحوی در این زبان‌ها مسئله‌ای حل‌ناپذیر (undecidable) باشد؛ به این معنا که ممکن است مرحلهٔ پارسینگ به پایان نرسد. برای مثال، در Perl امکان اجرای کد در حین پارس کردن با استفاده از دستور BEGIN وجود دارد، و پروتوتایپ توابع در Perl می‌توانند تفسیر نحوی کد باقی‌مانده را تغییر دهند، و حتی ممکن است اعتبار نحوی آن را نیز تحت تأثیر قرار دهند. به‌صورت محاوره‌ای، این حالت با عبارت مشهور «فقط Perl می‌تونه Perl رو پارس کنه» توصیف می‌شود (زیرا کد باید در حین پارس اجرا شود و می‌تواند گرامر را تغییر دهد)، یا به شکل قوی‌تر «حتی خود Perl هم نمی‌تونه Perl رو پارس کند» (چون از نظر تئوری این فرایند حل‌ناپذیر است). در مقابل، ماکروهای زبان C صرفاً جایگزینی رشته‌ای هستند و نیازی به اجرای کد ندارند. نحو در مقابل معناشناسی نحو (syntax) یک زبان، شکل و قالب برنامه‌ای معتبر را توصیف می‌کند، اما هیچ اطلاعاتی دربارهٔ معنای آن برنامه یا نتیجهٔ اجرای آن ارائه نمی‌دهد. معنایی که به ترکیبی از نمادها داده می‌شود، توسط معناشناسی (semantics) مشخص می‌شود (خواه به‌صورت رسمی و ریاضی، یا به صورت کدنویسی شده در پیاده‌سازی مرجع). نحو باید ابتدا به درستی برقرار شود تا معناشناسی بتواند از آن برداشت معنایی داشته باشد. بسیاری از برنامه‌هایی که از نظر نحوی درست هستند، با توجه به قوانین زبان، همچنان نادرست ساختاری (ill-formed) محسوب می‌شوند؛ و ممکن است (بسته به مشخصات زبان و صحت پیاده‌سازی آن) در زمان ترجمه یا اجرا منجر به خطا شوند. حتی زمانی‌که یک برنامه از نظر نحوی و معنایی در زبان تعریف شده باشد، ممکن است معنایی داشته باشد که با قصد نویسندهٔ برنامه متفاوت باشد. با استفاده از زبان طبیعی به‌عنوان مثال، ممکن است نتوان به جمله‌ای که از نظر دستوری صحیح است، معنایی نسبت داد، یا جمله‌ای ممکن است درست ساخته شده باشد اما معنایی نادرست را بیان کند: «ایده‌های سبزِ بی‌رنگ به شدت می‌خوابند.» و از نظر دستوری درست است، اما هیچ معنای پذیرفته‌شده‌ای ندارد. «جان یک مرد مجردِ متأهل است.» از نظر دستوری صحیح است، اما معنایی متناقض دارد و نمی‌تواند درست باشد. | ## جمع‌بندی | هرچند که کامپایلرهای برخی زبان‌های برنامه‌نویسی (مانند Java یا C#) می‌توانند چنین خطاهایی را شناسایی کنند، اما این نوع خطاها باید به‌عنوان خطاهای معنایی در نظر گرفته شوند، نه خطاهای نحوی.