| RE2SWIFT(1) | RE2SWIFT(1) |
نام (NAME)
re2swift - تولید تحلیلگرهای لغوی سریع برای Swift
خلاصه دستور (SYNOPSIS)
re2swift [ OPTIONS ] [ WARNINGS ] INPUT
ورودی میتواند یک فایل یا - برای stdin باشد.
مقدمه (INTRODUCTION)
ابزار re2swift به عنوان یک پیشپردازنده عمل میکند. این ابزار فایل ورودی را (که معمولاً برنامهای به زبان Swift است، اما میتواند هر چیز دیگری نیز باشد) خوانده و به دنبال بلوکهای کدی میگردد که درون نشانگرهای آغاز/پایان با ساختار ویژه محصور شدهاند. متن خارج از این بلوکها عیناً در فایل خروجی کپی میشود. محتوای درون بلوکها توسط re2swift پردازش میشود؛ این ابزار آنها را به کد زبان Swift ترجمه کرده و کد تولیدشده را به جای آن بلوک در خروجی قرار میدهد.
در اینجا مثالی از یک برنامه کوچک آورده شده است که بررسی میکند آیا یک رشته دادهشده حاوی یک عدد دهدهی است یا خیر:
// re2swift $INPUT -o $OUTPUT -i
func lex(_ yyinput: UnsafePointer<UInt8>) -> Bool {
var yycursor = 0
/*!re2c
re2c:yyfill:enable = 0;
[1-9][0-9]* { return true }
* { return false }
*/
}
assert(lex("1234"))
در خروجی، re2swift بلوک میانی را با کد تولیدشده جایگزین کرده است:
/* Generated by re2swift */
// re2swift $INPUT -o $OUTPUT -i
func lex(_ yyinput: UnsafePointer<UInt8>) -> Bool {
var yycursor = 0
var yych: UInt8 = 0
var yystate: UInt = 0
yyl: while true {
switch yystate {
case 0:
yych = yyinput[yycursor]
yycursor += 1
switch yych {
case 0x31...0x39:
yystate = 2
continue yyl
default:
yystate = 1
continue yyl
}
case 1: return false
case 2:
yych = yyinput[yycursor]
switch yych {
case 0x30...0x39:
yycursor += 1
yystate = 2
continue yyl
default:
yystate = 3
continue yyl
}
case 3: return true
default: fatalError("internal lexer error")
}
}
}
assert(lex("1234"))
یک برنامه re2swift از دنبالهای از بلوکها درآمیخته با کد به زبان مقصد تشکیل میشود. یک بلوک ممکن است شامل تعاریف، پیکربندیها، قواعد، کنشها و دستورالعملها به هر ترتیبی باشد:
- name = regular-expression ;
- یک تعریف، name را به regular-expression مقید میسازد. نامها میتوانند شامل نویسههای الفبایی-عددی و زیرخط باشند. بخش regular expressions مروری کلی بر نحو re2swift برای عبارتهای منظم ارائه میدهد. پس از تعریف، میتوان از name در سایر عبارتهای منظم و قواعد استفاده کرد. بازگشت در تعاریف نامگذاریشده مجاز نیست و هر نام باید پیش از استفاده تعریف شود. یک بلوک، تعاریف نامگذاریشده را از حوزه سراسری (global scope) به ارث میبرد. بازتعریف نامی که در حوزه کنونی وجود دارد یک خطا محسوب میشود.
- configuration = value ;
- یک پیکربندی امکان تغییر رفتار re2swift و سفارشیسازی کد تولیدشده را فراهم میکند. برای مشاهده فهرست کامل پیکربندیهای پشتیبانیشده توسط re2swift، بخش configurations را ببینید. بسته به یک پیکربندی خاص، value میتواند یک کلمه کلیدی، یک عدد صحیح غیرمنفی یا یک رشته تکخطی باشد که در صورت عدم تشکیل از نویسههای الفبایی-عددی، باید در نقلقول تکی یا دوگانه قرار گیرد. یک بلوک، پیکربندیها را از حوزه سراسری به ارث میبرد و میتواند آنها را بازتعریف کرده یا موارد جدیدی بیفزاید. پیکربندیهای تعریفشده درون یک بلوک، بر کل بلوک تأثیر میگذارند، حتی اگر در انتهای آن ظاهر شوند.
- regular-expression code
- یک قاعده، regular-expression را به کنش معنایی آن پیوند میدهد (یک بلوک کد درون آکوالاد، یا بلوکی از کد که با := شروع شده و در یک خط جدیدِ پس از آن با هر نویسهای غیر از فضای خالی پایان مییابد). اگر regular-expression مطابقت پیدا کند، code مرتبط اجرا میشود. اگر چندین قاعده مطابقت داشته باشند، طولانیترین تطابق اولویت دارد. اگر چندین قاعده با رشته یکسانی تطابق پیدا کنند، اولین قاعده اولویت دارد. دو قاعده ویژه وجود دارد: قاعده پیشفرض * و قاعده پایان ورودی $. قاعده پیشفرض همواره باید تعریف شود، صرفنظر از جایگاهش در بلوک دارای کمترین اولویت است، و با هر واحد کد (code unit) مطابقت مییابد (نه لزوماً یک نویسه معتبر، بخش encoding support را ببینید). در صورت استفاده از روش متناظر برای handling the end of input، قاعده پایان ورودی باید تعریف شود. همراه با start conditions قواعد نحو پیچیدهتری خواهند داشت.
- !action code
- یک کنش، بلوک کدی (code) تعریفشده توسط کاربر را به نقطه مشخصی در ماشین حالت متناهی تولیدشده متصل میکند (همانطور که کنشهای معنایی کد را به حالتهای پایانی پیوند میدهند). برای فهرست کاملی از کنشهای از پیشتعریفشده، بخش actions را ببینید.
- !directive ;
- یک دستورالعمل یکی از گزارههای ویژه از پیشتعریفشده است. هر دستورالعمل هدف منحصربهفردی دارد. برای جزئیات بخش directives را ببینید.
بلوکها (Blocks)
نشانگرهای شروع و پایان بلوک یا /*!re2c و */ هستند، یا %{ و %} (از هر دو شیوه پشتیبانی میشود). از نسخه 2.2 به بعد، بلوکها میتوانند نامهای اختیاری داشته باشند که امکان ارجاع به آنها را در سایر بلوکها فراهم میکند. انواع مختلفی از بلوکها وجود دارند:
- /*!re2c[:<name>] ... */ or %{[:<name>] ... %}
- یک بلوک سراسری حاوی تعاریف، پیکربندیها، قواعد و دستورالعملها است. re2swift عبارتهای منظم مرتبط با هر قاعده را در یک اتوماتای متناهی معین کامپایل کرده، آن را به شکل پرشهای شرطی در زبان مقصد کدگذاری میکند و بلوک را با کد تولیدشده جایگزین میسازد. نامها و پیکربندیهای تعریفشده در یک بلوک سراسری به حوزه سراسری اضافه میشوند و برای بلوکهای پس از آن قابل مشاهده خواهند بود. در آغاز برنامه، حوزه سراسری با options خط فرمان مقداردهی اولیه میشود.
- /*!local:re2c[:<name>] ... */ or %{local[:<name>] ... %}
- یک بلوک محلی شبیه بلوک سراسری است، اما نامها و پیکربندیهای موجود در آن حوزه محلی دارند (بر سایر بلوکها تأثیری نمیگذارند).
- /*!rules:re2c[:<name>] ... */ or %{rules[:<name>] ... %}
- یک بلوک قواعد مانند یک بلوک محلی است، اما خودش کدی تولید نمیکند و هیچ تعریفی را هم به حوزه سراسری اضافه نمینماید -- هدف آن، استفاده مجدد در سایر بلوکها است. این روشی برای اشتراکگذاری کد است (جزئیات بیشتر در بخش reusable blocks). پیش از نسخه 2.2 از re2swift، بلوکهای قواعد نیازمند گزینه -r --reusable بودند.
- /*!use:re2c[:<name>] ... */ or %{use[:<name>] ... %}
- یک بلوک use که به یک بلوک قواعد از پیش تعریفشده ارجاع میدهد. اگر نام مشخص شده باشد، re2swift به دنبال بلوک قواعدی با این نام میگردد. در غیر این صورت، از تازهترین بلوک قواعد (چه نامدار یا بینام) استفاده میشود. یک بلوک use میتواند تعاریف، پیکربندیها و قواعد مختص به خود را اضافه کند، که این موارد به بلوک قواعد ارجاعشده اضافه میشوند. پیش از نسخه 2.2 از re2swift، بلوکهای use نیازمند گزینه -r --reusable بودند.
- /*!max:re2c[:<name1>[:<name2>...]] ... */ or %{max[:<name1>[:<name2>...]] ... %}
- بلوکی که تعریف YYMAXFILL را تولید میکند. یک فهرست اختیاری از نامهای بلوک مشخص میکند که هنگام محاسبه مقدار YYMAXFILL کدام بلوکها باید لحاظ شوند (اگر فهرست خالی باشد، تمام بلوکها لحاظ میشوند). به طور پیشفرض، کد تولیدشده یک تعریف ماکرو برای C (#define YYMAXFILL <n>) یا یک متغیر سراسری برای Go (var YYMAXFILL int = <n>) است. این بخش را میتوان با پیکربندی اختیاری format سفارشی کرد که یک رشته الگو را مشخص میکند که در آن @@{max} (یا به اختصار @@) با مقدار عددی YYMAXFILL جایگزین میشود.
- /*!maxnmatch:re2c[:<name1>[:<name2>...]] ... */ or %{maxnmatch[:<name1>[:<name2>...]] ... %}
- بلوکی که تعریف YYMAXNMATCH را تولید میکند (نیازمند گزینه -P --posix-captures است). یک فهرست اختیاری از نامهای بلوک مشخص میکند که هنگام محاسبه مقدار YYMAXNMATCH کدام بلوکها باید لحاظ شوند (اگر فهرست خالی باشد، همه بلوکها لحاظ میشوند). به طور پیشفرض، کد تولیدشده یک تعریف ماکرو برای C (#define YYMAXNMATCH <n>) یا یک متغیر سراسری برای Go (var YYMAXNMATCH int = <n>) است. این بخش را میتوان با پیکربندی اختیاری format سفارشی کرد که یک رشته الگو را مشخص میکند که در آن @@{max} (یا به اختصار @@) با مقدار عددی YYMAXNMATCH جایگزین میشود.
- /*!stags:re2c[:<name1>[:<name2>...]] ... */, /*!mtags:re2c[:<name1>[:<name2>...]] ... */ or %{stags[:<name1>[:<name2>...]] ... %}, %{mtags[:<name1>[:<name2>...]] ... %{
- بلوکهایی که قطعه کدی از نوع الگو را تعیین میکنند که برای هر متغیر s-tag/m-tag تولیدشده توسط re2swift بسط مییابد. یک فهرست اختیاری از نامهای بلوک مشخص میکند که هنگام محاسبه مجموعه متغیرهای برچسب کدام بلوکها باید لحاظ شوند (اگر فهرست خالی باشد، همه بلوکها لحاظ میشوند). دو پیکربندی اختیاری وجود دارد: format و separator. پیکربندی format یک رشته الگو را مشخص میکند که در آن @@{tag} (یا به اختصار @@) با نام هر متغیر برچسب جایگزین میشود. پیکربندی separator قطعه کدی را مشخص میکند که برای متصل کردن قطعات format تولیدشده برای متغیرهای برچسب مختلف به کار میرود.
- /*!svars:re2c[:<name1>[:<name2>...]] ... */, /*!mvars:re2c[:<name1>[:<name2>...]] ... */ or %{svars[:<name1>[:<name2>...]] ... %}, %{mvars[:<name1>[:<name2>...]] ... %{
- بلوکهایی که قطعه کدی از نوع الگو را تعیین میکنند که برای هر s-tag/m-tag که یا صراحتاً توسط قواعد مشخص شده است (با گزینه --tags) یا به طور ضمنی توسط re2swift ایجاد شده است (با گزینههای --captvars یا --posix-captvars) بسط مییابد. یک فهرست اختیاری از نامهای بلوک مشخص میکند که هنگام محاسبه مجموعه برچسبها کدام بلوکها باید لحاظ شوند (اگر فهرست خالی باشد، تمام بلوکها لحاظ میشوند). دو پیکربندی اختیاری وجود دارد: format و separator. پیکربندی format یک رشته الگو را مشخص میکند که در آن @@{tag} (یا به اختصار @@) با نام هر برچسب جایگزین میشود. پیکربندی separator قطعه کدی را مشخص میکند که برای متصل کردن قطعات format تولیدشده برای برچسبهای مختلف به کار میرود.
- /*!getstate:re2c[:<name1>[:<name2>...]] ... */ or %{getstate[:<name1>[:<name2>...]] ... %}
- بلوکی که یک توزیع شرطی بر روی وضعیت lexer تولید میکند (نیازمند گزینه --storable-state است). یک فهرست اختیاری از نامهای بلوک مشخص میکند که کدام بلوکها باید در توزیع وضعیت گنجانده شوند. گذار پیشفرض به برچسب آغازین اولین بلوک در فهرست انجام میشود. اگر فهرست خالی باشد، همه بلوکها لحاظ میشوند و گذار پیشفرض به اولین بلوک در فایل که دارای برچسب آغازین است انجام میپذیرد. این نوع بلوک با گزینه --loop-switch ناسازگار است، زیرا نیازمند گذارهای بینبلوکی است که بدون goto یا فراخوانیهای تابع پشتیبانی نمیشوند.
- /*!conditions:re2c[:<name1>[:<name2>...]] ... */, /*!types:re2c... */ or %{conditions[:<name1>[:<name2>...]] ... %}, %{types... %}
- بلوکی که شمارش شرایط (condition enumeration) را تولید میکند (نیازمند گزینه --conditions است). یک فهرست اختیاری از نامهای بلوک مشخص میکند که هنگام محاسبه مجموعه شرایط، کدام بلوکها باید لحاظ شوند (اگر فهرست خالی باشد، تمام بلوکها لحاظ میشوند). به طور پیشفرض، کد تولیدشده یک شمارش YYCONDTYPE است. این بخش میتواند با پیکربندیهای اختیاری format و separator سفارشیسازی شود. پیکربندی format یک رشته الگو را مشخص میکند که در آن @@{cond} (یا به اختصار @@) با نام هر شرط، و @@{num} با نمایه عددی آن شرط جایگزین میشود. پیکربندی separator قطعه کدی را مشخص میکند که برای متصل کردن قطعات format تولیدشده برای شرایط مختلف به کار میرود.
- /*!include:re2c <file> */ or %{include <file> %}
- این بلوک به فرد اجازه میدهد <file> را درج کند، که باید یک مسیر فایل درون نقلقول دوگانه باشد. محتویات فایل به صورت عینبهعین به جای این بلوک جایگزین میشود، درست همانگونه که #include در C/C++ عمل میکند. این بلوک میتواند به همراه گزینه --depfile برای تولید وابستگیهای سیستم ساخت به فایلهای درجشده استفاده شود.
- /*!header:re2c:on*/ or %{header:on %}
- این بلوک نقطه آغاز فایل سرآیند (header) را علامتگذاری میکند. همهچیز پس از آن تا بلوک header:off بعدی توسط re2swift پردازش شده و در فایل سرآیند مشخصشده با گزینه -t --type-header نوشته میشود.
- /*!header:re2c:off*/ or %{header:off %}
- این بلوک پایان فایل سرآیندی که با بلوک header:on*/ آغاز شده بود را علامتگذاری میکند.
- /*!ignore:re2c ... */ or %{ignore ... %}
- بلوکی که
محتویات آن
نادیده
گرفته شده و
از فایل
خروجی حذف
میشود.
در اینجا فهرست کاملی از پیکربندیهای پشتیبانیشده توسط re2swift آمده است:
- re2c:api, re2c:input
- همانند گزینهٔ --api است.
- re2c:api:sigil
- نشانه ("sigil") مورد استفاده برای جاینگهدارهای آرگومان در ساختارهای اولیهٔ API را مشخص میکند. مقدار پیشفرض @@ است. یک جاینگهدار با sigil شروع شده و به دنبال آن نام آرگومان درون دو آکولاد قرار میگیرد. برای مثال، اگر sigil روی $ تنظیم شود، جاینگهدارها به شکل ${name} خواهند بود. APIهای تکآرگومانی میتوانند از نمادگذاری مختصر بدون نام درون آکولادها استفاده کنند. این گزینه میتواند با گزینههای مختص به هر یک از ساختارهای اولیهٔ API بازنویسی شود، مانند re2c:YYFILL@len برای YYFILL.
- re2c:api:style
- سبک API را تعیین میکند. مقادیر ممکن عبارتند از functions (پیشفرض برای C) و free-form (پیشفرض برای Go و Rust). در سبک functions ساختارهای اولیهٔ API همراه با فهرست آرگومانها درون پرانتز به دنبال نام ساختار اولیه تولید میشوند. آرگومانها تنها برای پارامترهای خودکار تولیدشده فراهم میشوند (مانند تعداد نویسههای ارسالی به YYFILL)، و نه برای زمینهٔ کلی تحلیلگر لغوی، از این رو ساختارهای اولیه بیشتر شبیه ماکروها در C/C++ یا بستارها در Go و Rust عمل میکنند. در سبک free-form ساختارهای اولیهٔ API شکل ثابتی ندارند: آنها باید به صورت رشتههایی شامل قطعهکدهای آزاد با متغیرهای جایگزینشده به فرم @@{var} یا @@ تعریف شوند (که متناظر با آرگومانها در سبک تابعگونه هستند). این پیکربندی ممکن است برای هر ساختار اولیهٔ API به صورت مجزا بازنویسی شود؛ برای نمونه پیکربندی re2c:YYFILL:naked را برای YYFILL ببینید.
- re2c:bit-vectors, re2c:flags:bit-vectors, re2c:flags:b
- همانند گزینهٔ --bit-vectors است، اما میتواند بر پایهٔ هر بلوک پیکربندی شود.
- re2c:captures, re2c:leftmost-captures
- همانند گزینهٔ --leftmost-captures است، اما میتواند بر پایهٔ هر بلوک پیکربندی شود.
- re2c:captvars, re2c:leftmost-captvars
- همانند گزینهٔ --leftmost-captvars است، اما میتواند بر پایهٔ هر بلوک پیکربندی شود.
- re2c:case-insensitive, re2c:flags:case-insensitive
- همانند گزینهٔ --case-insensitive است، اما میتواند بر پایهٔ هر بلوک پیکربندی شود.
- re2c:case-inverted, re2c:flags:case-inverted
- همانند گزینهٔ --case-inverted است، اما میتواند بر پایهٔ هر بلوک پیکربندی شود.
- re2c:case-ranges, re2c:flags:case-ranges
- همانند گزینهٔ --case-ranges است، اما میتواند بر پایهٔ هر بلوک پیکربندی شود.
- re2c:computed-gotos, re2c:flags:computed-gotos, re2c:flags:g
- همانند گزینهٔ --computed-gotos است، اما میتواند بر پایهٔ هر بلوک پیکربندی شود.
- re2c:computed-gotos:relative, re2c:cgoto:relative
- همانند گزینهٔ --computed-gotos-relative است، اما میتواند بر پایهٔ هر بلوک پیکربندی شود.
- re2c:computed-gotos:threshold, re2c:cgoto:threshold
- در صورت استفاده از goto محاسباتی، این پیکربندی آستانهٔ پیچیدگیای را مشخص میکند که باعث تولید جدولهای پرش بهجای دستورهای تودرتوی if و نگاشتهای بیتی میشود. مقدار پیشفرض 9 است.
- re2c:cond:abort
- اگر روی یک مقدار عددی صحیح مثبت تنظیم شود، حالت پیشفرض در هدایتگر وضعیتِ تولیدشده اجرای برنامه را متوقف میکند.
- re2c:cond:goto
- قطعهکدی را مشخص میکند که برای قواعد میانبر خودکار تولیدشدهٔ :=> در شرطها استفاده میشود. پیشفرض goto @@; است. جاینگهدار @@ با نام شرط جایگزین میشود (پیکربندیهای re2c:api:sigil و re2c:cond:goto@cond را ببینید).
- re2c:cond:goto@cond
- نشانهٔ مورد استفاده برای جایگزینی آرگومان در تعریف re2c:cond:goto را مشخص میکند. مقدار پیشفرض @@ است. پیکربندی عمومیتر re2c:api:sigil را بازنویسی میکند.
- re2c:cond:divider
- جداکنندهٔ بلوکهای شرط را تعریف میکند. مقدار پیشفرض /* *********************************** */ است. جاینگهدارها با نام شرط جایگزین میشوند (پیکربندیهای re2c:api;sigil و re2c:cond:divider@cond را ببینید).
- re2c:cond:divider@cond
- نشانهٔ مورد استفاده برای جایگزینی آرگومان در تعریف re2c:cond:divider را مشخص میکند. مقدار پیشفرض @@ است. پیکربندی عمومیتر re2c:api:sigil را بازنویسی میکند.
- re2c:cond:prefix, re2c:condprefix
- پیشوند مورد استفاده برای برچسبهای شرط را مشخص میکند. پیشفرض yyc_ است.
- re2c:cond:enumprefix, re2c:condenumprefix
- پیشوند مورد استفاده برای شناسههای شرط را مشخص میکند. پیشفرض yyc است.
- re2c:debug-output, re2c:flags:debug-output, re2c:flags:d
- همانند گزینهٔ --debug-output است، اما میتواند بر پایهٔ هر بلوک پیکربندی شود.
- re2c:empty-class, re2c:flags:empty-class
- همانند گزینهٔ --empty-class است، اما میتواند بر پایهٔ هر بلوک پیکربندی شود.
- re2c:encoding:ebcdic, re2c:flags:ecb, re2c:flags:e
- همانند گزینهٔ --ebcdic است، اما میتواند بر پایهٔ هر بلوک پیکربندی شود.
- re2c:encoding:ucs2, re2c:flags:wide-chars, re2c:flags:w
- همانند گزینهٔ --ucs2 است، اما میتواند بر پایهٔ هر بلوک پیکربندی شود.
- re2c:encoding:utf8, re2c:flags:utf-8, re2c:flags:8
- همانند گزینهٔ --utf8 است، اما میتواند بر پایهٔ هر بلوک پیکربندی شود.
- re2c:encoding:utf16, re2c:flags:utf-16, re2c:flags:x
- همانند گزینهٔ --utf16 است، اما میتواند بر پایهٔ هر بلوک پیکربندی شود.
- re2c:encoding:utf32, re2c:flags:unicode, re2c:flags:u
- همانند گزینهٔ --utf32 است، اما میتواند بر پایهٔ هر بلوک پیکربندی شود.
- re2c:encoding-policy, re2c:flags:encoding-policy
- همانند گزینهٔ --encoding-policy است، اما میتواند بر پایهٔ هر بلوک پیکربندی شود.
- re2c:eof
- نماد نگهبان مورد استفاده با قاعدهٔ پایان ورودی $ را مشخص میکند. مقدار پیشفرض -1 است (قاعدهٔ $ استفاده نمیشود). مقادیر ممکن دیگر شامل تمامی واحدهای کد معتبر هستند. تنها اعداد دهدهی شناسایی میشوند.
- re2c:header, re2c:flags:type-header, re2c:flags:t
- نام فایل سرآیند تولیدشده را به صورت نسبی نسبت به دایرکتوری فایل خروجی مشخص میکند. همانند گزینهٔ --header است با این تفاوت که مسیر فایل نسبی است.
- re2c:indent:string
- رشتهٔ مورد استفاده برای دندانهگذاری را مشخص میکند. پیشفرض یک نویسهٔ تب تک "\t" است. رشتهٔ دندانهگذاری تنها باید شامل نویسههای فاصلهٔ خالی باشد. برای غیرفعال کردن کامل دندانهگذاری، این پیکربندی را روی یک رشتهٔ خالی تنظیم کنید.
- re2c:indent:top
- حداقل مقدار دندانهگذاری برای استفاده را مشخص میکند. مقدار پیشفرض صفر است. این مقدار باید یک عدد صحیح غیرمنفی باشد.
- re2c:invert-captures
- همانند گزینهٔ --invert-captures است، اما میتواند بر پایهٔ هر بلوک پیکربندی شود.
- re2c:label:prefix, re2c:labelprefix
- پیشوند مورد استفاده برای برچسبهای وضعیت DFA را مشخص میکند. پیشفرض yy است.
- re2c:label:start, re2c:startlabel
- تولید برچسب شروع بلوک را کنترل میکند. مقدار پیشفرض صفر است، به این معنی که برچسب شروع تنها در صورت استفاده تولید میشود. یک مقدار عددی صحیح بزرگتر از صفر، حتی اگر برچسب شروع توسط تحلیلگر لغوی استفاده نشود، تولید آن را اجباری میکند. مقدار رشتهای نیز تولید برچسب شروع را اجباری کرده و نام برچسب را روی رشتهٔ مشخصشده تنظیم میکند. این پیکربندی تنها روی بلوک فعلی اعمال میشود (برای بلوک بعدی به مقدار پیشفرض بازنشانی میشود).
- re2c:label:yyFillLabel
- پیشوند برچسبهای YYFILL مورد استفاده همراه با re2c:eof و در حالت وضعیت قابلذخیره را مشخص میکند.
- re2c:label:yyloop
- نام برچسب نشاندهندهٔ شروع حلقهٔ تحلیلگر لغوی با گزینهٔ --loop-switch را مشخص میکند. پیشفرض yyloop است.
- re2c:label:yyNext
- نام برچسب اختیاری را مشخص میکند که بعد از سوییچ YYGETSTATE در حالت وضعیت قابلذخیره میآید (با re2c:state:nextlabel فعال میشود). پیشفرض yyNext است.
- re2c:lookahead, re2c:flags:lookahead
- منسوخ شده است (گزینهٔ منسوخشدهٔ --no-lookahead را ببینید).
- re2c:monadic
- اگر روی غیرصفر تنظیم شود، تحلیلگر لغوی تولیدشده از نشانهگذاری مونادیک استفاده خواهد کرد (این پیکربندی مختص به Haskell است).
- re2c:nested-ifs, re2c:flags:nested-ifs, re2c:flags:s
- همانند گزینهٔ --nested-ifs است، اما میتواند بر پایهٔ هر بلوک پیکربندی شود.
- re2c:posix-captures, re2c:flags:posix-captures, re2c:flags:P
- همانند گزینهٔ --posix-captures است، اما میتواند بر پایهٔ هر بلوک پیکربندی شود.
- re2c:posix-captvars
- همانند گزینهٔ --posix-captvars است، اما میتواند بر پایهٔ هر بلوک پیکربندی شود.
- همانند گزینهٔ --tags است، اما میتواند بر پایهٔ هر بلوک پیکربندی شود.
- عبارت مورد استفاده برای متغیرهای برچسب را مشخص میکند. به طور پیشفرض re2swift عبارتهایی به فرم yyt<N> تولید میکند. این حالت ممکن است ناخوشایند باشد، برای مثال اگر متغیرهای برچسب به عنوان فیلدهایی در یک struct تعریف شده باشند. تمامی رخدادهای @@{tag} یا @@ با نام واقعی برچسب جایگزین میشوند. برای مثال، re2c:tags:expression = "s.@@"; منجر به تولید عبارتهایی به فرم s.yyt<N> در کد تولیدشده میشود. همچنین پیکربندی re2c:api:sigil را ببینید..TP re2c:tags:negative عبارت ثابتی را مشخص میکند که برای مقدار منفی تگ استفاده میشود (معمولاً در صورتی که تگها آفستهای عدد صحیح در رشته ورودی باشند، مقدار آن -1 است یا اگر اشارهگر باشند، اشارهگر تهی خواهد بود).
- پیشوند نام متغیرهای تگ را مشخص میکند. مقدار پیشفرض yyt است.
- re2c:sentinel
- نماد دیدهبان (sentinel) مورد استفاده برای بررسیهای پایان ورودی را مشخص میکند (زمانی که بررسیهای محدوده با re2c:yyfill:enable = 0; غیرفعال شده باشند و re2c:eof تنظیم نشده باشد). این پیکربندی بر تولید کد تأثیری ندارد: هدف آن اطمینان از عدم وجود نماد دیدهبان در میان یک قاعده، و تضمین اینکه تحلیلگر لغوی فراتر از انتهای بافر را نخواهد خواند است. مقدار پیشفرض -1` است (در این حالت re2swift فرض میکند که دیدهبان صفر است، که رایجترین حالت میباشد). فقط اعداد دهدهی شناسایی میشوند.
- re2c:state:abort
- اگر روی یک مقدار عدد صحیح مثبت تنظیم شود، شاخهٔ پیشفرض (default case) در توزیع وضعیت تولیدشده، اجرای برنامه را لغو (abort) میکند و یک شاخهٔ صریح -1 شامل انتقال به ابتدای بلوک خواهد بود.
- re2c:state:nextlabel
- کنترل میکند که آیا بعد از عبارت switch مربوط به YYGETSTATE یک برچسب yyNext قرار گیرد یا خیر (مقدار پیشفرض صفر است که با عدم وجود برچسب مطابقت دارد). به عنوان جایگزین میتوان از re2c:label:start برای تولید یک برچسب شروع مشخص، یا از یک بلوک صریح getstate برای تولید ساختار switch مربوط به YYGETSTATE به صورت جداگانه از بلوک تحلیلگر استفاده کرد.
- re2c:unsafe, re2c:flags:unsafe
- مشابه گزینهٔ --no-unsafe است، اما میتواند بر پایهٔ هر بلوک پیکربندی شود. اگر روی صفر تنظیم شود، از تولید پوششهای (wrappers) unsafe در اطراف YYPEEK جلوگیری میکند. پیشفرض مقداری غیرصفر است (پوششها تولید میشوند). این پیکربندی مختص Rust است.
- re2c:YYBACKUP, re2c:define:YYBACKUP
- عنصر پایهٔ عمومی API به نام YYBACKUP را تعریف میکند.
- re2c:YYBACKUPCTX, re2c:define:YYBACKUPCTX
- عنصر پایهٔ عمومی API به نام YYBACKUPCTX را تعریف میکند.
- re2c:YYCONDTYPE, re2c:define:YYCONDTYPE
- عنصر پایهٔ API به نام YYCONDTYPE را تعریف میکند.
- re2c:YYCTYPE, re2c:define:YYCTYPE
- عنصر پایهٔ API به نام YYCTYPE را تعریف میکند.
- re2c:YYCTXMARKER, re2c:define:YYCTXMARKER
- عنصر پایهٔ API به نام YYCTXMARKER را تعریف میکند.
- re2c:YYCURSOR, re2c:define:YYCURSOR
- عنصر پایهٔ API به نام YYCURSOR را تعریف میکند.
- re2c:YYDEBUG, re2c:define:YYDEBUG
- عنصر پایهٔ API به نام YYDEBUG را تعریف میکند.
- re2c:YYFILL, re2c:define:YYFILL
- عنصر پایهٔ API به نام YYFILL را تعریف میکند.
- re2c:YYFILL@len, re2c:define:YYFILL@len
- علامت ویژه (sigil) مورد استفاده برای جایگزینی آرگومان در تعریف YYFILL را مشخص میکند. مقدار پیشفرض @@ است. پیکربندی عمومیتر re2c:api:sigil را لغو میکند.
- re2c:YYFILL:naked, re2c:define:YYFILL:naked
- پیکربندی عمومیتر re2c:api:style را برای YYFILL لغو میکند. مقدار صفر متناظر با سبک آزاد API (free-form) است.
- re2c:YYFN
- عنصر پایهٔ API به نام YYFN را تعریف میکند.
- re2c:YYINPUT
- عنصر پایهٔ API به نام YYINPUT را تعریف میکند.
- re2c:YYGETCOND, re2c:define:YYGETCONDITION
- عنصر پایهٔ API به نام YYGETCOND را تعریف میکند.
- re2c:YYGETCOND:naked, re2c:define:YYGETCONDITION:naked
- پیکربندی عمومیتر re2c:api:style را برای YYGETCOND لغو میکند. مقدار صفر متناظر با سبک آزاد API است.
- re2c:YYGETSTATE, re2c:define:YYGETSTATE
- عنصر پایهٔ API به نام YYGETSTATE را تعریف میکند.
- re2c:YYGETSTATE:naked, re2c:define:YYGETSTATE:naked
- پیکربندی عمومیتر re2c:api:style را برای YYGETSTATE لغو میکند. مقدار صفر متناظر با سبک آزاد API است.
- re2c:YYGETACCEPT, re2c:define:YYGETACCEPT
- عنصر پایهٔ API به نام YYGETACCEPT را تعریف میکند.
- re2c:YYLESSTHAN, re2c:define:YYLESSTHAN
- عنصر پایهٔ عمومی API به نام YYLESSTHAN را تعریف میکند.
- re2c:YYLIMIT, re2c:define:YYLIMIT
- عنصر پایهٔ API به نام YYLIMIT را تعریف میکند.
- re2c:YYMARKER, re2c:define:YYMARKER
- عنصر پایهٔ API به نام YYMARKER را تعریف میکند.
- re2c:YYMTAGN, re2c:define:YYMTAGN
- عنصر پایهٔ عمومی API به نام YYMTAGN را تعریف میکند.
- re2c:YYMTAGP, re2c:define:YYMTAGP
- عنصر پایهٔ عمومی API به نام YYMTAGP را تعریف میکند.
- re2c:YYPEEK, re2c:define:YYPEEK
- عنصر پایهٔ عمومی API به نام YYPEEK را تعریف میکند.
- re2c:YYRESTORE, re2c:define:YYRESTORE
- عنصر پایهٔ عمومی API به نام YYRESTORE را تعریف میکند.
- re2c:YYRESTORECTX, re2c:define:YYRESTORECTX
- عنصر پایهٔ عمومی API به نام YYRESTORECTX را تعریف میکند.
- re2c:YYRESTORETAG, re2c:define:YYRESTORETAG
- عنصر پایهٔ عمومی API به نام YYRESTORETAG را تعریف میکند.
- re2c:YYSETCOND, re2c:define:YYSETCONDITION
- عنصر پایهٔ API به نام YYSETCOND را تعریف میکند.
- re2c:YYSETCOND@cond, re2c:define:YYSETCONDITION@cond
- علامت ویژه (sigil) مورد استفاده برای جایگزینی آرگومان در تعریف YYSETCOND را مشخص میکند. مقدار پیشفرض @@ است. پیکربندی عمومیتر re2c:api:sigil را لغو میکند.
- re2c:YYSETCOND:naked, re2c:define:YYSETCONDITION:naked
- پیکربندی عمومیتر re2c:api:style را برای YYSETCOND لغو میکند. مقدار صفر متناظر با سبک آزاد API است.
- re2c:YYSETSTATE, re2c:define:YYSETSTATE
- عنصر پایهٔ API به نام YYSETSTATE را تعریف میکند.
- re2c:YYSETSTATE@state, re2c:define:YYSETSTATE@state
- علامت ویژه (sigil) مورد استفاده برای جایگزینی آرگومان در تعریف YYSETSTATE را مشخص میکند. مقدار پیشفرض @@ است. پیکربندی عمومیتر re2c:api:sigil را لغو میکند.
- re2c:YYSETSTATE:naked, re2c:define:YYSETSTATE:naked
- پیکربندی عمومیتر re2c:api:style را برای YYSETSTATE لغو میکند. مقدار صفر متناظر با سبک آزاد API است.
- re2c:YYSETACCEPT, re2c:define:YYSETACCEPT
- عنصر پایهٔ API به نام YYSETACCEPT را تعریف میکند.
- re2c:YYSKIP, re2c:define:YYSKIP
- عنصر پایهٔ عمومی API به نام YYSKIP را تعریف میکند.
- re2c:YYSHIFT, re2c:define:YYSHIFT
- عنصر پایهٔ عمومی API به نام YYSHIFT را تعریف میکند.
- re2c:YYCOPYMTAG, re2c:define:YYCOPYMTAG
- عنصر پایهٔ عمومی API به نام YYCOPYMTAG را تعریف میکند.
- re2c:YYCOPYSTAG, re2c:define:YYCOPYSTAG
- عنصر پایهٔ عمومی API به نام YYCOPYSTAG را تعریف میکند.
- re2c:YYSHIFTMTAG, re2c:define:YYSHIFTMTAG
- عنصر پایهٔ عمومی API به نام YYSHIFTMTAG را تعریف میکند.
- re2c:YYSHIFTSTAG, re2c:define:YYSHIFTSTAG
- عنصر پایهٔ عمومی API به نام YYSHIFTSTAG را تعریف میکند.
- re2c:YYSTAGN, re2c:define:YYSTAGN
- عنصر پایهٔ عمومی API به نام YYSTAGN را تعریف میکند.
- re2c:YYSTAGP, re2c:define:YYSTAGP
- عنصر پایهٔ عمومی API به نام YYSTAGP را تعریف میکند.
- re2c:yyaccept, re2c:variable:yyaccept
- عنصر پایهٔ API به نام yyaccept را تعریف میکند.
- re2c:yybm, re2c:variable:yybm
- عنصر پایهٔ API به نام yybm را تعریف میکند.
- re2c:yybm:hex, re2c:variable:yybm:hex
- اگر روی مقداری غیرصفر تنظیم شود، نقشههای بیتی (bitmaps) برای گزینهٔ --bit-vectors در قالب شانزدهشانزدهی (hexadecimal) تولید میشوند. پیشفرض صفر است (نقشههای بیتی در قالب دهدهی هستند).
- re2c:yych, re2c:variable:yych
- عنصر پایهٔ API به نام yych را تعریف میکند.
- re2c:yych:emit, re2c:variable:yych:emit
- اگر روی صفر تنظیم شود، تعریف yych تولید نمیشود. مقدار پیشفرض غیرصفر است.
- re2c:yych:conversion, re2c:variable:yych:conversion
- اگر روی مقداری غیرصفر تنظیم شود، re2swift به طور خودکار هر بار که yych خوانده میشود، یک تبدیل به YYCTYPE تولید میکند. پیشفرض صفر است (بدون تبدیل).
- re2c:yych:literals, re2c:variable:yych:literals
- قالب لیترالهایی که yych با آنها مطابقت داده میشود را مشخص میکند. مقادیر ممکن عبارتند از: char (لیترالهای نویسهای درون گیومه تکی؛ موارد غیرقابل چاپ از توالیهای گریز که با بکاسلش شروع میشوند استفاده میکنند)، hex (اعداد صحیح شانزدهشانزدهی) و char_or_hex (ترکیبی از هر دو؛ لیترالهای نویسهای برای نویسههای قابل چاپ و اعداد صحیح شانزدهشانزدهی برای بقیه).
- re2c:yyctable, re2c:variable:yyctable
- عنصر پایهٔ API به نام yyctable را تعریف میکند..TP re2c:yynmatch, re2c:variable:yynmatch عنصر پایه API با نام yynmatch را تعریف میکند.
- re2c:yypmatch, re2c:variable:yypmatch
- عنصر پایه API با نام yypmatch را تعریف میکند.
- re2c:yytarget, re2c:variable:yytarget
- عنصر پایه API با نام yytarget را تعریف میکند.
- re2c:yystable, re2c:variable:yystable
- منسوخ شده است.
- re2c:yystate, re2c:variable:yystate
- عنصر پایه API با نام yystate را تعریف میکند.
- re2c:yyfill, re2c:variable:yyfill
- عنصر پایه API با نام yyfill را تعریف میکند.
- re2c:yyfill:check
- اگر روی صفر تنظیم شود، از تولید بررسی پیش از YYFILL برای تعداد نویسههای ورودی (تعریف YYLESSTHAN در API عمومی و مقایسه مبتنی بر YYLIMIT در API اشارهگر C) جلوگیری میکند. مقدار پیشفرض غیرصفر است (بررسی را تولید میکند).
- re2c:yyfill:enable
- اگر روی صفر تنظیم شود، تولید YYFILL را (همراه با بررسی آن) لغو میکند. این حالت باید زمانی استفاده شود که کل ورودی در یک بخش یکپارچه از حافظه جا میشود (نیازی به بافر کردن نیست) و بررسیهای پایان ورودی به بررسیهای YYFILL متکی نیستند (برای نمونه، اگر از نویسه نشانگر پایان / sentinel استفاده شود). برای اطمینان از اینکه تحلیلگر لغوی تولیدشده نمیتواند فراتر از پایان ورودی را بخواند، از هشدارها (گزینه -W) و پیکربندی re2c:sentinel استفاده کنید. مقدار پیشفرض غیرصفر است (YYFILL فعال است).
- re2c:yyfill:parameter
- اگر روی صفر تنظیم شود، از تولید پارامتر ارسالی به YYFILL جلوگیری میکند. این پارامتر حداقل تعداد نویسههایی است که باید فراهم شوند. پیشفرض غیرصفر است (پارامتر تولید میشود). این پیکربندی را میتوان با re2c:YYFILL:naked یا re2c:api:style بازنویسی کرد.
- re2c:yyfn:sep
- جداکننده مورد استفاده در عناصر YYFN را مشخص میکند (پیشفرض نقطه-ویرگول است).
- re2c:yyfn:throw
- استثناهای پرتابشده توسط تابع YYFN را مشخص میکند (پیشفرض خالی است، که به معنای پرتاب نشدن هیچ استثنایی است).
عبارتهای منظم (Regular expressions)
ابزار re2swift از نحو زیر برای عبارتهای منظم استفاده میکند:
- "foo"
- ثابت رشتهای حساس به بزرگی و کوچکی حروف.
- 'foo'
- ثابت رشتهای غیرحساس به بزرگی و کوچکی حروف.
- [a-xyz], [^a-xyz]
- کلاس نویسه (احتمالاً نفیشده).
- .
- هر نویسهای به جز خط جدید.
- R \ S
- تفاضل کلاسهای نویسه R و S.
- R*
- صفر یا چند بار تکرار R.
- R+
- یک یا چند بار تکرار R.
- R?
- R اختیاری.
- R{n}
- تکرار R دقیقاً n بار.
- R{n,}
- تکرار R حداقل n بار.
- R{n,m}
- تکرار R از n تا m بار.
- (R)
- تنها همان R؛ پرانتزها برای تغییر اولویت استفاده میشوند. در صورت فعال بودن استخراج تطابق فرعی، (R) بسته به گزینه --invert-captures یک گروه ضبطکننده یا غیرضبطکننده است.
- (!R)
- در صورت فعال بودن استخراج تطابق فرعی، (!R) بسته به گزینه --invert-captures یک گروه غیرضبطکننده یا ضبطکننده است.
- R S
- الحاق: R و به دنبال آن S.
- R | S
- جایگزین: R یا S.
- R / S
- پیشنگری: R و به دنبال آن S، اما S مصرف نمیشود.
- name
- عبارت منظم تعریفشده با نام name (یا رشته دقیق "name" در حالت سازگاری با Flex).
- {name}
- عبارت منظم تعریفشده با نام name در حالت سازگاری با Flex.
- @stag
- یک s-tag: آخرین موقعیت ورودی که در آن @stag تطابق مییابد را در متغیری به نام stag ذخیره میکند.
- #mtag
- یک m-tag: تمامی موقعیتهای ورودی که در آنها #mtag تطابق مییابد را در متغیری به نام mtag ذخیره میکند.
- $
- پایان ورودی.
کلاسهای نویسه و ثابتهای رشتهای میتوانند شامل توالیهای گریز زیر باشند: \a، \b، \f، \n، \r، \t، \v، \\، گریزهای هشتهشتی \ooo و گریزهای شانزدهشانزدهی \xhh، \uhhhh و \Uhhhhhhhh..SS "کنشها (Actions)"
در اینجا فهرستی از کنشهای از پیش تعریفشده که توسط re2swift پشتیبانی میشوند آمده است:
- !entry code
- کنش ورودی (Entry action) یک بلوک از code تعریفشده توسط کاربر را به وضعیت آغازین ماشین حالت متناهی کنونی مقید میکند. اگر از start conditions استفاده شود، کنش ورودی را میتوان بهصورت جداگانه برای هر شرط تنظیم کرد. این کنش ممکن است برای انجام مقداردهی اولیه، مانند ذخیره موقعیت آغازین یک لکسم (lexeme) استفاده شود.
- !pre_rule code
- کنش پیشقاعده (Pre-rule action) یک بلوک از code تعریفشده توسط کاربر را به ابتدای کنشهای معنایی تمام قواعد در بلوک کنونی (یا در صورت استفاده از start conditions، به شرط کنونی) اضافه میکند. این کنش میتواند برای فاکتورگیری بخش مشترک تمام کنشهای معنایی (مانند ذخیره موقعیت پایانی یک لکسم) استفاده شود.
- !post_rule code
- کنش پسقاعده (Post-rule action) یک بلوک از code تعریفشده توسط کاربر را به انتهای کنشهای معنایی تمام قواعد در بلوک کنونی (یا در صورت استفاده از start conditions، به شرط کنونی) ضمیمه میکند. این کنش میتواند برای درج عبارتهای تله (trap) که در برابر جریان کنترل ناخواسته محافظت میکنند، استفاده شود.
رهنمودها (Directives)
در اینجا فهرست کاملی از رهنمودهای پشتیبانیشده توسط re2swift آمده است:
- !use:name ;
- یک رهنمود درونبلوکیِ use که یک بلوک قواعد از پیش تعریفشده با name مشخص را در بلوک کنونی ادغام میکند. تعاریف نامگذاریشده، پیکربندیها و قواعد بلوک مرجع به موارد فعلی اضافه میشوند. تداخلات میان قواعد و پیکربندیهای همپوشان به روش معمول حل میشوند: نخستین قاعده اولویت دارد، و آخرین پیکربندی بر موارد پیشین بازنویسی میشود. یک استثنا قواعد خاص *، $ و <!> هستند که برای آنها تعریف محلی بلوک همواره اولویت دارد. یک رهنمود use را میتوان در هر کجای یک بلوک قرار داد، و استفاده از چندین رهنمود use مجاز است.
- !include file ;
- این رهنمود همانند بلوک include است: محتویات file را عیناً به جای رهنمود درج میکند.
رابط برنامه (Program interface)
کد تولیدشده به کمک عناصر پایه (primitives) که در مجموع به عنوان API شناخته میشوند، با برنامه بیرونی ارتباط برقرار میکند. اینکه کدام پایهها باید برای یک برنامه خاص تعریف شوند به عوامل متعددی بستگی دارد، از جمله پیچیدگی عبارات باقاعده، نحوه نمایش ورودی، بافرینگ و استفاده از قابلیتهای گوناگون. تمام پایههای ضروری باید توسط کاربر در قالب ماکروها، توابع، متغیرها یا هر شکل مناسب دیگری که کد تولیدشده را از نظر نحوی و معنایی صحیح سازد، تعریف شوند. re2swift تعاریف را بررسی نمیکند (و نمیتواند بررسی کند)، بنابراین اگر موردی جا افتاده باشد یا به نادرستی تعریف شده باشد، ممکن است برنامه تولیدشده با خطاهای زمان کامپایل یا زمان اجرا مواجه شود. این راهنما مثالهایی از تعاریف API در رایجترین موارد ارائه میدهد.
ابزار re2swift دارای سه شیوه (flavor) از API است که مجموعه اصلی پایههای استفادهشده توسط یک برنامه را مشخص میکنند:
- Simple API
- این حالت پیشفرض برای backend سوئیفت (Swift) است. این API از پایههای زیر تشکیل شده است: YYINPUT (که باید بهصورت توالی از واحدهای کد، مانند یک رشته تعریف شود) و YYCURSOR، YYMARKER، YYCTXMARKER، YYLIMIT (که باید بهصورت اندیسهایی در YYINPUT تعریف شوند).
- Record API
- رابط Record API در مواردی مفید است که وضعیت تحلیلگر لغوی باید در یک شیء ذخیره شود. این حالت با گزینه --api record یا پیکربندی re2c:api = record فعال میشود. این API شامل متغیر yyrecord است (این نام را میتوان با re2c:YYRECORD بازنویسی کرد) که باید بهصورت یک ساختار (struct) یا کلاس (class) با فیلدهای yyinput، yycursor، yymarker، yyctxmarker و yylimit تعریف شود (تنها فیلدهایی که توسط کد تولیدشده استفاده میشوند باید تعریف شوند، و نامهای آنها نیز قابل پیکربندی است).
- Generic API
- این منعطفترین API است. این حالت با گزینه --api generic یا پیکربندی re2c:api = generic فعال میشود. این API شامل پایههای زیر برای عملیات عمومی است: YYPEEK، YYSKIP، YYBACKUP، YYBACKUPCTX، YYSTAGP، YYSTAGN، YYMTAGP، YYMTAGN، YYRESTORE، YYRESTORECTX، YYRESTORETAG، YYSHIFT، YYSHIFTSTAG، YYSHIFTMTAG، YYLESSTHAN، YYEND. برای مثال، اگر ورودی یک آرایه از بایتها با پایان صفر بهصورت buffer: [UInt8] باشد، متغیرهای cursor، limit، marker و ctxmarker از نوع Int نمایانگر موقعیتهای ورودی باشند، و -1 نمایانگر موقعیتهای نامعتبر باشد، یک Generic API میتواند بهصورت زیر تعریف شود:
/*!re2c
re2c:YYPEEK = "buffer[cursor]";
re2c:YYSKIP = "cursor++";
re2c:YYBACKUP = "marker = cursor";
re2c:YYRESTORE = "cursor = marker";
re2c:YYBACKUPCTX = "ctxmarker = cursor";
re2c:YYRESTORECTX = "cursor = ctxmarker";
re2c:YYRESTORETAG = "cursor = ${tag}";
re2c:YYLESSTHAN = "limit - cursor < @@{len}";
re2c:YYEND = "limit == cursor";
re2c:YYSTAGP = "@@{tag} = cursor";
re2c:YYSTAGN = "@@{tag} = -1";
re2c:YYSHIFT = "cursor += @@{shift}";
re2c:YYSHIFTSTAG = "@@{tag} += @@{shift}";
*/
در اینجا فهرست کاملی از پایههای API که ممکن است توسط کد تولیدشده جهت برقراری ارتباط با برنامه بیرونی استفاده شوند آمده است:
- YYCTYPE
- نوع نویسههای ورودی (واحدهای کد). برای کدگذاریهای ASCII، EBCDIC و UTF-8 باید یک عدد صحیح بدون علامت ۱ بایتی باشد. برای UTF-16 یا UCS-2 باید عدد صحیح بدون علامت ۲ بایتی باشد. برای UTF-32 باید عدد صحیح بدون علامت ۴ بایتی باشد.
- YYCURSOR
- یک مقدار چپ (l-value) که موقعیت کنونی ورودی را ذخیره میکند (یک اشارهگر یا آفست صحیح در YYINPUT). در ابتدا YYCURSOR باید به نخستین نویسه ورودی اشاره کند، و سپس توسط کد تولیدشده به جلو برده میشود. هنگامی که قاعدهای مطابقت یابد، موقعیت YYCURSOR نویسه پس از آخرین نویسه تطبیقیافته خواهد بود.
- YYLIMIT
- یک مقدار راست (r-value) که موقعیت پایان ورودی را ذخیره میکند (یک اشارهگر یا آفست صحیح در YYINPUT). در ابتدا YYLIMIT باید به موقعیت پس از آخرین نویسه ورودی موجود اشاره کند. این مقدار توسط کد تولیدشده تغییر داده نمیشود. تحلیلگر لغوی YYCURSOR را با YYLIMIT مقایسه میکند تا مشخص شود آیا نویسههای ورودی کافی باقی ماندهاند یا خیر.
- YYMARKER
- یک مقدار چپ (l-value) که موقعیت آخرین قاعده تطبیقیافته را ذخیره میکند (یک اشارهگر یا آفست صحیح در YYINPUT). این مقدار برای بازیابی موقعیت YYCURSOR در صورتی که تطبیق طولانیتر با شکست مواجه شود و تحلیلگر لغوی نیاز به بازگشت به عقب (rollback) داشته باشد، استفاده میشود. مقداردهی اولیه نیاز نیست.
- YYCTXMARKER
- یک مقدار چپ (l-value) که موقعیت زمینه دنبالهدار (trailing context) را ذخیره میکند (یک اشارهگر یا آفست صحیح در YYINPUT). به مقداردهی اولیه نیازی نیست. YYCTXMARKER تنها در صورتی نیاز است که از عملگر پیشنگری (lookahead) / استفاده شود.
- YYFILL
- یک پایه Generic API با یک متغیر len. پایه YYFILL باید دستکم len نویسه ورودی بیشتر تأمین کند یا با شکست مواجه شود. اگر از re2c:eof استفاده شود، len همیشه 1 است و YYFILL باید همواره به تابع فراخواننده بازگردد؛ مقدار بازگشتی صفر نشاندهنده موفقیت است. اگر از re2c:eof استفاده نشود، مقدار بازگشتی YYFILL نادیده گرفته میشود و نباید هنگام شکست بازگشتی داشته باشد. بیشینه مقدار len برابر با YYMAXFILL است.
- YYFN
- پایهای که پیشنمونه (prototype) تابع را در مدل کد --recursive-functions تعریف میکند. مقدار آن باید آرایهای از یک یا چند رشته باشد، که در آن هر رشته شامل دو یا سه مؤلفه است که توسط رشته مشخصشده در پیکربندی re2c:fn:sep (معمولاً یک نقطهویرگول) از هم جدا شدهاند. نخستین عنصر آرایه نام تابع و نوع بازگشتی را مشخص میکند (برای یک تابع void خالی است). عناصر بعدی آرگومانهای تابع را تعریف میکنند: اول، عبارت مربوط به آرگومان مورد استفاده در بدنه تابع (معمولاً تنها یک نام)؛ دوم، نوع آرگومان؛ سوم، یک پارامتر صوری اختیاری (پیشفرض آن مؤلفه اول است - معمولاً هر دو آرگومان و پارامتر شناسه یکسانی هستند).
- YYINPUT
- یک مقدار راست (r-value) که توالی نویسههای ورودی کنونی (رشته، بافر، و غیره) را ذخیره میکند.
- YYMAXFILL
- یک ثابت صحیح برابر با بیشینه مقدار آرگومان ورودی به YYFILL. این مقدار را میتوان با یک بلوک max تولید کرد.
- YYLESSTHAN
- یک پایه Generic API با یک متغیر len. این پایه باید بهصورت یک مقدار راست (r-value) از نوع بولی تعریف شود که اگر و تنها اگر کمتر از len نویسه ورودی باقی مانده باشد برابر با true است.
- YYEND
- یک پایه Generic API بدون متغیر. این پایه باید بهصورت یک مقدار راست (r-value) از نوع بولی تعریف شود که اگر و تنها اگر به پایان منطقی ورودی (بدون احتساب هرگونه پدینگ یا نمادهای نگهبان/sentinel) رسیده باشیم برابر با true است. YYEND برای پیادهسازی نماد $ در عبارات باقاعده استفاده میشود. این پایه با YYLESSTHAN تفاوت دارد، که برای اطمینان از اینکه تحلیلگر لغوی فراتر از انتهای بافر را نخواند استفاده میشود. YYPEEK یک دستور اولیه (primitive) عمومی API بدون متغیر. باید بهصورت یک r-value از نوع YYCTYPE تعریف شود که برابر با نویسه در موقعیت فعلی ورودی است.
- YYSKIP
- یک دستور اولیه عمومی API که باید موقعیت فعلی ورودی را به اندازهٔ یک واحد کد جلو ببرد.
- YYBACKUP
- یک دستور اولیه عمومی API که باید موقعیت فعلی ورودی را ذخیره کند (تا بعداً با YYRESTORE بازیابی شود).
- YYRESTORE
- یک دستور اولیه عمومی API که باید موقعیت فعلی ورودی را به مقدار ذخیرهشده توسط YYBACKUP بازگرداند.
- YYBACKUPCTX
- یک دستور اولیه عمومی API که باید موقعیت فعلی ورودی را بهعنوان موقعیت بافت انتهایی (trailing context) ذخیره کند (تا بعداً با YYRESTORECTX بازیابی شود).
- YYRESTORECTX
- یک دستور اولیه عمومی API که باید موقعیت بافت انتهایی ذخیرهشده با YYBACKUPCTX را بازگرداند.
- YYRESTORETAG
- یک دستور اولیه عمومی API با یک متغیر tag که باید موقعیت بافت انتهایی را به مقدار tag بازگرداند.
- YYSTAGP
- یک دستور اولیه عمومی API با یک متغیر tag، که در آن tag میتواند یک اشارهگر یا آفست در YYINPUT باشد (برای جزئیات به بخش استخراج تطابقهای فرعی مراجعه کنید). YYSTAGP باید tag را روی موقعیت فعلی ورودی تنظیم کند.
- YYSTAGN
- یک دستور اولیه عمومی API با یک متغیر tag، که در آن tag میتواند یک اشارهگر یا آفست در YYINPUT باشد (برای جزئیات به بخش استخراج تطابقهای فرعی مراجعه کنید). YYSTAGN باید tag را روی مقداری تنظیم کند که نشاندهندهٔ موقعیت ناموجود در ورودی است.
- YYMTAGP
- یک دستور اولیه عمومی API با یک متغیر tag. YYMTAGP باید موقعیت فعلی را به تاریخچهٔ تطابقهای فرعی tag الحاق کند (برای جزئیات به بخش استخراج تطابقهای فرعی مراجعه کنید).
- YYMTAGN
- یک دستور اولیه عمومی API با یک متغیر tag. YYMTAGN باید مقداری را که نشاندهندهٔ موقعیت ناموجود در ورودی است، به تاریخچهٔ تطابقهای فرعی tag الحاق کند (برای جزئیات به بخش استخراج تطابقهای فرعی مراجعه کنید).
- YYSHIFT
- یک دستور اولیه عمومی API با یک متغیر shift که باید موقعیت فعلی ورودی را به اندازهٔ shift نویسه جابهجا کند (مقدار جابهجایی ممکن است منفی باشد).
- YYCOPYSTAG
- یک دستور اولیه عمومی API با دو متغیر lhs و rhs که باید متغیر s-tag سمت راست (rhs) را در متغیر s-tag سمت چپ (lhs) رونوشت کند. برای بیشتر زبانها این دستور اولیه دارای یک تعریف پیشفرض است که lhs را برابر rhs قرار میدهد.
- YYCOPYMTAG
- یک دستور اولیه عمومی API با دو متغیر lhs و rhs که باید متغیر m-tag سمت راست (rhs) را در متغیر m-tag سمت چپ (lhs) رونوشت کند. برای بیشتر زبانها این دستور اولیه دارای یک تعریف پیشفرض است که lhs را برابر rhs قرار میدهد.
- YYSHIFTSTAG
- یک دستور اولیه عمومی API با دو متغیر tag و shift که باید tag را به اندازهٔ shift واحد کد جابهجا کند (مقدار جابهجایی ممکن است منفی باشد).
- YYSHIFTMTAG
- یک دستور اولیه عمومی API با دو متغیر tag و shift که باید آخرین مقدار در تاریخچهٔ tag را به اندازهٔ shift واحد کد جابهجا کند (مقدار جابهجایی ممکن است منفی باشد).
- YYMAXNMATCH
- یک ثابت عدد صحیح برابر با حداکثر تعداد گروههای تسخیرکنندهٔ POSIX در یک قاعده. این مقدار با یک بلوک maxnmatch تولید میشود.
- YYCONDTYPE
- نوع نوع شمارشی شرط (condition enum). میتواند با بلوک conditions یا گزینهٔ --header تولید شود.
- YYGETACCEPT
- یک دستور اولیه با یک متغیر var که انتخابگر عددیِ قاعدهٔ پذیرفتهشده را ذخیره میکند. برای بیشتر زبانها این دستور اولیه تعریفی پیشفرض دارد که از var میخواند.
- YYSETACCEPT
- یک دستور اولیه با دو متغیر: var (یک l-value که انتخابگر عددی قاعدهٔ پذیرفتهشده را ذخیره میکند) و val (مقدار انتخابگر). برای بیشتر زبانها این دستور اولیه تعریفی پیشفرض دارد که var را برابر val قرار میدهد.
- YYGETCOND
- یک r-value از نوع YYCONDTYPE که برابر با شناسهٔ شرط فعلی است.
- YYSETCOND
- یک دستور اولیه با یک متغیر cond که باید شناسهٔ شرط فعلی را روی cond تنظیم کند.
- YYGETSTATE
- یک r-value از نوع عدد صحیح که برابر با وضعیت فعلی تحلیلگر لغوی (lexer) است. مقدار آن باید به -1 مقداردهی اولیه شود.
- YYSETSTATE
- یک دستور اولیه با یک متغیر state که باید وضعیت فعلی تحلیلگر لغوی را روی state تنظیم کند.
- YYDEBUG
- این دستور اولیه فقط با گزینهٔ -d یا --debug-output تولید میشود. هدف آن افزودن امکان ثبت وقایع (لاگین) به کد تولیدشده است (تعریف معمول YYDEBUG یک دستور چاپ است). عبارتهای YYDEBUG در هر وضعیت تولید میشوند و دو متغیر دارند: state (یا نمایهٔ وضعیت DFA یا -1) و symbol (نماد ورودی فعلی).
- yyaccept
- یک l-value از نوع عددیِ صحیح بدون علامت که شمارهٔ آخرین قاعدهٔ منطبقشده را ذخیره میکند. تعریف توسط کاربر تنها با گزینهٔ --storable-state ضروری است.
- yybm
- جدولی حاوی نقشههای بیتی فشردهشده برای حداکثر ۸ گذار (که با گزینهٔ --bitmaps استفاده میشود). این جدول شامل ۲۵۶ عنصر است و با واحدهای کد ۱ بایتی اندیسگذاری میشود. هر عنصر ۸ بیتی، مقادیر بولی را برای حداکثر ۸ گذار ترکیب میکند. بیت k-اُم از عنصر n-اُم درست (true) است اگر و تنها اگر n-اُمین واحد کد در محدودهٔ k-اُمین گذار باشد. ایدهٔ این نقشهٔ بیتی جایگزینی چندین شاخهٔ if یا حالتهای switch با یک بررسیِ تکبیتی در جدول است.
- yych
- یک l-value از نوع YYCTYPE که نویسهٔ ورودی فعلی را ذخیره میکند. تعریف توسط کاربر تنها با گزینهٔ -f --storable-state لازم است.
- yyctable
- جدول پرش تولیدشده برای توزیع شرط اولیه (که با ترکیب گزینههای --conditions و --computed-gotos فعال میشود).
- yyfill
- یک l-value که نتیجهٔ فراخوانی YYFILL را ذخیره میکند (این ممکن است برای زبانهای تابعی محض لازم باشد، جایی که YYFILL تابعی مونادیک با مقدار بازگشتی پیچیده است).
- yynmatch
- یک l-value از نوع عددیِ صحیح بدون علامت که تعداد گروههای تسخیرکنندهٔ POSIX را در قاعدهٔ منطبقشده ذخیره میکند. تنها با گزینهٔ -P --posix-captures استفاده میشود.
- yypmatch
- آرایهای از l-valueها که برای نگهداری مقادیر برچسب مربوط به پرانتزهای تسخیرکننده در قاعدهٔ منطبقشده استفاده میشوند. طول آرایه باید حداقل yynmatch * 2 باشد (معمولاً YYMAXNMATCH * 2 انتخاب مناسبی است). تنها با گزینهٔ -P --posix-captures استفاده میشود.
- yystable
- منسوخشده.
- yystate
- یک l-value که با گزینهٔ --loop-switch برای ذخیرهٔ وضعیت فعلی DFA استفاده میشود.
- yytarget
- جدول پرشی
که حاوی
مقصدهای
پرش
(نشانیهای
برچسب) برای
تمامی
گذارها از
یک وضعیت
است. این
جدول مختص
هر وضعیت
بهصورت
محلی است.
تولید
جدولهای
yytarget با
گزینهٔ --computed-gotos
فعال
میشود.
برخی از گزینهها دارای پیکربندیهای متناظر هستند، برخی دیگر سراسری بوده و پس از شروع خواندن فایل ورودی توسط re2c قابل تغییر نیستند. گزینههای اشکالزدایی (دیباگ) عموماً نیازمند ساخت re2c در پیکربندی دیباگ هستند. گزینههای داخلی برای آزمایش الگوریتمهای بهکاررفته در re2c مفیدند.
- -? --help -h
- نمایش پیام راهنما.
- --api <simple | record | generic>
- مشخص کردن API مورد استفاده توسط کد تولیدشده جهت برقراری ارتباط با کد تعریفشده توسط کاربر. گزینهٔ simple باید در موارد ساده استفاده شود؛ هنگامی که نیازی به پر کردن مجدد بافر و ذخیرهسازی وضعیت تحلیلگر لغوی (lexer) نیست. گزینهٔ record باید زمانی استفاده شود که وضعیت لکسر باید در یک رکورد (struct، class و غیره) ذخیره شود. گزینهٔ generic باید در سناریوهای پیچیدهای به کار رود که دو API دیگر انعطافپذیری کافی ندارند.
- --bit-vectors -b
- بهینهسازی پرشهای شرطی با استفاده از ماسکهای بیتی. این گزینه مستلزم --nested-ifs است.
- --captures, --leftmost-captures
- فعالسازی استخراج زیرتطابق با گروههای گیرندهٔ حریصانهترینِ چپترین (leftmost greedy). نتیجه در آرایهٔ yybmatch با ظرفیت 2 * YYMAXNMATCH جمعآوری میشود، و yynmatch به تعداد گروهها برای قاعدهٔ منطبقشده تنظیم میگردد.
- --captvars, --leftmost-captvars
- فعالسازی استخراج زیرتطابق با گروههای گیرندهٔ حریصانهترینِ چپترین (leftmost greedy). نتیجه در متغیرهای yytl<k> و yytr<k> برای گروه گیرندهٔ k-اُم گردآوری میشود.
- --case-insensitive
- در نظر گرفتن رشتههای دارای تکنقلقول و دونقلقول به صورت غیرحساس به بزرگی و کوچکی حروف.
- --case-inverted
- معکوس کردن معنای رشتههای دارای تکنقلقول و دونقلقول: رشتههای با تکنقلقول حساس به حروف و رشتههای با دونقلقول غیرحساس به بزرگی و کوچکی حروف در نظر گرفته میشوند.
- --case-ranges
- ادغام caseهای متوالی در دستور switch به یک بازه در قالب low ... high. این نحو، یک افزونهٔ زبان C/C++ است که توسط کامپایلرهایی مانند GCC، Clang و Tcc پشتیبانی میشود. مزیت اصلی نسبت به استفاده از caseهای تکی، تولید کد کوچکتر و سرعت بالاتر در زمان تولید کد است، اگرچه برای برخی کامپایلرها مانند Tcc منجر به کاهش اندازهٔ فایل باینری نیز میشود.
- --computed-gotos -g
- بهینهسازی پرشهای شرطی با استفاده از افزونهٔ غیراستاندارد "computed goto" (که باید توسط کامپایلر پشتیبانی شود). برنامه re2swift جدولهای پرش را تنها در موارد پیچیده با تعداد زیادی شاخهٔ شرطی تولید میکند. آستانهٔ پیچیدگی با پیکربندی cgoto:threshold قابل تنظیم است. آفستهای نسبی را میتوان با پیکربندی cgoto:relative فعال کرد. این گزینه مستلزم --bit-vectors است.
- --computed-gotos-relative
- مشابه --computed-gotos، اما در عوض آفستهای نسبی را برای جدولهای پرش تولید میکند (که باید توسط کامپایلر پشتیبانی شود). این گزینه مستلزم --computed-gotos است.
- --conditions --start-conditions -c
- فعالسازی پشتیبانی از "شرایط" مشابه Flex: چندین لکسر مرتبط درون یک بلوک. این یک جایگزین برای مشخصکردن دستی بلوکهای مختلف re2swift متصلشده با goto یا فراخوانیهای توابع است.
- --depfile FILE
- نوشتن اطلاعات وابستگی در FILE در قالب یک قاعدهٔ Makefile به شکل <output-file> : <input-file> [include-file ...]. این قابلیت امکان ردیابی وابستگیهای ساخت را در حضور بلوکها/دستورهای include فراهم میکند، به طوری که بهروزرسانی فایلهای include باعث تولید مجدد فایل خروجی میشود. این گزینه به گزینهٔ --output وابسته است.
- --ebcdic --ecb -e
- تولید لکسری که ورودی را با کدگذاری EBCDIC میخواند. برنامه re2swift فرض میکند که محدودهٔ نویسهها 0 -- 0xFF و اندازهٔ نویسه ۱ بایت است.
- --empty-class <match-empty | match-none | error>
- تعیین نحوهٔ رفتار re2swift با کلاسهای نویسهٔ خالی. با مقدار match-empty (پیشفرض) کلاس خالی با ورودی خالی تطابق مییابد (که غیرمنطقی است، اما برای سازگاری با نسخههای قبلی حفظ شده است). با match-none تطبیق کلاس خالی همیشه با شکست مواجه میشود. با error کلاس خالی یک خطای کامپایل ایجاد میکند.
- --encoding-policy <fail | substitute | ignore>
- تعیین نحوهٔ رفتار re2swift با جانشینهای یونیکد (surrogates). با مقدار fail برنامه re2swift با مواجهه با یک جانشین، با خطا متوقف میشود. با substitute برنامه re2swift بدون هشدار جانشینها را با کدپوینت خطای 0xFFFD جایگزین میکند. با ignore (پیشفرض) re2swift با جانشینها مانند کدپوینتهای معمولی رفتار میکند. استاندارد یونیکد بیان میکند که جانشینهای مستقل نامعتبر هستند، اما کتابخانهها و برنامههای دنیای واقعی رفتارهای متفاوتی دارند.
- --flex-syntax -F
- پشتیبانی جزئی از نحو Flex: در این حالت تعاریف نامگذاریشده نیازی به علامت مساوی و سمیکولنِ پایانی ندارند، و در صورت استفاده باید درون آکوالاد قرار گیرند. نامهای بدون آکوالاد به عنوان رشتههای محصور در دونقلقول در نظر گرفته میشوند.
- --goto-label
- استفاده از مدل کد "goto/label": کدگذاری DFA در قالب بلوکهای کد برچسبدار متصلشده با انتقالهای goto میان بلوکها. این گزینه تنها برای زبانهایی پشتیبانی میشود که دستور goto دارند.
- --header --type-header -t HEADER
- تولید یک فایل HEADER. محتوای فایل را میتوان با استفاده از بلوکهای ویژهٔ header:on و header:off تعیین کرد. در صورت استفاده از شرایط، شمارشگر مربوط به شرایط (enum) به طور خودکار به انتهای فایل سرایندِ تولیدشده اضافه خواهد شد (مگر اینکه یک بلوک صریح conditions وجود داشته باشد).
- -I PATH
- افزودن PATH به فهرست مسیرهایی که هنگام جستجوی فایلهای include استفاده میشوند. این گزینه در ترکیب با بلوک یا دستور include مفید است. برنامه re2swift فایل FILE را در دایرکتوری فایل والد و در مسیرهای include تعیینشده با گزینهٔ -I جستجو میکند.
- --input <default | custom>
- نام مستعار منسوخشده برای --api. گزینهٔ default متناظر با simple است (در واقع برای اکثر بکاندها پیشفرض است، اما نه برای همه). گزینهٔ custom متناظر با generic است.
- --input-encoding <ascii | utf8>
- مشخص کردن نحوهٔ تجزیهٔ عبارتهای منظم توسط re2swift. با مقدار ascii (پیشفرض) re2swift ورودی را به عنوان کدگذاری ASCII در نظر میگیرد: هر توالی از واحدهای کد، توالی از نویسههای مستقل ۱ بایتی است. با utf8 برنامه re2swift ورودی را به عنوان کدگذاری UTF-8 در نظر گرفته و نویسههای چندبایتی را تشخیص میدهد.
- --invert-captures
- معکوس کردن معنای گروههای گیرنده و غیرگیرنده. به صورت پیشفرض (...) گیرنده و (! ...) غیرگیرنده است. با این گزینه (! ...) گیرنده و (...) غیرگیرنده خواهد بود.
- --lang <none | c | d | go | haskell | java | js | ocaml | python | rust | swift | v | zig>
- مشخص کردن زبان مقصد. زبانهای پشتیبانیشده عبارتند از C، D، Go، Haskell، Java، JS، OCaml، Python، Rust، Swift، V، Zig (زبانهای بیشتر را میتوان از طریق فایلهای نحوِ تعریفشده توسط کاربر اضافه کرد؛ گزینهٔ --syntax را ببینید). گزینهٔ none پیکربندیهای پیشفرض نحو را غیرفعال میکند تا زبان مقصد تعریفنشده باقی بماند.
- --location-format <gnu | msvc>
- تعیین قالب مکان در پیامها. با gnu مکانها به صورت 'filename:line:column: ...' چاپ میشوند. با msvc مکانها به صورت 'filename(line,column) ...' چاپ میشوند. پیشفرض gnu است.
- --loop-switch
- استفاده از مدل کد "loop/switch": کدگذاری DFA در قالب یک حلقه روی دستور switch، که در آن وضعیتهای جداگانه همان caseهای switch هستند. وضعیت در یک متغیر yystate ذخیره میشود. انتقالهای بین وضعیتها، yystate را به برچسب case وضعیت مقصد بهروزرسانی کرده و اجرا را به ابتدای حلقه ادامه میدهند.
- --nested-ifs -s
- استفاده از دستورات تودرتوی if به جای دستورات switch در پرشهای شرطی. این کار معمولاً در کامپایلرهای غیربهینهساز منجر به کدی کارآمدتر میشود.
- --no-debug-info -i
- عدم درج دستورهای خط (line directives) در خروجی. این قابلیت زمانی که کد تولیدشده در یک سیستم کنترل نسخه نگهداری میشود مفید است (برای جلوگیری از diffهای حجیم خودکار روی تغییرات کوچک).
- --no-generation-date
- جلوگیری از چاپ تاریخ در فایل تولیدشده.
- --no-version
- جلوگیری از چاپ نسخه در فایل تولیدشده.
- --no-unsafe
- عدم تولید بستهبند unsafe روی YYPEEK (این گزینه مختص Rust است). به دلایل کارایی، YYPEEK باید از بررسی محدوده (bounds-checking) خودداری کند، زیرا لکسر پیش از این بررسیهای پایان ورودی را به روشی کارآمدتر انجام میدهد. کاربر ممکن است تعریفی امن از YYPEEK ارائه دهد، یا تعریفی که تنها در بیلد انتشار (release) ناامن باشد، که در این حالت گزینهٔ --no-unsafe به جلوگیری از هشدارهای مربوط به بلوکهای unsafe زاید کمک میکند.
- --output -o OUTPUT
- مشخص کردن فایل OUTPUT.
- --posix-captures, -P
- فعالسازی استخراج زیرتطابق با گروههای گیرندهٔ سبک POSIX. نتیجه در آرایهٔ yybmatch با ظرفیت 2 * YYMAXNMATCH گردآوری شده، و yynmatch به تعداد گروهها برای قاعدهٔ منطبق تنظیم میشود.
- --posix-captvars
- فعالسازی استخراج زیرتطابق با گروههای گیرندهٔ سبک POSIX. نتیجه در متغیرهای yytl<k> و yytr<k> برای گروه گیرندهٔ k-اُم گردآوری میشود.
- --recursive-functions
- استفاده از مدل کد مبتنی بر توابع همبازگشتی (co-recursive)، که در آن هر وضعیت DFA یک تابع مجزاست که میتواند سایر توابع وضعیت یا خودش را فراخوانی کند.
- --reusable -r
- از نگارش 2.2 منسوخ شده است (بلوکهای با قابلیت استفاده مجدد اکنون به طور پیشفرض مجاز هستند).
- --skeleton -S
- نادیده گرفتن کد واسط تعریفشده توسط کاربر و تولید یک برنامهٔ خودکفای "اسکلت" (skeleton). علاوه بر این، تولید فایلهای ورودی با رشتههای مشتقشده از گرامر منظم و نتایج تطابق فشردهشده که برای اعتبارسنجی رفتار برنامهٔ "اسکلت" روی تمام ورودیها به کار میروند. این گزینه برای یافتن باگها در بهینهسازیها و تولید کد مفید است. این گزینه تنها برای C پشتیبانی میشود..TP --storable-state -f تولید یک تحلیلگر لغوی که میتواند وضعیت درونی خود را ذخیره کند. این ویژگی در تحلیلگرهای لغوی مبتنی بر مدل push مفید است که در هنگام عدم وجود ورودی کافی توسط برنامهای بیرونی متوقف شده و پس از در دسترس قرار گرفتن ورودی بیشتر، اجرای آنها از سر گرفته میشود. در این حالت، کاربران باید علاوه بر این، دستورات اولیهٔ YYGETSTATE و YYSETSTATE را تعریف کنند، و متغیرهای yych، yyaccept و state باید بخشی از وضعیت ذخیرهشدهٔ تحلیلگر لغوی باشند.
- --syntax FILE
- بارگذاری پیکربندیها از FILE مشخصشده و اعمال آنها روی فایل نحو پیشفرض. توجه داشته باشید که FILE میتواند تنها چند پیکربندی را تعریف کند (اگر برای اصلاح فایل نحو پیشفرض استفاده شود)، یا یک بکاند زبان کاملاً جدید را تعریف نماید (در حالت دوم توصیه میشود از گزینهٔ --lang none استفاده شود).
- فعالسازی استخراج تطابقهای فرعی با برچسبها.
- --ucs2 --wide-chars -w
- تولید یک تحلیلگر لغوی که ورودی با کدگذاری UCS2 را میخواند. re2swift فرض میکند که دامنهٔ نویسهها 0 -- 0xFFFF و اندازهٔ نویسه ۲ بایت است. این گزینه مستلزم --nested-ifs است.
- --utf8 --utf-8 -8
- تولید یک تحلیلگر لغوی که ورودی با کدگذاری UTF-8 را میخواند. re2swift فرض میکند که دامنهٔ نویسهها 0 -- 0x10FFFF و اندازهٔ نویسه ۱ بایت است.
- --utf16 --utf-16 -x
- تولید یک تحلیلگر لغوی که ورودی با کدگذاری UTF16 را میخواند. re2swift فرض میکند که دامنهٔ نویسهها 0 -- 0x10FFFF و اندازهٔ نویسه ۲ بایت است. این گزینه مستلزم --nested-ifs است.
- --utf32 --unicode -u
- تولید یک تحلیلگر لغوی که ورودی با کدگذاری UTF32 را میخواند. re2swift فرض میکند که دامنهٔ نویسهها 0 -- 0x10FFFF و اندازهٔ نویسه ۴ بایت است. این گزینه مستلزم --nested-ifs است.
- --verbose
- چاپ یک پیام کوتاه در صورت موفقیت.
- --vernum -V
- نمایش اطلاعات نسخه در قالب MMmmpp (نسخه اصلی، فرعی، وصله).
- --version -v
- نمایش اطلاعات نسخه.
- --single-pass -1
- منسوخ شده است. کاری انجام نمیدهد (حالت تکگذره در حال حاضر پیشفرض است).
- --debug-output -d
- درج فراخوانیهای YYDEBUG در کد تولیدشده. این قابلیت برای ردگیری اجرای تحلیلگر لغوی مفید است.
- --dump-adfa
- گزینهٔ اشکالزدایی: خروجی دادن DFA پس از تونلزنی (به قالب .dot).
- --dump-cfg
- گزینهٔ اشکالزدایی: خروجی دادن گراف جریان کنترل متغیرهای برچسب (به قالب .dot).
- --dump-closure-stats
- گزینهٔ اشکالزدایی: خروجی دادن آمار تعداد وضعیتها در بستار.
- --dump-dfa-det
- گزینهٔ اشکالزدایی: خروجی دادن DFA بلافاصله پس از قطعیسازی (به قالب .dot).
- --dump-dfa-min
- گزینهٔ اشکالزدایی: خروجی دادن DFA پس از کمینهسازی (به قالب .dot).
- --dump-dfa-tagopt
- گزینهٔ اشکالزدایی: خروجی دادن DFA پس از بهینهسازیهای برچسب (به قالب .dot).
- --dump-dfa-tree
- گزینهٔ اشکالزدایی: خروجی دادن DFA در حال ساخت با وضعیتهایی که به صورت درختهای تاریخچهٔ برچسب نمایش داده شدهاند (به قالب .dot).
- --dump-dfa-raw
- گزینهٔ اشکالزدایی: خروجی دادن DFA در حال ساخت با مجموعهوضعیتهای بسطیافته (به قالب .dot).
- --dump-interf
- گزینهٔ اشکالزدایی: خروجی دادن جدول تداخل حاصل از تحلیل زندهبودنِ متغیرهای برچسب.
- --dump-nfa
- گزینهٔ اشکالزدایی: خروجی دادن NFA (به قالب .dot).
- --emit-dot -D
- به جای خروجی معمول، گراف تحلیلگر لغوی را در قالب .dot تولید میکند. خروجی را میتوان با کمک Graphviz به یک تصویر تبدیل کرد (مانند dot -Tpng -odfa.png dfa.dot).
- --dfa-minimization <moore | table>
- گزینهٔ داخلی: الگوریتم کمینهسازی DFA مورد استفاده توسط re2swift. گزینهٔ moore الگوریتم مور است (که پیشفرض است). گزینهٔ table الگوریتم "پر کردن جدول" است. هر دو الگوریتم باید صرفنظر از نامگذاری مجدد وضعیتها، DFA یکسانی تولید کنند؛ پر کردن جدول سادهتر و بسیار کندتر است و به عنوان پیادهسازی مرجع عمل میکند.
- --eager-skip
- گزینهٔ داخلی: وادار کردن تحلیلگر لغوی تولیدشده به پیش بردن مشتاقانهٔ موقعیت ورودی -- بلافاصله پس از خواندن نماد ورودی. این گزینه رفتار پیشفرض را تغییر میدهد که در آن موقعیت ورودی به صورت تنبلانه -- پس از گذار به وضعیت بعدی -- به جلو برده میشود.
- --no-lookahead
- گزینهٔ داخلی، منسوخ شده. در گذشته برای فعالسازی الگوریتم TDFA(0) به کار میرفت. بر خلاف TDFA(1)، الگوریتم TDFA(0) از پیشنگری یکنمادی استفاده نمیکند. این الگوریتم عملیات ثباتها را به جای گذارهای خروجی، روی گذارهای ورودی اعمال میکند. ارزیابیها نشان دادند که الگوریتم TDFA(0) کارایی کمتری نسبت به TDFA(1) دارد.
- --no-optimize-tags
- گزینهٔ داخلی: غیرفعال کردن بهینهسازی متغیرهای برچسب (مفید برای اشکالزدایی).
- --posix-closure <gor1 | gtop>
- گزینهٔ داخلی: تعیین الگوریتم کوتاهترین مسیر مورد استفاده برای ساخت بستار اپسیلون با معناشناسی رفع ابهام POSIX؛ مقدار gor1 (پیشفرض) نشاندهندهٔ الگوریتم گلدبرگ-رادزیک (Goldberg-Radzik) است و gtop نشاندهندهٔ الگوریتم "ترتیب توپولوژیکی سراسری" میباشد.
- --posix-prectable <complex | naive>
- گزینهٔ داخلی: تعیین الگوریتم مورد استفاده برای محاسبهٔ جدول اولویت POSIX. الگوریتم complex جدول اولویت را در یک پیمایش از درخت تاریخچهٔ برچسب محاسبه میکند و دارای پیچیدگی درجه دو بر حسب تعداد وضعیتهای TNFA است؛ این حالت پیشفرض است. الگوریتم naive در بدترین حالت دارای پیچیدگی درجه سه بر حسب تعداد وضعیتهای TNFA است، اما بسیار سادهتر از complex بوده و ممکن است در موارد غیرپاتولوژیک کمی سریعتر باشد.
- --stadfa
- گزینهٔ داخلی، منسوخ شده. در گذشته برای فعالسازی الگوریتم staDFA به کار میرفت؛ تفاوت این الگوریتم با TDFA در این است که عملیات ثباتها به جای قرارگیری روی گذارها، در وضعیتها قرار میگیرند. ارزیابیها نشان دادند که الگوریتم staDFA کارایی کمتری نسبت به TDFA دارد.
- --fixed-tags <none | toplevel | all>
- گزینهٔ
داخلی: مشخص
میکند که
آیا
بهینهسازی
برچسبهای
ثابت باید
روی همهٔ
برچسبها
اعمال شود
(all)، روی
هیچکدام
(none)، یا فقط
روی
آنهایی که
در الحاق
سطح بالا
قرار دارند
(toplevel). مقدار
پیشفرض all
است.
برچسبهای
"ثابت"
برچسبهایی
هستند که در
فاصلهای
معین نسبت
به برچسبی
دیگر (موسوم
به "پایه")
قرار دارند.
در چنین
حالتی تنها
برچسب پایه
نیاز به
ردیابی
دارد، و
مقدار
برچسب ثابت
را میتوان
از حاصل جمع
مقدار
برچسب پایه
با یک آفست
ایستا
محاسبه کرد.
برای
برچسبهایی
که درون
ساختار
انتخاب (alternative)
یا تکرار
قرار
دارند،
بررسی این
موضوع نیز
لازم است که
آیا برچسب
پایه
مقداری
معادل عدم
تطابق دارد
یا خیر (در
این صورت
برچسب ثابت
نیز باید
بدون در نظر
گرفتن
آفست، روی
عدم تطابق
تنظیم شود).
برای
برچسبهای
واقع در
الحاق سطح
بالا، این
بررسی لازم
نیست زیرا
آنها
همیشه
مطابقت
دارند.
هشدارها را میتوان بهصورت جداگانه فعال یا غیرفعال کرد و یا به یک خطا تبدیل نمود.
- -W
- فعالسازی تمام هشدارها.
- -Werror
- تبدیل هشدارها به خطا. توجه داشته باشید که این گزینه بهتنهایی هیچ هشداری را فعال نمیکند؛ بلکه تنها بر هشدارهایی تأثیر میگذارد که تاکنون فعال شدهاند یا بعداً فعال خواهند شد.
- -W<warning>
- فعالسازی warning.
- -Wno-<warning>
- غیرفعالسازی warning.
- -Werror-<warning>
- فعالسازی warning و در نظر گرفتن آن بهعنوان خطا (شامل -W<warning> نیز میشود).
- -Wno-error-<warning>
- این warning خاص را بهعنوان خطا در نظر نگیر. این گزینه خودِ هشدار را غیرفعال نمیکند.
- -Wcondition-order
- هشدار در صورتی که برنامه تولیدشده فرضیات ضمنی درباره شمارهگذاری شرطها داشته باشد. کاربر باید از گزینه --header یا بلوک conditions برای تولید نگاشت نامهای شرط به اعداد استفاده کند و سپس نامهای شرط تولیدشده بهصورت خودکار را به کار ببرد.
- -Wempty-character-class
- هشدار در صورتی که یک عبارت باقاعده شامل یک کلاس نویسه خالی باشد. تلاش برای تطبیق یک کلاس نویسه خالی بیمعنی است: این تطبیق همیشه باید با شکست مواجه شود. با این حال، به دلایل سازگاری با گذشته، re2swift کلاسهای نویسه خالی را مجاز دانسته و با آنها مانند رشتههای خالی رفتار میکند. از گزینه --empty-class برای تغییر رفتار پیشفرض استفاده کنید.
- -Wmatch-empty-string
- هشدار در صورتی که یک قاعده تهیپذیر باشد (با یک رشته خالی تطبیق یابد). اگر تحلیلگر لغوی در یک حلقه اجرا شود و تطبیق خالی ناخواسته باشد، تحلیلگر لغوی ممکن است بهطور غیرمنتظره در یک حلقه بیپایان گیر کند.
- -Wswapped-range
- هشدار در صورتی که کران پایین یک بازه بزرگتر از کران بالای آن باشد. رفتار پیشفرض جابهجا کردن بیسروصدای کرانهای بازه است.
- -Wundefined-control-flow
- هشدار در صورتی که برخی از رشتههای ورودی باعث ایجاد جریان کنترل تعریفنشده در تحلیلگر لغوی شوند (الگوهای ناقص گزارش میشوند). این یک اشتباه رایج و خطرناک است. این مشکل را میتوان بهسادگی با افزودن قاعده پیشفرض * رفع کرد که کمترین اولویت را دارد، با هر واحد کد تطبیق مییابد، و همیشه یک واحد کد منفرد را مصرف میکند.
- -Wunreachable-rules
- هشدار درباره قواعدی که توسط قواعد دیگر تحتالشعاع قرار گرفتهاند و هرگز تطبیق نخواهند یافت.
- -Wdeprecated-eof_rule
- هشدار درباره قواعد مستقل پایان ورودی $ که با تغییرات آینده شکسته خواهند شد و نیاز به اصلاح دارند. در حال حاضر این قواعد هنگام تداخل با قواعد دیگر تقدم دارند، اما پس از معرفی نماد تعمیمیافته پایان ورودی $، ترتیب تقدم تغییر خواهد کرد و این قواعد توسط سایر قواعد تحتالشعاع قرار خواهند گرفت.
- -Wuseless-escape
- هشدار در صورتی که نمادی گریزدهی شده باشد که نباید میشد. بهطور پیشفرض، re2swift چنین گریزهایی را نادیده میگیرد، اما این مورد ممکن است نشاندهنده یک غلط تایپی یا خطایی در توالی گریز باشد.
- -Wnondeterministic-tags
- هشدار در صورتی که یک برچسب دارای درجه n-ام از غیرقطعی بودن باشد، جایی که n بزرگتر از ۱ است.
- -Wsentinel-in-midrule
- هشدار در صورتی که نماد دیدهبان در میانه یک قاعده رخ دهد --- این امر ممکن است باعث خواندن فراتر از انتهای بافر، خرابیها یا خراب شدن حافظه در تحلیلگر لغوی تولیدشده شود. این هشدار تنها در صورتی کاربرد دارد که از روش دیدهبان برای بررسی پایان ورودی استفاده شود. در صورت استفاده از پیکربندی re2c:sentinel، این هشدار به خطا تبدیل میشود.
- -Wundefined-syntax-config
- هشدار در صورتی که فایل نحو مشخصشده با گزینه --syntax فاقد تعاریف برخی از پیکربندیها باشد. این به نگهداری فایلهای نحو تعریفشده توسط کاربر کمک میکند: اگر نسخه جدیدی پیکربندیهایی را اضافه کند، فایل نحو قدیمی هشداری صادر خواهد کرد و کاربر مطلع خواهد شد. اگر برخی از پیکربندیها استفاده نمیشوند و نیازی به تعریف ندارند، باید بهصراحت روی <undefined> تنظیم شوند.
فایلهای نحو (Syntax files)
پشتیبانی از زبانهای مختلف در re2c بر پایه ایده فایلهای نحو استوار است. یک فایل نحو، فایلی پیکربندی است که نحو زبان مقصد را تعریف میکند -- نه کل زبان، بلکه بخش کوچکی از آن را که توسط کد تولیدشده استفاده میشود. فایلهای نحو re2c را بسیار انعطافپذیر میکنند، اما نباید بهعنوان جایگزینی برای پیکربندیهای re2c: استفاده شوند: هدف آنها تعریف نحو زبان مقصد است، نه سفارشیسازی یک تحلیلگر لغوی خاص. همه زبانهای پشتیبانیشده دارای فایلهای نحو پیشفرضی هستند که بخشی از توزیع به شمار میروند (زیردایرکتوری include/syntax را ببینید)؛ آنها همچنین در باینری re2swift تعبیه شدهاند. کاربران میتوانند یک فایل نحو سفارشی ارائه دهند که چند پیکربندی را برای یکی از زبانهای پشتیبانیشده بازنویسی میکند، یا میتوانند تمام پیکربندیها را از نو تعریف کنند (در این صورت باید از گزینه --lang none استفاده شود). فایلهای نحو حاوی پیکربندیهایی از چهار نوع مختلف هستند: فهرست ویژگیها، پیکربندیهای زبان، پیکربندیهای درجا و الگوهای کد.
فهرست ویژگیها (Feature lists)
- supported_apis
- فهرستی از APIهای پشتیبانیشده با عناصر ممکنِ simple، record و generic.
- supported_api_styles
- فهرستی از شیوههای API پشتیبانیشده با عناصر ممکنِ functions و free-form.
- supported_code_models
- فهرستی از مدلهای کد پشتیبانیشده با عناصر ممکنِ goto-label، loop-switch و recursive-functions.
- supported_targets
- فهرستی از مقصدهای تولید کد پشتیبانیشده با عناصر ممکنِ code، dot و skeleton.
- supported_features
- فهرستی از ویژگیهای پشتیبانیشده با عناصر ممکنِ nested-ifs، bitmaps، computed-gotos، case-ranges، monadic، unsafe، tags، captures و captvars.
پیکربندیهای زبان (Language configurations)
- semicolons
- غیرصفر اگر زبان پس از دستورها از نقطهویرگول استفاده کند.
- backtick_quoted_strings
- غیرصفر اگر زبان دارای رشتههای احاطهشده با بکتیک باشد.
- single_quoted_strings
- غیرصفر اگر زبان دارای رشتههای احاطهشده با تکنقلقول باشد.
- indentation_sensitive
- غیرصفر اگر زبان به تورفتگی حساس باشد.
- wrap_blocks_in_braces
- غیرصفر اگر دستورهای مرکب باید در آکوالد قرار گیرند.
پیکربندیهای درجا (Inplace configurations)
الگوهای کد (Code templates)
code-template ::
name '=' code-exprs ';'
| CODE_TEMPLATE ';'
| '<undefined>' ';'
code-exprs ::
<EMPTY>
| code-exprs code-expr
code-expr ::
STRING
| VARIABLE
| optional
| list
optional ::
'(' CONDITIONAL '?' code-exprs ')'
| '(' CONDITIONAL '?' code-exprs ':' code-exprs ')'
list ::
'[' VARIABLE ':' code-exprs ']'
| '[' VARIABLE '{' NUMBER '}' ':' code-exprs ']'
| '[' VARIABLE '{' NUMBER ',' NUMBER '}' ':' code-exprs ']'
یک الگوی کد توالیای از رشتههای لفظی، متغیرها، عناصر اختیاری و فهرستها، یا ارجاعی به الگوی کد دیگر، یا مقدار ویژه <undefined> است. متغیرها جایبانهایی هستند که در مرحله تولید کد جایگزین میشوند. متغیرهای فهرستی خاص هستند: هنگام بسط الگوهای فهرستی، re2swift عبارتهای سمت راست دونقطه را چند بار تکرار میکند، و هر بار رخدادهای متغیر فهرست را با مقداری مخصوص به این تکرار جایگزین میسازد. فهرستها دارای کرانهای اختیاری هستند (مقادیر منفی از انتها شمرده میشوند، مثلاً -1 به معنای آخرین عنصر است). نامهای شرطی با یک نقطه شروع میشوند. هم شرطها و هم متغیرها میتوانند محلی (مخصوص به الگوی کد دادهشده) یا سراسری (مجاز در تمام الگوهای کد) باشند. هنگامی که re2swift فایل نحو را میخواند، بررسی میکند که هر الگوی کد تنها از متغیرها و شرطهایی استفاده کند که در آن مجاز هستند.
بهعنوان مثال، الگوی کد زیر ساختار if-then-else را برای یک زبان C-مانند تعریف میکند:
code:if_then_else =
[branch{0}: topindent "if " cond " {" nl
indent [stmt: stmt] dedent]
[branch{1:-1}: topindent "} else" (.cond ? " if " cond) " {" nl
indent [stmt: stmt] dedent]
topindent "}" nl;
در اینجا branch یک متغیر فهرستی است: branch{0} به شاخه نخست بسط مییابد (که ویژه است، زیرا بخش else ندارد)، و branch{1:-1} به تمام شاخههای باقیمانده (در صورت وجود) بسط پیدا میکند. stmt نیز یک متغیر فهرستی است: [stmt: stmt] یک فهرست تودرتو است که به فهرستی از دستورها در بدنه شاخه جاری بسط مییابد. topindent، indent، dedent و nl متغیرهای سراسری هستند، و .cond یک شرط محلی است (معنای آنها در ادامه شرح داده شده است). این الگوی کد میتواند کد زیر را تولید کند:
if x {
// do something
} else if y {
// do something else
} else {
// don't do anything
}
در ادامه فهرستی از تمام الگوهای کد پشتیبانیشده توسط re2swift به همراه متغیرها و شرطهای محلی آنها آورده شده است. توجه داشته باشید که یک تعریف خاص میتواند از متغیرها و شرطهای محلی استفاده کند، اما ملزم به انجام آن نیست. هر الگوی کد بدون استفاده باید روی <undefined> تنظیم شود.
- code:var_global
- مشابه code:var_local، با این تفاوت که در سطح بالا (top-level) استفاده میشود.
- code:const_local
- تعریف یک مقدار ثابت محلی. متغیرهای پشتیبانیشده: type (نوع مقدار ثابت)، name (نام آن) و init (مقدار اولیه).
- code:const_global
- مشابه code:const_local، با این تفاوت که در سطح بالا (top-level) استفاده میشود.
- code:array_local
- تعریف یک آرایه محلی (جدول). متغیرهای پشتیبانیشده: type (نوع عناصر آرایه)، name (نام آرایه)، size (اندازه آن)، row (یک متغیر فهرستی که به خودی خود کدی تولید نمیکند، اما عبارت فهرست را به تعداد سطرهای جدول بسط میدهد) و elem (یک متغیر فهرستی که به تمام عناصر جدول در سطر فعلی بسط مییابد -- برای قرارگیری بهصورت تودرتو در فهرست row در نظر گرفته شده است). شرط پشتیبانیشده: .const (اگر آرایه تغییرناپذیر باشد، true).
- code:array_global
- مشابه code:array_local، با این تفاوت که در سطح بالا (top-level) استفاده میشود.
- code:array_elem
- ارجاع به عنصری از یک آرایه (جدول). متغیرهای پشتیبانیشده: array (نام آرایه) و index (اندیس عنصر).
- code:enum
- تعریف یک نوع شمارشی (ممکن است با استفاده از یک ساختار زبانی ویژه برای شمارشها، یا صرفاً بهصورت چند مقدار ثابت مستقل تعریف شود). متغیرهای پشتیبانیشده عبارتند از type (نوع شمارشی تعریفشده توسط کاربر یا نوع ثابتها)، elem (متغیر فهرستی که به نام هر عضو بسط مییابد) و init (مقداردهی اولیه برای هر عضو). شرطها: .init (اگر مقداردهی اولیه وجود داشته باشد، true).
- code:enum_elem
- عنصر نوع شمارشی (عضوی از یک نوع شمارشی تعریفشده توسط کاربر یا نام یک مقدار ثابت، بسته به نحوه تعریف code:enum). متغیرهای پشتیبانیشده عبارتند از name (نام عنصر) و type (نوع آن).
- code:assign
- دستور انتساب. متغیرهای پشتیبانیشده عبارتند از lhs (سمت چپ) و rhs (سمت راست).
- code:type_int
- نوع عدد صحیح علامتدار (signed integer).
- code:type_uint
- نوع عدد صحیح بدون علامت (unsigned integer).
- code:type_yybm
- نوع عناصر موجود در جدول yybm.
- code:type_yytarget
- نوع عناصر موجود در جدول yytarget.
- code:type_yyctable
- نوع عناصر موجود در جدول yyctable.
- code:cmp_eq
- عملگر "equals" (مساوی).
- code:cmp_ne
- عملگر "not equals" (نامساوی).
- code:cmp_lt
- عملگر "less than" (کوچکتر).
- code:cmp_gt
- عملگر "greater than" (بزرگتر).
- code:cmp_le
- عملگر "less or equal" (کوچکتر یا مساوی).
- code:cmp_ge
- عملگر "greater or equal" (بزرگتر یا مساوی).
- code:if_then_else
- دستور If-then-else با یک یا چند شاخه. متغیرهای پشتیبانیشده: branch (یک متغیر فهرستی که به خودی خود کدی تولید نمیکند، اما عبارت فهرست را به تعداد شاخهها بسط میدهد)، cond (شرط شاخه فعلی) و stmt (یک متغیر فهرستی که به تمام دستورهای موجود در شاخه فعلی بسط مییابد). شرطها: .cond (اگر شاخه فعلی دارای شرط باشد، true)، .many (اگر بیش از یک شاخه وجود داشته باشد، true).
- code:if_then_else_oneline
- حالت خاصی از code:if_then_else برای زمانی که تمام شاخهها دستورهای تکخطی دارند. اگر این مورد <undefined> باشد، به جای آن از code:if_then_else استفاده میشود.
- code:switch
- یک دستور switch با یک یا چند حالت (case). متغیرهای پشتیبانیشده: expr (عبارتی که روی آن switch انجام میشود) و case (یک متغیر فهرستی که به تمام گروههای case همراه با بلوکهای کدشان بسط مییابد).
- code:switch_cases
- گروهی از حالتهای switch که به یک بلوک کد واحد نگاشت میشوند. متغیرهای پشتیبانیشده عبارتند از case (یک متغیر فهرستی که به تمام حالتهای این گروه بسط مییابد) و stmt (یک متغیر فهرستی که به تمام دستورهای موجود در بلوک کد بسط مییابد).
- code:switch_cases_oneline
- حالت خاصی از code:switch_cases برای زمانی که بلوک کد شامل یک دستور تکخطی است. اگر این مورد <undefined> باشد، به جای آن از code:switch_cases استفاده میشود.
- code:switch_case_range
- یک حالت switch تکی که محدودهای از مقادیر را پوشش میدهد (که ممکن است شامل یک مقدار واحد باشد). متغیر پشتیبانیشده: val (یک متغیر فهرستی که به تمام مقادیر موجود در محدوده بسط مییابد). شرطهای پشتیبانیشده: .many (اگر بیش از یک مقدار در محدوده وجود داشته باشد، true) و .char_literals (اگر این یک switch روی نویسههای حرفی باشد، true -- برخی زبانها نحو ویژهای برای این حالت ارائه میدهند).
- code:switch_case_default
- حالت پیشفرض (default) در switch.
- code:loop
- حلقهای که برای همیشه اجرا میشود (مگر اینکه از داخل بدنه حلقه متوقف شود). متغیرهای پشتیبانیشده: label (برچسب حلقه)، stmt (یک متغیر فهرستی که به تمام دستورهای بدنه حلقه بسط مییابد).
- code:continue
- دستور continue. متغیرهای پشتیبانیشده: label (برچسبی که اجرا باید از آن ادامه یابد).
- code:goto
- دستور goto. متغیرهای پشتیبانیشده: label (برچسب مقصد پرش).
- code:cgoto
- دستور goto محاسباتی (computed goto). متغیرهای پشتیبانیشده: array (جدول حاوی اطلاعات goto محاسباتی)، index (اندیس عنصر در جدول) و base (برچسب پایه، تنها در صورتی استفاده میشود که .cgoto.relative برابر با true باشد).
- code:cgoto:data
- عبارت مقداردهی اولیه برای یک عنصر واحد در جدول goto محاسباتی. متغیرهای پشتیبانیشده: label (برچسبی که برای مقداردهی اولیه عنصر فعلی استفاده میشود)، type (نوع پایهای عناصر موجود در جدول) و base (برچسب پایه - تنها در صورتی استفاده میشود که .cgoto.relative برابر با true باشد).
- code:fndecl
- اعلان تابع. متغیرهای پشتیبانیشده: name (نام تابع)، type (نوع بازگشتی)، throw (استثناهای پرتابشده توسط این تابع، به پیکربندی re2c:yyfn:throw نگاشت میشود)، arg (یک متغیر فهرستی که به خودی خود کدی تولید نمیکند، اما عبارت فهرست را به تعداد آرگومانهای تابع بسط میدهد)، argname (نام آرگومان فعلی)، argtype (نوع آرگومان فعلی). شرط: .type (اگر این یک تابع غیر void باشد، true).
- code:fndef
- مشابه code:fndecl، اما برای تعریف تابع استفاده میشود، بنابراین یک متغیر فهرستی اضافی stmt دارد که به تمام دستورهای درون بدنه تابع بسط مییابد.
- code:fncall
- دستور فراخوانی تابع. متغیرهای پشتیبانیشده: name (نام تابع)، retval (مقدار سمت چپ یا l-value که مقدار بازگشتی در آن ذخیره میشود، در صورت وجود) و arg (یک متغیر فهرستی که به تمام آرگومانهای تابع بسط مییابد). شرطها: .args (اگر تابع دارای آرگومان باشد، true) و .retval (اگر مقدار بازگشتی نیاز به ذخیره شدن داشته باشد، true).
- code:tailcall
- دستور فراخوانی دمدمی (tail call). متغیرهای پشتیبانیشده: name (نام تابع) و arg (یک متغیر فهرستی که به تمام آرگومانهای تابع بسط مییابد). شرطها: .args (اگر تابع دارای آرگومان باشد، true) و .retval (اگر این یک تابع غیر void باشد، true).
- code:recursive_functions
- بدنه برنامه با مدل کد --recursive-functions. متغیرهای پشتیبانیشده: fn (یک متغیر فهرستی که به خودی خود هیچ کدی تولید نمیکند، اما عبارت فهرست را به تعداد توابع بسط میدهد)، fndecl (اعلان تابع فعلی) و fndef (تعریف تابع فعلی).
- code:fingerprint
- اثر انگشت (fingerprint) در بالای فایل خروجی تولیدشده. متغیرهای پشتیبانیشده: ver (نسخه re2swift که برای تولید این فایل استفاده شده است) و date (تاریخ تولید).
- code:line_info
- قالب دستورالعملهای خط (اگر روی <undefined> تنظیم شده باشد، هیچ دستورالعملی تولید نمیشود). متغیرهای پشتیبانیشده: line (شماره خط) و file (نام فایل).
- code:abort
- دستوری که اجرای برنامه را بیدرنگ متوقف (abort) میکند.
- code:yydebug
- دستور YYDEBUG، احتمالاً اختصاصیشده برای APIهای مختلف. متغیرهای پشتیبانیشده: YYDEBUG، yyrecord، yych (به پیکربندیهای re2c: متناظر نگاشت میشوند)، state (شماره وضعیت DFA)..TP code:yypeek دستور YYPEEK، که ممکن است برای APIهای مختلف سفارشیسازی شده باشد. متغیرهای پشتیبانیشده: YYPEEK، YYCTYPE، YYINPUT، YYCURSOR، yyrecord، yych (نگاشت به پیکربندیهای متناظر re2c:). شرطها: .cast (درست اگر re2c:yych:conversion روی مقداری غیر از صفر تنظیم شده باشد).
- code:yyskip
- دستور YYSKIP، که ممکن است برای APIهای مختلف سفارشیسازی شده باشد. متغیرهای پشتیبانیشده: YYSKIP، YYCURSOR، yyrecord (نگاشت به پیکربندیهای متناظر re2c:).
- code:yybackup
- دستور YYBACKUP، که ممکن است برای APIهای مختلف سفارشیسازی شده باشد. متغیرهای پشتیبانیشده: YYBACKUP، YYCURSOR، YYMARKER، yyrecord (نگاشت به پیکربندیهای متناظر re2c:).
- code:yybackupctx
- دستور YYBACKUPCTX، که ممکن است برای APIهای مختلف سفارشیسازی شده باشد. متغیرهای پشتیبانیشده: YYBACKUPCTX، YYCURSOR، YYCTXMARKER، yyrecord (نگاشت به پیکربندیهای متناظر re2c:).
- code:yyskip_yypeek
- دستور ترکیبی code:yyskip و code:yypeek (بهطور پیشفرض code:yyskip و به دنبال آن code:yypeek).
- code:yypeek_yyskip
- دستور ترکیبی code:yypeek و code:yyskip (بهطور پیشفرض code:yypeek و به دنبال آن code:yyskip).
- code:yyskip_yybackup
- دستور ترکیبی code:yyskip و code:yybackup (بهطور پیشفرض code:yyskip و به دنبال آن code:yybackup).
- code:yybackup_yyskip
- دستور ترکیبی code:yybackup و code:yyskip (بهطور پیشفرض code:yybackup و به دنبال آن code:yyskip).
- code:yybackup_yypeek
- دستور ترکیبی code:yybackup و code:yypeek (بهطور پیشفرض code:yybackup و به دنبال آن code:yypeek).
- code:yyskip_yybackup_yypeek
- دستور ترکیبی code:yyskip، code:yybackup و code:yypeek (بهطور پیشفرض ``code:yyskip`` و به دنبال آن code:yybackup و سپس code:yypeek).
- code:yybackup_yypeek_yyskip
- دستور ترکیبی code:yybackup، code:yypeek و code:yyskip (بهطور پیشفرض ``code:yybackup`` و به دنبال آن code:yypeek و سپس code:yyskip).
- code:yyrestore
- دستور YYRESTORE، که ممکن است برای APIهای مختلف سفارشیسازی شده باشد. متغیرهای پشتیبانیشده: YYRESTORE، YYCURSOR، YYMARKER، yyrecord (نگاشت به پیکربندیهای متناظر re2c:).
- code:yyrestorectx
- دستور YYRESTORECTX، که ممکن است برای APIهای مختلف سفارشیسازی شده باشد. متغیرهای پشتیبانیشده: YYRESTORECTX، YYCURSOR، YYCTXMARKER، yyrecord (نگاشت به پیکربندیهای متناظر re2c:).
- code:yyrestoretag
- دستور YYRESTORETAG، که ممکن است برای APIهای مختلف سفارشیسازی شده باشد. متغیرهای پشتیبانیشده: YYRESTORETAG، YYCURSOR، yyrecord (نگاشت به پیکربندیهای متناظر re2c:)، tag (نام متغیر برچسب که برای بازیابی موقعیت استفاده میشود).
- code:yyshift
- دستور YYSHIFT، که ممکن است برای APIهای مختلف سفارشیسازی شده باشد. متغیرهای پشتیبانیشده: YYSHIFT، YYCURSOR، yyrecord (نگاشت به پیکربندیهای متناظر re2c:)، offset (تعداد واحدهای کد برای انتقال موقعیت فعلی).
- code:yyshiftstag
- دستور YYSHIFTSTAG، که ممکن است برای APIهای مختلف سفارشیسازی شده باشد. متغیرهای پشتیبانیشده: YYSHIFTSTAG، yyrecord، negative (نگاشت به پیکربندیهای متناظر re2c:)، tag (متغیر برچسب که باید منتقل شود)، offset (تعداد واحدهای کد برای انتقال). شرطها: .nested (درست اگر این یک برچسب تودرتو باشد -- در این حالت ممکن است مقدار آن برابر با re2c:tags:negative باشد، که نباید منتقل شود).
- code:yyshiftmtag
- دستور YYSHIFTMTAG، که ممکن است برای APIهای مختلف سفارشیسازی شده باشد. متغیرهای پشتیبانیشده: YYSHIFTMTAG (نگاشت به پیکربندی متناظر re2c:)، tag (متغیر برچسب که باید منتقل شود)، offset (تعداد واحدهای کد برای انتقال).
- code:yystagp
- دستور YYSTAGP، که ممکن است برای APIهای مختلف سفارشیسازی شده باشد. متغیرهای پشتیبانیشده: YYSTAGP، YYCURSOR، yyrecord (نگاشت به پیکربندیهای متناظر re2c:)، tag (متغیر برچسب که باید بهروزرسانی شود).
- code:yymtagp
- دستور YYMTAGP، که ممکن است برای APIهای مختلف سفارشیسازی شده باشد. متغیرهای پشتیبانیشده: YYMTAGP (نگاشت به پیکربندی متناظر re2c:)، tag (متغیر برچسب که باید بهروزرسانی شود).
- code:yystagn
- دستور YYSTAGN، که ممکن است برای APIهای مختلف سفارشیسازی شده باشد. متغیرهای پشتیبانیشده: YYSTAGN، negative، yyrecord (نگاشت به پیکربندیهای متناظر re2c:)، tag (متغیر برچسب که باید بهروزرسانی شود).
- code:yymtagn
- دستور YYMTAGN، که ممکن است برای APIهای مختلف سفارشیسازی شده باشد. متغیرهای پشتیبانیشده: YYMTAGN (نگاشت به پیکربندی متناظر re2c:)، tag (متغیر برچسب که باید بهروزرسانی شود).
- code:yycopystag
- دستور YYCOPYSTAG، که ممکن است برای APIهای مختلف سفارشیسازی شده باشد. متغیرهای پشتیبانیشده: YYCOPYSTAG، yyrecord (نگاشت به پیکربندیهای متناظر re2c:)، lhs، rhs (متغیرهای برچسب سمت چپ و راست عملیات رونوشت).
- code:yycopymtag
- دستور YYCOPYMTAG، که ممکن است برای APIهای مختلف سفارشیسازی شده باشد. متغیرهای پشتیبانیشده: YYCOPYMTAG، yyrecord (نگاشت به پیکربندیهای متناظر re2c:)، lhs، rhs (متغیرهای برچسب سمت چپ و راست عملیات رونوشت).
- code:yygetaccept
- دستور YYGETACCEPT، که ممکن است برای APIهای مختلف سفارشیسازی شده باشد. متغیرهای پشتیبانیشده: YYGETACCEPT، yyrecord (نگاشت به پیکربندیهای متناظر re2c:)، var (نگاشت به پیکربندی re2c:yyaccept).
- code:yysetaccept
- دستور YYSETACCEPT، که ممکن است برای APIهای مختلف سفارشیسازی شده باشد. متغیرهای پشتیبانیشده: YYSETACCEPT، yyrecord (نگاشت به پیکربندیهای متناظر re2c:)، var (نگاشت به پیکربندی re2c:yyaccept) و val (مقدار عددی قاعده پذیرفتهشده).
- code:yygetcond
- دستور YYGETCOND، که ممکن است برای APIهای مختلف سفارشیسازی شده باشد. متغیرهای پشتیبانیشده: YYGETCOND، yyrecord (نگاشت به پیکربندیهای متناظر re2c:)، var (نگاشت به پیکربندی re2c:yycond).
- code:yysetcond
- دستور YYSETCOND، که ممکن است برای APIهای مختلف سفارشیسازی شده باشد. متغیرهای پشتیبانیشده: YYSETCOND، yyrecord (نگاشت به پیکربندیهای متناظر re2c:)، var (نگاشت به پیکربندی re2c:yycond) و val (شناسه عددی شرط).
- code:yygetstate
- دستور YYGETSTATE، که ممکن است برای APIهای مختلف سفارشیسازی شده باشد. متغیرهای پشتیبانیشده: YYGETSTATE، yyrecord (نگاشت به پیکربندیهای متناظر re2c:)، var (نگاشت به پیکربندی re2c:yystate).
- code:yysetstate
- دستور YYSETSTATE، که ممکن است برای APIهای مختلف سفارشیسازی شده باشد. متغیرهای پشتیبانیشده: YYSETSTATE، yyrecord (نگاشت به پیکربندیهای متناظر re2c:)، var (نگاشت به پیکربندی re2c:yystate) و val (شماره وضعیت).
- code:yylessthan
- دستور YYLESSTHAN، که ممکن است برای APIهای مختلف سفارشیسازی شده باشد. متغیرهای پشتیبانیشده: YYLESSTHAN، YYCURSOR، YYLIMIT، yyrecord (نگاشت به پیکربندیهای متناظر re2c:)، need (تعداد واحدهای کد برای مقایسه در برابر آن). شرط: .many (درست اگر مقدار need بیشتر از یک باشد).
- code:yyend
- عبارت YYEND، که ممکن است برای APIهای مختلف سفارشیسازی شده باشد. متغیرهای پشتیبانیشده: YYEND، YYCURSOR، YYLIMIT.
- code:yybm_filter
- شرطی که برای پالایش مقادیر yych که توسط جدول yybm پوشش داده نشدهاند استفاده میشود (همراه با گزینه --bitmaps به کار میرود). متغیر پشتیبانیشده: yych (نگاشت به پیکربندی re2c:yych).
- code:yybm_match
- قالب بررسی جدول yybm (تولیدشده با گزینه --bitmaps). متغیرهای پشتیبانیشده: yybm، yych (نگاشت به پیکربندیهای متناظر re2c:)، offset (آفست در جدول yybm که باید به yych افزوده شود) و mask (ماسک بیتی که باید روی ورودی جدول اعمال شود تا مقدار بولی مورد نیاز برای بررسی به دست آید)
- code:yytarget_filter
- شرطی که برای پالایش مقادیر yych که توسط جدول yytarget پوشش داده نشدهاند استفاده میشود (همراه با گزینه --computed-gotos به کار میرود). متغیر پشتیبانیشده: yych (نگاشت به پیکربندی re2c:yych).
در اینجا فهرستی از تمام متغیرهای سراسری مجاز در فایلهای نحو آورده شده است:
- nl
- یک خط جدید.
- indent
- متغیری که هیچ کدی تولید نمیکند، اما اثر جانبی افزایش سطح تورفتگی را به همراه دارد..TP dedent متغیری که کدی تولید نمیکند، اما دارای اثر جانبی کاهش سطح تورفتگی است.
- topindent
- رشتهٔ تورفتگی برای دستور جاری. سطح تورفتگی توسط تولیدکنندهٔ کد ردیابی شده و بهطور خودکار بهروزرسانی میشود.
در اینجا فهرستی از تمام شرطهای سراسری مجاز در فایلهای نحو آمده است:
- .api.simple
- درست است اگر از API ساده استفاده شود (--api simple یا re2c:api = simple).
- .api.generic
- درست است اگر از API عمومی استفاده شود (--api generic یا re2c:api = generic).
- .api.record
- درست است اگر از API رکورد استفاده شود (--api record یا re2c:api = record).
- .api_style.functions
- درست است اگر از سبک API تابعگونه استفاده شود (re2c:api-style = functions).
- .api_style.freeform
- درست است اگر از سبک API با فرم آزاد استفاده شود (re2c:api-style = free-form).
- .case_ranges
- درست است اگر ویژگی بازههای case فعال باشد (--case-ranges یا re2c:case-ranges = 1).
- .cgoto.relative
- درست است اگر شکل نسبی goto محاسباتی استفاده شود (--computed-gotos-relative یا re2c:cgoto:relative = 1).
- .code_model.goto_label
- درست است اگر مدل کد مبتنی بر goto/label استفاده شود (--goto-label).
- .code_model.loop_switch
- درست است اگر مدل کد مبتنی بر loop/switch استفاده شود (--loop-switch).
- .code_model.recursive_functions
- درست است اگر مدل کد مبتنی بر توابع بازگشتی استفاده شود (--recursive-function).
- .date
- درست است اگر اثرانگشت تولیدشده باید شامل تاریخ تولید باشد.
- .loop_label
- درست است اگر حلقههای تولیدشده توسط re2swift باید برچسب داشته باشند (re2c:label:yyloop روی یک رشتهٔ غیرخالی تنظیم شده باشد).
- .monadic
- درست است اگر کد تولیدشده باید بهصورت مونادیک باشد (re2c:monadic = 1). این مورد تنها برای زبانهای تابعی خالص کاربرد دارد.
- .start_conditions
- درست است اگر شرایط شروع فعال باشند (--start-conditions).
- .storable_state
- درست است اگر وضعیت قابل ذخیرهسازی فعال باشد (--storable-state).
- .unsafe
- درست است اگر re2swift برای تولید کد سریعتر باید از بلوکهای "unsafe" استفاده کند (--unsafe، re2c:unsafe = 1). این مورد تنها برای زبانهایی که دارای ویژگی "unsafe" هستند کاربرد دارد.
- .version
- درست است اگر اثرانگشت تولیدشده باید شامل نسخهٔ re2swift باشد.
- .yyfill.enable
- درست است اگر YYFILL فعال باشد (re2c:yyfill:enable = 1).
- .yyfn.throw
- درست است اگر پیکربندی re2c:yyfn:throw با یک رشتهٔ غیرخالی تعریف شده باشد.
مدیریت پایان ورودی (HANDLING THE END OF INPUT)
یکی از مشکلات اصلی برای تحلیلگر واژگانی (lexer)، دانستن زمان توقف است. چند شرط برای خاتمه وجود دارد:
- تحلیلگر واژگانی ممکن است با قاعدهای تطابق یابد (شامل قاعدهٔ پیشفرض *) و به یک وضعیت پایانی برسد
- تحلیلگر واژگانی ممکن است در تطبیق با هر قاعدهای ناموفق باشد و به یک وضعیت پیشفرض برسد
- تحلیلگر واژگانی ممکن است به پایان ورودی برسد
دو شرط نخست، تحلیلگر واژگانی را به شیوهای «طبیعی» خاتمه میدهند: تحلیلگر به وضعیتی بدون گذارهای خروجی میرسد و تطبیق بهطور خودکار متوقف میشود. شرط سوم، پایان ورودی، متفاوت است: این حالت ممکن است در هر وضعیتی رخ دهد و تحلیلگر واژگانی باید قادر به مدیریت آن باشد. بررسی پایان ورودی، روند کار عادی تحلیلگر واژگانی را دچار وقفه کرده و شاخههای شرطی را به برنامهٔ تولیدشده میافزاید؛ بنابراین لازم است تعداد چنین بررسیهایی به حداقل برسد. re2swift از چند روش مختلف برای مدیریت پایان ورودی پشتیبانی میکند. انتخاب روش مورد استفاده به پیچیدگی عبارات منظم، نیاز به بافرسازی، ملاحظات کارایی و سایر عوامل بستگی دارد. در ادامه فهرستی از این روشها آمده است:
- نویسهٔ نگهبان (Sentinel). این روش نیاز به بررسیهای پایان ورودی را بهکلی برطرف میکند. این روش ساده و کارآمد است، اما محدود به مواردی است که یک نویسهٔ «نگهبان» طبیعی وجود داشته باشد که هرگز در ورودی معتبر رخ ندهد. این نویسه ممکن است همچنان در ورودی نامعتبر رخ دهد، اما نباید توسط عبارات منظم مجاز دانسته شود، مگر شاید به عنوان آخرین نویسهٔ یک قاعده. نویسهٔ نگهبان به انتهای ورودی ضمیمه شده و به عنوان یک سیگنال توقف عمل میکند: زمانی که تحلیلگر واژگانی این نویسه را بخواند، یا یک خطای نحوی است یا پایان ورودی. در هر دو حالت، تحلیلگر واژگانی باید متوقف شود. این روش زمانی استفاده میشود که YYFILL با re2c:yyfill:enable = 0; غیرفعال شده و re2c:eof مقدار پیشفرض -1 را داشته باشد.
- نویسهٔ نگهبان به همراه بررسی کرانها (Sentinel with bounds checks). این روش همگانی (generic) است: امکان پردازش هر ورودی را بدون محدودیت روی عبارات منظم فراهم میکند. ایدهٔ کار کاهش تعداد بررسیهای پایان ورودی با انجام آنها تنها روی نویسههایی خاص است. مشابه روش «نگهبان»، یکی از نویسهها به عنوان «نگهبان» انتخاب شده و به انتهای ورودی ضمیمه میشود. با این حال، هیچ محدودیتی در مورد محل وقوع نویسهٔ نگهبان وجود ندارد (در واقع هر نویسهای میتواند به عنوان نگهبان انتخاب شود). زمانی که تحلیلگر واژگانی این نویسه را میخواند، علاوه بر آن یک بررسی کران انجام میدهد. اگر موقعیت فعلی در محدودهٔ مجاز باشد، تحلیلگر واژگانی تطبیق را از سر گرفته و با نویسهٔ نگهبان مانند یک نویسهٔ معمولی رفتار میکند. در غیر این صورت، YYFILL را فراخوانی میکند (مگر اینکه غیرفعال شده باشد). اگر ورودی بیشتری ارائه شود، تحلیلگر واژگانی نویسهٔ پایانی را مجدداً تطبیق داده و بهگونهای ادامه میدهد که گویی نگهبان اصلاً وجود نداشته است. در غیر این صورت، باید پایان واقعی ورودی باشد و تحلیلگر واژگانی متوقف میشود. این روش زمانی استفاده میشود که re2c:eof مقداری غیرمنفی داشته باشد (باید روی مقدار عددیِ نویسهٔ نگهبان تنظیم شود). استفاده از YYFILL اختیاری است.
- بررسی کرانها به همراه لایهگذاری (Bounds checks with padding). این روش همگانی است و ممکن است از روش «نگهبان به همراه بررسی کرانها» سریعتر باشد، اما پیچیدهتر نیز هست. ایدهٔ کار این است که وضعیتهای DFA به مؤلفههای قویاً همبند (SCCها) افراز شوند و برای هر SCC یک بررسی منفرد به اندازهٔ کافی نویسه تولید شود تا طولانیترین مسیر بدون حلقه در این SCC را پوشش دهد. این کار تعداد بررسیها را کاهش میدهد، اما مشکلی با تکواژهای (lexemes) کوتاه در انتهای ورودی ایجاد میکند، زیرا بررسی به نویسههای کافی برای پوشش طولانیترین تکواژ نیاز دارد. این مشکل را میتوان با لایهگذاری ورودی با چند نویسهٔ ساختگی که پسوند معتبری برای یک تکواژ تشکیل نمیدهند (تا تحلیلگر واژگانی نتواند با آنها تطبیق یابد) برطرف کرد. طول لایهگذاری باید YYMAXFILL باشد که با یک بلوک max تولید میشود. اگر ورودی کافی وجود نداشته باشد، تحلیلگر واژگانی YYFILL را فراخوانی میکند که باید حداقل تعداد نویسههای لازم را تأمین کند یا بازنگردد. این روش در صورتی به کار میرود که YYFILL فعال باشد و re2c:eof برابر -1 باشد (این پیکربندی پیشفرض است).
- بررسیهای سفارشی (Custom checks). رابط Generic API به کاربر اجازه میدهد عملیات پایهای مانند خواندن یک نویسه را رونویسی کند، که امکان گنجاندن بررسیهای پایان ورودی را به عنوان بخشی از آنها فراهم میسازد. این رویکرد خطازاست و باید با احتیاط استفاده شود. برای استفاده از یک روش سفارشی، API عمومی را با --api custom یا re2c:api = custom; فعال کرده و بررسیهای پیشفرض کرانها را با re2c:yyfill:enable = 0; یا re2c:yyfill:check = 0; غیرفعال کنید.
زیربخشهای زیر شامل مثالی از هر روش هستند..SS نگهبان (Sentinel)
این مثال از یک نویسه نگهبان (sentinel character) برای مدیریت پایان ورودی استفاده میکند. این برنامه واژههای جداشده با فاصله را در یک رشته پایانیافته با null شمارش میکند. نویسه نگهبان null است: آخرین نویسه از هر رشته ورودی است، و در هیچیک از قواعد در میانه یک لکسم (lexeme) مجاز دانسته نشده است (بهویژه، در بازههای نویسهای که نادیدهگرفتن آن آسان است گنجانده نشده است). اگر یک null در میانه رشته رخ دهد، یک خطای نحو (syntax error) است و لکسر با قاعده پیشفرض * تطابق مییابد، اما دادهای فراتر از پایان ورودی را نخواهد خواند و دچار فروپاشی (crash) نخواهد شد (از هشدار -Wsentinel-in-midrule و پیکربندی re2c:sentinel برای راستیآزمایی این مورد استفاده کنید). پیکربندی re2c:yyfill:enable = 0; از تولید بررسیهای محدوده (bounds checks) و فراخوانیهای YYFILL جلوگیری میکند.
// re2swift $INPUT -o $OUTPUT
// Expects a null-terminated string.
func lex(_ yyinput: UnsafePointer<UInt8>) -> Int? {
var yycursor = 0, count = 0
loop: while true {
/*!re2c
re2c:yyfill:enable = 0;
[a-z]+ {
count += 1
continue loop
}
[ ]+ { continue loop }
[\x00] { return count }
* { return nil }
*/
}
}
func test(_ str: StaticString, _ expect: Int?) {
assert(lex(str.utf8Start) == expect)
}
test("", 0)
test("one two three", 3)
test("f0ur", nil)
نگهبان با بررسیهای محدوده (Sentinel with bounds checks)
این مثال از نویسه نگهبان همراه با بررسیهای محدوده برای مدیریت پایان ورودی استفاده میکند (این روش در نسخه 1.2 اضافه شد). این برنامه رشتههای محصور در تکنقلقول (single-quoted) که با فاصله از هم جدا شدهاند را میشمارد. نویسه نگهبان null است که با پیکربندی re2c:eof = 0; تعیین میشود. همانند روش sentinel، نویسه null آخرین نویسه هر رشته ورودی است، اما در میانه یک قاعده نیز مجاز است (برای مثال، 'aaa\0aa'\0 ورودی معتبری است، اما 'aaa\0 یک خطای نحوی است). بررسیهای محدوده در هر حالتی که یک نویسه ورودی را تطبیق میدهد تولید میشوند، اما دامنه آنها به شاخهای که null را مدیریت میکند محدود است. بررسیهای محدوده با API عمومی به صورت YYLIMIT <= YYCURSOR یا YYLESSTHAN(1) هستند. اگر شرط بررسی برقرار (true) باشد، لکسر به پایان ورودی رسیده است و باید متوقف شود (از آنجا که ورودی در یک بافر جای میگیرد، YYFILL با re2c:yyfill:enable = 0; غیرفعال شده است؛ برای مشاهده مثالی که از YYFILL استفاده میکند، به بخش YYFILL with sentinel مراجعه کنید). رسیدن به پایان ورودی سه حالت را پیش میآورد: اگر لکسر در حالت اولیه باشد، با قاعده پایان ورودی $ مطابقت مییابد؛ در غیر این صورت ممکن است به یک قاعده تطبیقیافته قبلی (از جمله قاعده پیشفرض *) بازگردد یا به یک حالت پیشفرض برود، که موجب هشدار -Wundefined-control-flow میشود.
// re2swift $INPUT -o $OUTPUT
// Expects a null-terminated string.
func lex(_ yyinput: UnsafePointer<UInt8>, _ length: Int) -> Int? {
var yycursor = 0, yylimit = length, yymarker = 0
var count = 0
loop: while true {
/*!re2c
re2c:eof = 0;
re2c:yyfill:enable = 0;
str = ['] ([^'\\] | [\\][^])* ['];
str {
count += 1
continue loop
}
[ ]+ { continue loop }
* { return nil }
$ { return count }
*/
}
}
func test(_ str: StaticString, _ expect: Int?) {
assert(lex(str.utf8Start, str.utf8CodeUnitCount) == expect)
}
test("", 0);
test("'qu\0tes' 'are' 'fine: \\'' ", 3);
test("'unterminated\\'", nil);
بررسیهای محدوده با پدینگ (Bounds checks with padding)
این مثال از بررسیهای محدوده به همراه پدینگ (padding) برای مدیریت پایان ورودی استفاده میکند (این روش بهطور پیشفرض فعال است). این برنامه رشتههای محصور در تکنقلقول که با فاصله جدا شدهاند را میشمارد. یک پدینگ متشکل از نویسههای null به تعداد YYMAXFILL به انتهای ورودی افزوده شده است، که در آن مقدار YYMAXFILL بهطور خودکار توسط یک بلوک max تولید میشود. الزامی به استفاده از null برای پدینگ وجود ندارد --- تا زمانی که نویسهها پسوند معتبری برای یک لکسم تشکیل ندهند، میتوان از هر نویسهای استفاده کرد (در این مثال، پدینگ نباید شامل تکنقلقول باشد، چرا که ممکن است با پسوند یک رشته محصور در تکنقلقول اشتباه گرفته شود). یک قاعده توقف ("stop") وجود دارد که با نخستین نویسه پدینگ (null) تطبیق یافته و لکسر را خاتمه میدهد (توجه داشته باشید که بررسی میکند آیا null در ابتدای پدینگ قرار دارد یا خیر، در غیر این صورت یک خطای نحوی است). بررسیهای محدوده تنها در برخی از حالتها تولید میشوند که توسط مؤلفههای قویاً همبند خودکار (اتوماتون) زیرین تعیین میگردند. بررسیها با API عمومی به صورت (YYLIMIT - YYCURSOR) < n یا YYLESSTHAN(n) هستند، که در آن n حداقل تعداد نویسههای مورد نیاز برای ادامه کار لکسر است (این همچنین بدان معناست که بررسی محدوده بعدی حداکثر در n نویسه دیگر رخ خواهد داد). اگر شرط بررسی برقرار باشد، لکسر به پایان ورودی رسیده است و YYFILL(n) را فراخوانی میکند که یا باید حداقل n نویسه ورودی را فراهم کند یا بازگشتی نداشته باشد. در این مثال YYFILL همیشه ناموفق است و لکسر را با یک خطا متوقف میکند (که مشکلی نیست، زیرا ورودی درون یک بافر جا میشود). برای مثالی که بافر ورودی را با استفاده از YYFILL دوباره پر میکند، به بخش YYFILL with padding مراجعه کنید.
// re2swift $INPUT -o $OUTPUT
/*!max:re2c*/
func lex(_ str: UnsafeBufferPointer<UInt8>) -> Int? {
let length = str.count
// Make a copy of the string with YYMAXFILL zeroes at the end.
let yyinput = ContiguousArray<UInt8>(str) + repeatElement(0, count: yymaxfill)
var yycursor = 0, yylimit = length + yymaxfill
var count = 0
loop: while true {
/*!re2c
re2c:YYFILL = "return nil";
str = ['] ([^'\\] | [\\][^])* ['];
str {
count += 1
continue loop
}
[\x00] {
// Check that it's the sentinel, not some unexpected null.
if yycursor - 1 == length { return count } else { return nil }
}
[ ]+ { continue loop }
* { return nil }
*/
}
}
func test(_ str: StaticString, _ expect: Int?) {
str.withUTF8Buffer {
assert(lex($0) == expect)
}
}
test("", 0)
test("'qu\0tes' 'are' 'fine: \\'' ", 3)
test("'unterminated\\'", nil)
test("'unexpected \0 null\\'", nil)
این مثال از یک روش سفارشی مدیریت پایان ورودی (end-of-input) بر پایهٔ API عمومی (generic API) استفاده میکند. برنامه رشتههای محصور در نقلقول تکی (single-quoted) را که با فاصله از هم جدا شدهاند میشمارد. این برنامه مشابه مثال sentinel است، با این تفاوت که ورودی با نویسهٔ تهی پایان نیافته است (null-terminated نیست). برای جبران نبود نویسهٔ نگهبان (sentinel) در انتهای ورودی، YYPEEK بازتعریف شده است تا پیش از خواندن نویسهٔ بعدی ورودی، یک بررسی محدوده (bounds check) انجام دهد. این روش ناکارآمد است زیرا بررسیها بسیار مکرر انجام میشوند. اگر شرط بررسی برقرار نباشد، YYPEEK نویسهٔ واقعی را برمیگرداند؛ در غیر این صورت یک نویسهٔ نگهبان ساختگی بازمیگرداند.
// re2swift $INPUT -o $OUTPUT
import Foundation
func lex(_ str: Data) -> Int? {
var cursor = 0, limit = str.count
var count = 0
loop: while true {
/*!re2c
re2c:api = generic;
re2c:yyfill:enable = 0;
re2c:YYSKIP = "cursor += 1";
re2c:YYPEEK = "cursor < limit ? str[cursor] : 0"; // fake null
[a-z]+ {
count += 1
continue loop
}
[\x00] { return count }
[ ]+ { continue loop }
* { return nil}
*/
}
}
func test(_ str: String, _ expect: Int?) {
// For the sake of example create a string without terminating null.
assert(lex(Data(str.utf8)) == expect)
}
test("", 0)
test("one two three ", 3)
test("f0ur", nil)
پر کردن مجدد بافر (BUFFER REFILLING)
نیاز به بافرسازی زمانی رخ میدهد که ورودی را نتوان بهطور یکجا در حافظه نگاشت کرد: یا بیش از حد بزرگ است، یا به صورت جریانی دریافت میشود (مانند خواندن از یک سوکت). تکنیک معمول در چنین مواردی، تخصیص یک بافر حافظه با اندازهٔ ثابت و پردازش ورودی در تکههایی (chunks) است که در بافر جا میشوند. هنگامی که تکهٔ فعلی پردازش شد، به بیرون منتقل شده و دادههای جدید وارد میشوند. در عمل این موضوع تا حدی پیچیدهتر است، زیرا وضعیت تحلیلگر لغوی (lexer) تنها از یک موقعیت ورودی تشکیل نشده، بلکه شامل مجموعهای از موقعیتهای مرتبط است:
- مکاننما (cursor): نویسهٔ بعدی ورودی برای خواندن (YYCURSOR در API اشارهگر C یا YYSKIP/YYPEEK در generic API)
- حد (limit): موقعیتِ پس از آخرین نویسهٔ موجود ورودی (YYLIMIT در API اشارهگر C، یا مدیریتشده بهطور ضمنی توسط YYLESSTHAN در generic API)
- نشانهگر (marker): موقعیت تازهترین تطابق، در صورت وجود (YYMARKER در API پیشفرض یا YYBACKUP/YYRESTORE در generic API)
- توکن (token): ابتدای لکسم (lexeme) فعلی (در re2swift API ضمنی است، چرا که برای عملکرد عادی لکسر نیازی به آن نیست و میتواند توسط کاربر تعریف و بهروزرسانی شود)
- نشانهگر زمینه (context marker): موقعیت زمینهٔ انتهایی (YYCTXMARKER در API اشارهگر C یا YYBACKUPCTX/YYRESTORECTX در generic API)
- متغیرهای برچسب (tag variables): موقعیتهای زیرتطابق (تعریفشده با بلوکهای stags و mtags و توابع اولیهٔ generic API شامل YYSTAGP/YYSTAGN/YYMTAGP/YYMTAGN)
همهٔ این موارد در هر حالتی به کار نمیروند، اما در صورت استفاده، باید توسط YYFILL بهروزرسانی شوند. تمام موقعیتهای فعال در بازهٔ بین token و cursor قرار دارند؛ بنابراین هر چیزی میان ابتدای بافر و token را میتوان دور ریخت، بازهٔ token تا limit باید به ابتدای بافر منتقل شود، و فضای خالی انتهای بافر باید با دادههای جدید پر شود. برای جلوگیری از فراخوانیهای مکرر YYFILL، بهتر است تا حد امکان نویسههای ورودی بیشتری پر شود (حتی اگر نویسههای کمتری برای از سرگیری لکسر کافی باشد). جزئیات پیادهسازی YYFILL بسته به روش مدیریت EOF مورداستفاده کمی متفاوت است: حالت قاعدهٔ EOF تا حدی سادهتر از حالت بررسی محدوده همراه با لایهگذاری (padding) است. همچنین توجه داشته باشید که اگر گزینهٔ -f --storable-state استفاده شود، YYFILL معناشناسی کمی متفاوتی خواهد داشت (که در بخش مربوط به وضعیت قابل ذخیره شرح داده شده است).
YYFILL با نویسهٔ نگهبان (YYFILL with sentinel)
اگر از قاعدهٔ EOF استفاده شود، YYFILL یک سازوکار اولیّهٔ شبهتابع است که هیچ آرگومانی نمیپذیرد و مقداری بازمیگرداند که با صفر مقایسه میشود. فراخوانی YYFILL با شرط YYLIMIT <= YYCURSOR در API اشارهگر C و YYLESSTHAN() در generic API تحریک میشود. یک مقدار بازگشتی غیرصفر به این معنی است که YYFILL شکست خورده است. یک فراخوانی موفق YYFILL باید حداقل یک نویسه را تامین کرده و موقعیتهای ورودی را بر این اساس تنظیم کند. Limit همیشه باید روی یک واحد پس از آخرین موقعیت ورودی در بافر تنظیم شود، و نویسهٔ موجود در موقعیت limit باید نماد نگهبان تعیینشده توسط پیکربندی re2c:eof باشد. تصاویر زیر مکانهای نسبی موقعیتهای ورودی در بافر را قبل و بعد از فراخوانی YYFILL نشان میدهند (نماد نگهبان با # مشخص شده است، و تصویر دوم حالتی را نشان میدهد که ورودی کافی برای پر کردن کل بافر وجود ندارد).
<-- shift -->
>-A------------B---------C------------D#-----------E->
buffer token marker limit,
cursor
>-A------------B---------C------------D------------E#->
buffer, marker cursor limit
token
<-- shift -->
>-A------------B---------C------------D#--E (EOF)
buffer token marker limit,
cursor
>-A------------B---------C------------D---E#........
buffer, marker cursor limit
token
در اینجا مثالی از برنامهای آورده شده است که فایل ورودی input.txt را در تکههای ۴۰۹۶ بایتی میخواند و از قاعدهٔ EOF استفاده میکند.
// re2swift $INPUT -o $OUTPUT
import Foundation
struct Input {
static let bufferSize = 4095
var file: FileHandle
var yyinput = ContiguousArray<UInt8>(repeating: 0, count: Self.bufferSize + 1)
var yylimit = Self.bufferSize
var yycursor = Self.bufferSize
var yymarker = Self.bufferSize
var token = -1
var eof = false
}
extension Input {
mutating func lex() -> Int? {
var count = 0
loop: while true {
self.token = self.yycursor
/*!re2c
re2c:api = record;
re2c:eof = 0;
re2c:yyrecord = "self";
re2c:YYFILL = "self.fill() == .ok";
str = ['] ([^'\\] | [\\][^])* ['];
str {
count += 1
continue loop
}
[ ]+ { continue loop }
$ { return count }
* { return nil }
*/
}
}
mutating func fill() -> FillStatus {
guard !self.eof else {
return .eof
}
let shift = self.token
let used = self.yylimit - self.token
let free = Self.bufferSize - used
// Error: Lexeme too long. In the real world we can reallocate a larger buffer.
if self.token < 1 {
return .longLexeme
}
// Shift buffer contents, discarding everything up to the current lexeme.
self.yyinput.replaceSubrange(..<used, with: self.yyinput[shift..<self.yylimit])
self.yylimit -= shift
self.yycursor -= shift
self.yymarker &-= shift // May underflow if marker is unused
self.token = 0
// Fill free space at the end of buffer with new data from file.
do {
if let data = try self.file.read(upToCount: free) {
self.yyinput.replaceSubrange(self.yylimit..<(self.yylimit + data.count), with: data)
self.yylimit += data.count
}
} catch {
fatalError("cannot read from file: \(error.localizedDescription)")
}
self.yyinput[self.yylimit] = 0 // append sentinel
self.eof = self.yylimit < Self.bufferSize
return .ok
}
enum FillStatus {
case ok, eof, longLexeme
}
}
let fileName = "input"
let content = "'qu\0tes' 'are' 'fine: \\'' "
// Prepare input file: a few times the size of the buffer,
// containing strings with zeroes and escaped quotes.
guard FileManager.default.createFile(
atPath: fileName,
contents: Data(String(repeating: content, count: Input.bufferSize).utf8)
) else {
fatalError("failed to write file \"\(fileName)\"")
}
// Number of quoted strings written to file
let count = 3 * Input.bufferSize
// Initialize lexer state
guard let file = FileHandle(forReadingAtPath: fileName) else {
throw NSError(domain: NSCocoaErrorDomain, code: CocoaError.fileReadNoSuchFile.rawValue)
}
var `in` = Input(file: file)
// Run the lexer
assert(`in`.lex() == count)
// Cleanup: remove input file
try file.close()
try FileManager.default.removeItem(atPath: fileName)
در حالت پیشفرض (زمانی که قاعده EOF استفاده نمیشود)، YYFILL یک سازوکار پایهای تابعگونه است که یک آرگومان واحد را میپذیرد و هیچ مقداری بازنمیگرداند. فراخوانی YYFILL با شرط (YYLIMIT - YYCURSOR) < n در API اشارهگر C و با YYLESSTHAN(n) در API عمومی فعال میشود. آرگومان ارسالشده به YYFILL حداقل تعداد نویسههایی است که باید فراهم شوند. در صورت عدم موفقیت در تأمین آن، YYFILL نباید کنترل را به لکسر (lexer) بازگرداند (به همین دلیل بهتر است به صورت یک ماکرو پیادهسازی شود که در صورت شکست، از تابع فراخواننده بازمیگردد). در صورت فراخوانی موفق YYFILL، موقعیت حد (limit) باید یا روی یک موقعیت پس از آخرین موقعیت ورودی در بافر، یا روی انتهای لایهگذاری YYMAXFILL تنظیم شود (در حالتی که YYFILL حداقل n نویسه را با موفقیت خوانده باشد، اما این مقدار برای پر کردن کل بافر کافی نباشد). تصاویر زیر موقعیتهای نسبی جایگاههای ورودی در بافر را پیش و پس از فراخوانی YYFILL نشان میدهند (لایهگذاری YYMAXFILL در تصویر دوم با نمادهای # مشخص شده است).
<-- shift --> <-- need -->
>-A------------B---------C-----D-------E---F--------G->
buffer token marker cursor limit
>-A------------B---------C-----D-------E---F--------G->
buffer, marker cursor limit
token
<-- shift --> <-- need -->
>-A------------B---------C-----D-------E-F (EOF)
buffer token marker cursor limit
>-A------------B---------C-----D-------E-F###############
buffer, marker cursor limit
token <- YYMAXFILL ->
در اینجا مثالی از برنامهای آمده است که فایل ورودی input.txt را در قطعات ۴۰۹۶ بایتی میخواند و از بررسی محدودهها (bounds-checking) به همراه لایهگذاری استفاده میکند.
// re2swift $INPUT -o $OUTPUT
import Foundation
/*!max:re2c*/
struct Input {
static let bufferSize = 4096 - yymaxfill
var file: FileHandle
var yyinput = ContiguousArray<UInt8>(repeating: 0, count: Self.bufferSize + yymaxfill)
var yylimit = Self.bufferSize
var yycursor = Self.bufferSize
var token = -1
var eof = false
}
extension Input {
mutating func lex() -> Int? {
var count = 0
loop: while true {
self.token = self.yycursor
/*!re2c
re2c:api = record;
re2c:yyrecord = "self";
re2c:YYFILL = "if self.fill(@@) != .ok { return nil }";
str = ['] ([^'\\] | [\\][^])* ['];
[\x00] {
// Check that it is the sentinel, not some unexpected null.
return self.token == self.yylimit - yymaxfill ? count : nil;
}
str {
count += 1
continue loop
}
[ ]+ { continue loop }
* { return nil }
*/
}
}
mutating func fill(_ need: Int) -> FillStatus {
guard !self.eof else {
return .eof
}
let shift = self.token
let used = self.yylimit - self.token
let free = Self.bufferSize - used
// Error: Lexeme too long. In the real world we can reallocate a larger buffer.
if self.token < need {
return .longLexeme
}
// Shift buffer contents, discarding everything up to the current lexeme.
self.yyinput.replaceSubrange(..<used, with: self.yyinput[shift..<self.yylimit])
self.yylimit -= shift
self.yycursor -= shift
self.token = 0
// Fill free space at the end of buffer with new data from file.
let read: Int
do {
if let data = try self.file.read(upToCount: free) {
read = data.count
self.yyinput.replaceSubrange(self.yylimit..<(self.yylimit + read), with: data)
} else {
read = 0
}
} catch {
fatalError("cannot read from file: \(error.localizedDescription)")
}
// If read less than expected, this is end of input => add zero padding
// so that the lexer can access characters at the end of buffer.
self.yylimit += read
if read < free {
self.eof = true
self.yyinput.withUnsafeMutableBytes {
_ = memset($0.baseAddress! + self.yylimit, 0, yymaxfill)
}
self.yylimit += yymaxfill
}
return .ok
}
enum FillStatus {
case ok, eof, longLexeme
}
}
let fileName = "input"
let content = "'qu\0tes' 'are' 'fine: \\'' ";
// Prepare input file: a few times the size of the buffer,
// containing strings with zeroes and escaped quotes.
guard FileManager.default.createFile(
atPath: fileName,
contents: Data(String(repeating: content, count: Input.bufferSize).utf8)
) else {
fatalError("failed to write file \"\(fileName)\"")
}
// Number of quoted strings written to file
let count = 3 * Input.bufferSize
// Initialize lexer state
// This immediately triggers YYFILL, as the check `in.yycursor < in.yylimit` fails.
guard let file = FileHandle(forReadingAtPath: fileName) else {
throw NSError(domain: NSCocoaErrorDomain, code: CocoaError.fileReadNoSuchFile.rawValue)
}
var `in` = Input(file: file)
// Run the lexer
assert(`in`.lex() == count)
// Cleanup: remove input file
try file.close()
try FileManager.default.removeItem(atPath: fileName)
بلوکهای چندگانه (Multiple blocks)
گاهی اوقات لازم است چندین تحلیلگر لغوی (lexer) مرتبط با یکدیگر داشته باشیم (برای مثال، اگر یک ماشین حالت سطح بالا وجود داشته باشد که بین حالتهای مختلف لکسر گذار انجام میدهد). این کار میتواند با استفاده از چندین بلوک بههمپیوستهٔ re2swift پیادهسازی شود. گزینهٔ دیگر استفاده از شرایط شروع (start conditions) است.
پیادهسازی اتصالات بین بلوکها به زبان مقصد بستگی دارد. در زبانهایی که دستور goto دارند (مانند C/C++ و Go) میتوان همهٔ بلوکها را در یک تابع قرار داد، به طوری که پیشوند هر یک از آنها یک برچسب باشد. گذار از یک بلوک به بلوک دیگر یک goto ساده است. در زبانهایی که goto ندارند (مانند Rust)، لازم است از یک حلقه به همراه switch روی متغیر حالت استفاده شود، مشابه حلقه/switch مربوط به yystate که توسط re2swift تولید میشود؛ یا اینکه هر بلوک درون یک تابع قرار گرفته و از فراخوانی توابع استفاده گردد.
مثال زیر از چندین بلوک برای تجزیهٔ اعداد دودویی (binary)، هشتهشتی (octal)، دهدهی (decimal) و شانزدهشانزدهی (hexadecimal) استفاده میکند. هر مبنا بلوک مخصوص به خود را دارد. بلوک اولیه مبنا را تعیین کرده و کنترل را به سایر بلوکها هدایت میکند. پیکربندیهای مشترک در یک بلوک مجزا در ابتدای برنامه تعریف شدهاند؛ این پیکربندیها توسط سایر بلوکها به ارث برده میشوند.
// re2swift $INPUT -o $OUTPUT
func parseUInt32(_ yyinput: UnsafePointer<UInt8>) -> UInt32? {
var yycursor = 0, yymarker = 0
/*!re2c
re2c:yyfill:enable = 0;
"0b" / [01] { return parseBin() }
"0" { return parseOct() }
"" / [1-9] { return parseDec() }
"0x" / [0-9a-fA-F] { return parseHex() }
* { return nil }
*/
func add(_ accum: inout UInt64, _ charOrigin: UnicodeScalar, _ base: UInt64, _ offset: UInt64 = 0) {
let digit = yyinput[yycursor - 1] - UInt8(ascii: charOrigin)
accum = min(accum * base + UInt64(digit) + offset, UInt64(UInt32.max) + 1)
}
func parseBin() -> UInt32? {
var accum: UInt64 = 0
parse: while true {
/*!re2c
[01] { add(&accum, "0", 2); continue parse }
* { return UInt32(exactly: accum) }
*/
}
}
func parseOct() -> UInt32? {
var accum: UInt64 = 0
parse: while true {
/*!re2c
[0-7] { add(&accum, "0", 8); continue parse }
* { return UInt32(exactly: accum) }
*/
}
}
func parseDec() -> UInt32? {
var accum: UInt64 = 0
parse: while true {
/*!re2c
[0-9] { add(&accum, "0", 10); continue parse }
* { return UInt32(exactly: accum) }
*/
}
}
func parseHex() -> UInt32? {
var accum: UInt64 = 0
parse: while true {
/*!re2c
[0-9] { add(&accum, "0", 16); continue parse }
[a-f] { add(&accum, "a", 16, 10); continue parse }
[A-F] { add(&accum, "A", 16, 10); continue parse }
* { return UInt32(exactly: accum) }
*/
}
}
}
assert(parseUInt32("") == nil)
assert(parseUInt32("1234567890") == 1234567890)
assert(parseUInt32("0b1101") == 13)
assert(parseUInt32("0x7Fe") == 2046)
assert(parseUInt32("0644") == 420)
assert(parseUInt32("9999999999") == nil)
شرایط شروع (Start conditions)
شرایط شروع با گزینهٔ --start-conditions فعال میشوند. آنها روشی برای کدگذاری چندین خودکارهٔ (اتوماتای) بههمپیوسته درون یک بلوک re2swift یکسان فراهم میکنند.
هر شرط متناظر با یک خودکارهٔ منفرد است و یک نام یکتا دارد که توسط کاربر مشخص میشود، و همچنین یک شمارهٔ داخلی یکتا که توسط re2swift تعریف میگردد. این شمارهها برای جابهجایی میان شرایط به کار میروند: کد تولیدشده از پیشساختههای YYGETCOND و YYSETCOND برای دریافت شرط کنونی یا تنظیم آن به شمارهٔ دادهشده استفاده میکند. برای تولید شناسههای عددی شرایط، از بلوک conditions، گزینهٔ --header یا پیکربندی re2c:header استفاده کنید. پیکربندی re2c:cond:enumprefix پیشوند شناسههای تولیدشده را مشخص میکند.
در حالت شرط، هر قاعده باید دارای پیشوندی از فهرستی از نامهای شرایط جداشده با کاما درون قلابهای شکسته، یا نویسهٔ عام <*> برای نشان دادن همهٔ شرایط باشد. نحو قواعد بهصورت زیر گسترش مییابد:
- < condition-list > regular-expression code
- قاعدهای که در هر شرط موجود در condition-list ادغام میشود. این قاعده با regular-expression مطابقت مییابد و code مرتبط را اجرا میکند.
- < condition-list > regular-expression => condition code
- قاعدهای که در هر شرط موجود در condition-list ادغام میشود. این قاعده با regular-expression مطابقت مییابد، شرط کنونی را به condition تنظیم میکند و code مرتبط را اجرا میکند.
- < condition-list > regular-expression :=> condition
- قاعدهای که در هر شرط موجود در condition-list ادغام میشود. این قاعده با regular-expression مطابقت مییابد و بلافاصله به condition گذار میکند (هیچ عمل معنایی وجود ندارد).
- < condition-list > !action code
- قاعدهای که code را به مکان تعیینشده توسط action در هر شرط موجود در condition-list پیوند میدهد (برای انواع مختلف کنشها به بخش actions مراجعه کنید).
- <! condition-list > code
- قاعدهای که code را به ابتدای کنشهای معناییِ تمام قواعد برای هر شرط در condition-list اضافه میکند. این نحو منسوخ شده است و در عوض باید از کنش !pre_rule استفاده شود (دقیقاً همان کار را انجام میدهد).
- < > code
- قاعدهای که یک شرط ورودی ویژه با شمارهٔ صفر و نام "0" ایجاد میکند که قبل از پرش به سایر شرایط، code را اجرا میکند. این نحو منسوخ شده است و باید به جای آن از کنش !entry استفاده شود (کنترل دقیقتری فراهم میکند، زیرا کد میتواند بر مبنای هر شرط مشخص شود و میتوان مستقیماً بدون گذر از توزیع شرط، به شروع شرط پرش کرد).
- < > => condition code
- مشابه قاعدهٔ قبلی، با این تفاوت که condition بعدی را تعیین میکند.
- < > :=> condition
- مشابه قاعدهٔ قبلی، با این تفاوت که هیچ کد مرتبطی ندارد و بلافاصله به condition پرش میکند.
کدی که re2swift برای شرایط تولید میکند بستگی به این دارد که re2swift از روش goto/label استفاده میکند یا روش loop/switch برای کدگذاری خودکارهها.
در زبانهایی که دستور goto دارند (مانند C/C++ و Go)، شرایط بهطور طبیعی بهصورت بلوکهایی از کد با پیشوند برچسبهایی به فرم yyc_<cond> پیادهسازی میشوند، که در آن cond نام یک شرط است (پیشوند برچسب را میتوان با re2c:cond:prefix تغییر داد). گذارها بین شرایط با استفاده از goto و برچسبهای شرایط پیادهسازی میشوند. پیش از همهٔ شرایط، re2swift یک switch اولیه روی YYGETSTATE تولید میکند که به وضعیت شروع شرط کنونی پرش میکند. قواعد میانبُر :=> از switch اولیه گذر کرده و مستقیماً به شرط مشخصشده پرش میکنند (میتوان از re2c:cond:goto برای تغییر رفتار پیشفرض استفاده کرد). قواعد دارای کنشهای معنایی بهطور خودکار به شرط بعدی پرش نمیکنند؛ این کار باید توسط کد کنش تعریفشده توسط کاربر انجام شود.
در زبانهایی که goto ندارند (مانند Rust)، re2swift از متغیر yystate برای ذخیرهٔ شمارههای شرایط استفادهٔ مجدد میکند. هر شرط یک شناسهٔ عددی برابر با شمارهٔ وضعیت آغازین خود دریافت میکند، و یک switch بین شرایط هیچ تفاوتی با یک switch بین وضعیتهای DFA یک شرط منفرد ندارد. نیازی به یک switch شرط اولیهٔ مجزا نیست. (از آنجا که رویکرد مشابهی برای پیادهسازی وضعیتهای قابل ذخیره استفاده میشود، در صورتی که هم از وضعیتهای قابل ذخیره و هم از شرایط استفاده شود، YYGETCOND/YYSETCOND زائد خواهند بود).
برنامهٔ زیر از شرایط شروع برای تجزیهٔ اعداد دودویی، هشتهشتی، دهدهی و شانزدهشانزدهی استفاده میکند. یک بلوک منفرد وجود دارد که در آن هر مبنا شرط مخصوص به خود را دارد، و شرط اولیه به همهٔ آنها متصل است. متغیر تعریفشده توسط کاربر cond شمارهٔ شرط کنونی را ذخیره میکند؛ این متغیر با شمارهٔ شرط اولیهای که توسط بلوک conditions تولید شده است مقداردهی اولیه میشود.
// re2swift $INPUT -o $OUTPUT -c
/*!conditions:re2c*/
func parseUInt32(_ yyinput: UnsafePointer<UInt8>) -> UInt32? {
var yycursor = 0, yymarker = 0, yycond = yycinit
var accum: UInt64 = 0
loop: while true {
/*!re2c
re2c:yyfill:enable = 0;
<init> '0b' / [01] :=> bin
<init> "0" :=> oct
<init> "" / [1-9] :=> dec
<init> '0x' / [0-9a-fA-F] :=> hex
<bin> [01] { add("0", 2); continue loop }
<oct> [0-7] { add("0", 8); continue loop }
<dec> [0-9] { add("0", 10); continue loop }
<hex> [0-9] { add("0", 16); continue loop }
<hex> [a-f] { add("a", 16, 10); continue loop }
<hex> [A-F] { add("A", 16, 10); continue loop }
<bin, oct, dec, hex> "\x00" { return UInt32(exactly: accum) }
<*> * { return nil }
*/
}
func add(_ charOrigin: UnicodeScalar, _ base: UInt64, _ offset: UInt64 = 0) {
let digit = yyinput[yycursor - 1] - UInt8(ascii: charOrigin)
accum = min(accum * base + UInt64(digit) + offset, UInt64(UInt32.max) + 1)
}
}
assert(parseUInt32("") == nil)
assert(parseUInt32("1234567890") == 1234567890)
assert(parseUInt32("0b1101") == 13)
assert(parseUInt32("0x7Fe") == 2046)
assert(parseUInt32("0644") == 420)
assert(parseUInt32("9999999999") == nil)
با گزینهٔ --storable-state، ابزار re2swift تحلیلگر لغوی (lexer) تولید میکند که میتواند وضعیت فعلی خود را ذخیره کرده، به فراخواننده بازگردد، و بعداً عملیات را دقیقاً از همانجایی که رها کرده بود از سر بگیرد. حالت پیشفرضِ کارکرد در re2swift مدل "pull" (کِششی) است، که در آن تحلیلگر لغوی هر زمان که به ورودی بیشتری نیاز داشته باشد آن را «میکِشد». این ممکن است در مواردی که ورودی به صورت تکهتکه در دسترس قرار میگیرد غیرقابلقبول باشد (به عنوان مثال، اگر تحلیلگر لغوی توسط تجزیهکننده (parser) فراخوانی شود، یا اگر برنامهٔ تحلیلگر لغوی از طریق پروتکل سوکت با برنامهٔ دیگری ارتباط برقرار کند که باید پیش از ارسال پیام بعدی منتظر پاسخ از تحلیلگر لغوی بماند). ویژگی وضعیت قابل ذخیرهسازی دقیقاً برای چنین مواردی در نظر گرفته شده است: این ویژگی امکان تولید تحلیلگرهای لغوی را فراهم میکند که در یک مدل "push" (رانشی) کار میکنند. هنگامی که تحلیلگر لغوی به ورودی بیشتری نیاز دارد، وضعیت خود را ذخیره کرده و به فراخواننده بازمیگردد. بعداً، زمانی که ورودی بیشتری در دسترس قرار گرفت، فراخواننده تحلیلگر لغوی را دقیقاً از همان نقطهای که متوقف شده بود از سر میگیرد. در مقایسه با مدل "pull"، چند تغییر لازم است:
- تعریف دستورهای اولیهٔ YYSETSTATE() و YYGETSTATE(state).
- تعریف متغیرهای yych، yyaccept (در صورت استفاده) و state به عنوان بخشی از وضعیت پایدار (persistent) تحلیلگر لغوی. متغیر state باید با -1 مقداردهی اولیه شود.
- دستور YYFILL باید به جای تلاش برای تأمین ورودی بیشتر، به برنامهٔ بیرونی بازگردد. کد بازگشتی باید نشان دهد که تحلیلگر لغوی به ورودی بیشتری نیاز دارد.
- برنامهٔ بیرونی باید موقعیتهایی را که تحلیلگر لغوی به ورودی بیشتری نیاز دارد تشخیص داده و واکنش مناسب نشان دهد.
- استفادهٔ اختیاری از بلوک getstate برای تولید سوییچِ YYGETSTATE جدا از تحلیلگر لغوی اصلی. این مورد فقط برای زبانهایی کار میکند که دارای goto هستند (نه در حالت --loop-switch).
- استفاده از re2c:eof و روش sentinel with bounds checks برای مدیریت پایان ورودی. روش مبتنی بر لایهگذاری (padding) ممکن است کار نکند زیرا مشخص نیست چه زمانی باید لایهگذاری اضافه شود: پایان فعلی ورودی ممکن است پایان نهایی ورودی نباشد، و اضافه کردن زودهنگام لایهگذاری ممکن است نشانهٔ لغویِ حریصانهای (greedy lexeme) را که تا حدی خوانده شده قطع کند. علاوه بر این، به دلیل منطق سطحبالای برنامه، دریافت ورودی بیشتر ممکن است به پردازش نشانهٔ لغوی در انتهای بافر بستگی داشته باشد (که از قبل به دلیل شرایط پایان ورودی مسدود شده است).
در اینجا مثالی از یک تحلیلگر لغوی با مدل "push" آورده شده است که خواندن بستهها از یک سوکت را شبیهسازی میکند. تحلیلگر لغوی تا زمان رسیدن به پایان ورودی تکرار میشود و به تابع فراخواننده بازمیگردد. تابع فراخواننده با «ارسال» بستهٔ بعدی ورودی بیشتری فراهم کرده و تحلیل لغوی را از سر میگیرد. این فرایند زمانی که تمام بستهها ارسال شدند، یا زمانی که خطایی رخ دهد، متوقف میشود.
// re2swift $INPUT -o $OUTPUT -f
import Foundation
func log(_ items: Any..., separator: String = " ", terminator: String = "\n") {
#if DEBUG
var stderr = FileHandle.standardError
print(items, separator: separator, terminator: terminator, to: &stderr)
#endif
}
extension FileHandle: TextOutputStream {
public func write(_ string: String) {
self.write(Data(string.utf8))
}
}
struct State {
// Use a small buffer to cover the case when a lexeme doesn't fit,
// in a real world use case use a larger buffer.
static let bufferSize = 10
let file: FileHandle
// Extra '\0' byte on buffer acts as terminator.
var yyinput = ContiguousArray<UInt8>(repeating: 0, count: Self.bufferSize + 1)
var yylimit = Self.bufferSize
var yycursor = Self.bufferSize
var yymarker = Self.bufferSize
var token = Self.bufferSize
var yystate = -1
}
extension State {
mutating func lex(recv: inout Int) -> Status {
var yych: UInt8 = 0
lex: while true {
self.token = self.yycursor
/*!re2c
re2c:api = record;
re2c:eof = 0;
re2c:variable:yyrecord = "self";
re2c:YYFILL = "return .waiting";
packet = [a-z]+[;];
* { return .badPacket }
$ { return .end }
packet {
recv += 1
continue lex
}
*/
}
}
mutating func fill() -> Status {
let used = self.yylimit - self.token
let free = Self.bufferSize - used
// Error: No space. In the real world we can reallocate a larger buffer.
if free < 1 {
return .bigPacket
}
// Shift buffer contents, discarding everything up to the current lexeme.
let shift = self.token
self.yyinput.replaceSubrange(..<used, with: self.yyinput[shift..<self.yylimit])
self.yylimit -= shift
self.yycursor -= shift
self.yymarker -= shift
self.token = 0
// Fill free space at the end of buffer with new data.
do {
if let data = try self.file.read(upToCount: free) {
self.yyinput.replaceSubrange(self.yylimit..<(self.yylimit + data.count), with: data)
self.yylimit += data.count
}
} catch {
fatalError("cannot read from file: \(error.localizedDescription)")
}
self.yyinput[self.yylimit] = 0 // append sentinel
return .ready
}
enum Status {
case end, ready, waiting, badPacket, bigPacket
}
}
func test(_ packets: [StaticString]) -> State.Status {
// Create a "socket" (open the same file for reading and writing).
let fname: String = "pipe"
guard FileManager.default.createFile(atPath: fname, contents: nil),
let fw = FileHandle(forWritingAtPath: fname) else {
fatalError("cannot open '\(fname)'")
}
guard let fr = FileHandle(forReadingAtPath: fname) else {
fatalError("cannot read file '\(fname)'")
}
defer {
// Cleanup: remove input file.
do {
try fw.close()
try fr.close()
try FileManager.default.removeItem(atPath: fname)
} catch {
fatalError("error on file cleanup: \(error.localizedDescription)")
}
}
var state = State(file: fr)
// Main loop. The buffer contains incomplete data which appears packet by
// packet. When the lexer needs more input it saves its internal state and
// returns to the caller which should provide more input and resume lexing.
var send = 0, recv = 0
while true {
switch state.lex(recv: &recv) {
case .end:
log("done: got \(recv)")
assert(recv == send)
return .end
case .waiting:
log("waiting...")
if send < packets.count {
log("sent packet \(send)")
packets[send].withUTF8Buffer {
do {
try fw.write(contentsOf: $0)
} catch {
fatalError("cannot write to \(fname): \(error.localizedDescription)")
}
}
send += 1
}
let status = state.fill()
state.yyinput.withUnsafeBytes {
let buf = $0.bindMemory(to: CChar.self)
log("queue: '\(String(utf8String: buf.baseAddress!) ?? "")'")
}
if status == .bigPacket {
log("error: packet too big")
return .bigPacket
}
assert(status == .ready)
case .badPacket:
log("error: ill-formed packet")
return .badPacket
default:
fatalError("unreachable")
}
}
}
assert(test([]) == .end)
assert(test([ "zero;", "one;", "two;", "three;", "four;" ]) == .end)
assert(test([ "zer0;" ]) == .badPacket)
assert(test([ "looooooooooong;" ]) == .bigPacket)
log("all tests completed successfully")
بلوکهای چندبار مصرف به شکل /*!rules:re2c[:<name>] ... */ یا %{rules[:<name>] ... %} را میتوان به هر تعداد دفعات دوباره استفاده کرد و با سایر بلوکهای re2swift ترکیب نمود. مقدار <name> اختیاری است. یک بلوک قواعد را میتوان در یک بلوک یا دستورالعمل use به کار برد. کد یک بلوک قواعد در هر نقطه از استفاده تولید میشود.
بلوکهای use با /*!use:re2c[:<name>] ... */ یا %{use[:<name>] ... %} تعریف میشوند. مقدار <name> اختیاری است: اگر مشخص نشده باشد، بلوک قواعد مرتبط، جدیدترین بلوک موجود خواهد بود (چه نامگذاری شده و چه بدون نام). یک بلوک use میتواند تعاریف نامگذاریشده، پیکربندیها و قواعد خاص خودش را بیفزاید. یک مورد کاربرد مهم برای بلوکهای use، تحلیلگر لغوی (lexer) است که از چندین کدگذاری ورودی مختلف پشتیبانی میکند: یک بلوک قواعد یکسان، چندین بار همراه با پیکربندیهای مختص هر کدگذاری استفاده میشود (مثال زیر را ببینید).
دستورالعمل استفاده درونبلوکی !use:<name>; میتواند از داخل یک بلوک re2swift به کار رود. این دستورالعمل، بلوک ارجاعشده <name> را با بلوک فعلی ادغام میکند. اگر برخی از قواعد و پیکربندیهای ادغامشده با موارد تعریفشده قبلی همپوشانی داشته باشند، تداخلها به شیوه معمول حل میشوند: نخستین قاعده تقدم دارد، و جدیدترین پیکربندی جایگزین موارد پیشین میشود. یک استثنا، قواعد ویژه *، $ و (در حالت شرط) <!> هستند که برای آنها تعریف محلی بلوک، هرگونه تعریف به ارثرسیده را لغو میکند. دستورالعمل use امکان ترکیب بلوکهای مختلف re2swift را با یکدیگر در یک بلوک فراهم میسازد (مثال زیر را ببینید).
بلوکهای نامگذاریشده و دستورالعمل استفاده درونبلوکی در نسخه 2.2 برنامه re2swift اضافه شدند. از آن نسخه به بعد، بلوکهای چندبار مصرف بهطور پیشفرض مجاز هستند (نیازی به گزینه خاصی نیست). پیش از نسخه 2.2، حالت استفاده مجدد با گزینه -r --reusable فعال میشد. پیش از نسخه 1.2 بلوکهای چندبار مصرف نمیتوانستند با بلوکهای عادی ترکیب شوند.
مثالی از یک دستورالعمل !use
// re2swift $INPUT -o $OUTPUT
// This example shows how to combine reusable re2c blocks: two blocks
// ('colors' and 'fish') are merged into one. The 'salmon' rule occurs
// in both blocks; the 'fish' block takes priority because it is used
// earlier. Default rule * occurs in all three blocks; the local (not
// inherited) definition takes priority.
/*!rules:re2c:colors
* { fatalError("unreachable") }
"red" | "salmon" | "magenta" { return .color }
*/
/*!rules:re2c:fish
* { fatalError("unreachable") }
"haddock" | "salmon" | "eel" { return .fish }
*/
func lex(_ yyinput: UnsafePointer<UInt8>) -> What {
var yycursor = 0, yymarker = 0
/*!re2c
re2c:yyfill:enable = 0;
!use:fish;
!use:colors;
* { return .dunno } // Overrides inherited '*' rules
*/
}
enum What { case color, fish, dunno }
assert(lex("salmon") == .fish)
assert(lex("what?") == .dunno)
مثالی از یک بلوک /*!use:re2c ... */
// re2swift $INPUT -o $OUTPUT --input-encoding utf8
// This example supports multiple input encodings: UTF-8 and UTF-32.
// Both lexers are generated from the same rules block, and the use
// blocks add only encoding-specific configurations.
/*!rules:re2c
re2c:yyfill:enable = 0;
"∀x ∃y" { return true }
* { return false }
*/
func lexUTF8(_ yyinput: [UInt8]) -> Bool {
var yycursor = 0, yymarker = 0
/*!use:re2c
re2c:YYCTYPE = UInt8;
re2c:encoding:utf8 = 1;
*/
}
func lexUTF32(_ yyinput: [UInt32]) -> Bool {
var yycursor = 0, yymarker = 0
/*!use:re2c
re2c:YYCTYPE = UInt32;
re2c:encoding:utf32 = 1;
*/
}
assert(lexUTF8([ 0xe2, 0x88, 0x80, 0x78, 0x20, 0xe2, 0x88, 0x83, 0x79 ])) // UTF-8
assert(lexUTF32([ 0x00002200, 0x00000078, 0x00000020, 0x00002203, 0x00000079 ])) // UTF-32
استخراج زیرتطابق (Submatch extraction)
برنامه re2swift دو گزینه برای استخراج زیرتطابق دارد.
- Tags
- گزینه اول استفاده از برچسبها (tags) مستقل به شکل @stag یا #mtag است، که در آن stag و mtag نامهای اختیاری تعریفشده توسط کاربر هستند. برچسبها با گزینه -T --tags یا پیکربندی re2c:tags = 1 فعال میشوند. برچسبها از لحاظ معنایی نشانگر موقعیت هستند: میتوانند در هر کجای یک عبارت منظم درج شوند و به موقعیت متناظر (یا موقعیتهای چندگانه) در رشته ورودی متصل گردند. برچسبهای S به آخرین موقعیت منطبق متصل میشوند و برچسبهای m به فهرستی از موقعیتها متصل میگردند (ممکن است در زیرعبارتهای تکرار استفاده شوند، جایی که یک موقعیت واحد در عبارت منظم با چندین موقعیت در رشته ورودی متناظر است). تمام برچسبها باید توسط کاربر، یا بهصورت دستی یا با کمک بلوکهای svars و mvars، تعریف شوند. اگر بیش از یک روش برای تطبیق برچسبها با ورودی وجود داشته باشد، ابهام با استفاده از راهبرد ابهامزدایی چپترین حریصانه (leftmost greedy) برطرف میشود.
- Captures
- گزینه دوم استفاده از گروههای گیرانداز (capturing groups) است. آنها با گزینه --captures یا پیکربندی re2c:captures = 1 فعال میشوند. دو گونه برای سیاستهای مختلف رفع ابهام وجود دارد؛ --leftmost-captures (پیشفرض) برای سیاست چپترین حریصانه، و --posix-captures برای سیاست طولانیترین تطابق POSIX است. در این حالت همه زیرعبارتهای داخل پرانتز به عنوان گروههای گیرانداز در نظر گرفته میشوند و میتوان از علامت تعجب برای مشخص کردن گروههای غیرگیرانداز استفاده کرد: (! ... ). با گزینه --invert-captures یا پیکربندی re2c:invert-captures = 1، معنای علامت تعجب معکوس میشود. تعداد گروهها برای قاعده منطبق در متغیر yynmatch ذخیره میشود (کل عبارت منظم، گروه شماره صفر است)، و نتایج زیرتطابق در آرایه yypmatch ذخیره میگردند. هم yynmatch و هم yypmatch باید توسط کاربر تعریف شوند و اندازه yypmatch باید حداقل [yynmatch * 2] باشد. از بلوک maxnmatch برای تعریف YYMAXNMATCH استفاده کنید، ثابتی که برابر با بیشینه مقدار yynmatch در میان همه قواعد است.
- Captvars
- روش دیگر برای استفاده از گروههای گیرانداز، گزینه --captvars یا پیکربندی re2c:captvars = 1 است. تنها تفاوت با --captures در نحوه ذخیرهسازی نتایج زیرتطابق توسط کد تولیدشده است: بهجای yynmatch و yypmatch، برنامه re2swift متغیرهای yytl<k> و yytr<k> را برای k-امین گروه گیرانداز تولید میکند (کاربر باید این متغیرها را با استفاده از یک بلوک svars اعلان کند). گیراندازی با متغیرها از دو سیاست رفع ابهام پشتیبانی میکند: --leftmost-captvars یا re2c:leftmost-captvars = 1 برای سیاست چپترین حریصانه (حالت پیشفرض) و --posix-captvars یا re2c:posix-captvars برای سیاست طولانیترین تطابق POSIX.
در لایههای زیرین، تمام این گزینهها به برچسبها و اتوماتای متناهی معین برچسبدار با پیشنگری (Tagged Deterministic Finite Automata with Lookahead یا TDFA) تبدیل میشوند. ایده اصلی TDFA به حداقل رساندن سربار استخراج زیرتطابق است. در حالت حداقلی، اگر هیچ برچسب یا گروه گیراندازی در یک عبارت منظم وجود نداشته باشد، TDFA صرفاً یک DFA معمولی است. اگر تعداد برچسبها در حد اعتدال باشد، سربار بهندرت محسوس است. اتوماتای TDFA تولیدشده از تعدادی متغیر برچسب استفاده میکند که مستقیماً به برچسبها نگاشت نمیشوند: یک متغیر واحد ممکن است برای برچسبهای متفاوتی به کار رود، و یک برچسب ممکن است به چندین متغیر برای نگهداری تمام مقادیر ممکن خود نیاز داشته باشد. در نهایت، ابهام رفع شده و به ازای هر برچسب تنها یک متغیر نهایی باقی میماند. متغیرهای برچسب باید با استفاده از بلوکهای stags یا mtags تعریف شوند. اگر وضعیت لکسر ذخیره شود، متغیرهای برچسب نیز باید بخشی از آن باشند. آنها همچنین باید توسط YYFILL بهروزرسانی شوند.
برچسبهای S از عملیات زیر پشتیبانی میکنند:
- ذخیره موقعیت ورودی در یک برچسب s: دستور t = YYCURSOR با API اشارهگر C یا عملیات تعریفشده توسط کاربر YYSTAGP(t) با API عمومی
- ذخیره مقدار پیشفرض در یک برچسب s: دستور t = NULL با API اشارهگر C یا عملیات تعریفشده توسط کاربر YYSTAGN(t) با API عمومی
- کپی یک برچسب s به دیگری: t1 = t2
برچسبهای M از عملیات زیر پشتیبانی میکنند:
- الحاق موقعیت ورودی به یک برچسب m: عملیات تعریفشده توسط کاربر YYMTAGP(t) هم با API پیشفرض و هم با API عمومی
- الحاق مقدار پیشفرض به یک برچسب m: عملیات تعریفشده توسط کاربر YYMTAGN(t) هم با API پیشفرض و هم با API عمومی
- کپی یک برچسب m به دیگری: t1 = t2
برچسبهای S را میتوان به صورت مقادیر اسکالر (اشارهگرها یا آفستها) پیادهسازی کرد. برچسبهای M به نمایش پیچیدهتری نیاز دارند، چرا که باید دنبالهای از مقادیر برچسب را ذخیره کنند. سادهترین و ناکارآمدترین نمایش یک برچسب m، یک فهرست (آرایه، بردار) از مقادیر برچسب است؛ نمایش کارآمدتر، ذخیره تمام برچسبهای m در یک درخت پیشوندی است که به صورت آرایهای از گرههای (v, p) نمایش داده میشود، که در آن v مقدار برچسب و p اشارهگری به گره والد است.
در اینجا یک مثال ساده از استفاده از برچسبهای s برای تجزیه نسخهبندی معنایی متشکل از سه مؤلفه عددی آورده شده است: major، minor، patch (مورد آخر اختیاری است). برای یک مثال پیچیدهتر که از YYFILL استفاده میکند، به بخش زیر مراجعه کنید.
// re2swift $INPUT -o $OUTPUT
struct SemVer: Equatable { var major: Int, minor: Int, patch: Int }
func lex(_ yyinput: UnsafePointer<UInt8>) -> SemVer? {
let tagNone = -1
var yycursor = 0, yymarker = 0
// Final tag variables available in semantic action.
/*!svars:re2c format = "var @@ = tagNone\n"; */
// Intermediate tag variables used by the lexer (must be autogenerated).
/*!stags:re2c format = 'var @@ = tagNone\n'; */
/*!re2c
re2c:yyfill:enable = 0;
re2c:tags = 1;
num = [0-9]+;
@t1 num @t2 "." @t3 num @t4 ("." @t5 num)? [\x00] {
let s2n = { (range: Range<Int>) in
range.reduce(0) { accum, idx in
accum * 10 + Int(yyinput[idx] - UInt8(ascii: "0"))
}
}
return SemVer(
major: s2n(t1..<t2),
minor: s2n(t3..<t4),
patch: t5 != tagNone ? s2n(t5..<(yycursor - 1)) : 0)
}
* { return nil }
*/
}
assert(lex("23.34") == SemVer(major: 23, minor: 34, patch: 0))
assert(lex("1.2.999") == SemVer(major: 1, minor: 2, patch: 999))
assert(lex("1.a") == nil)
در اینجا مثال پیچیدهتری از استفاده از برچسبهای s با YYFILL برای تجزیه فایلی با نسخههای معنایی جدا شده با خط جدید آورده شده است. متغیرهای برچسب بخشی از وضعیت لکسر هستند و مانند سایر موقعیتهای ورودی در YYFILL تنظیم میشوند. توجه داشته باشید که این کار برای برچسبهای s ضروری است زیرا مقادیر آنها پس از شیفت دادن محتویات بافر باطل میشوند. ممکن است در یک پیادهسازی سفارشی که در آن متغیرهای برچسب آفستهای نسبت به ابتدای رشته ورودی را بهجای بافر ذخیره میکنند (که ممکن است برای برچسبهای m صادق باشد)، نیازی به این کار نباشد.
// re2swift $INPUT -o $OUTPUT --api generic --tags
import Foundation
struct SemVer: Equatable { var major: Int, minor: Int, patch: Int }
struct Input {
static let bufferSize = 4095
static let tagNone = -1
var yyinput = ContiguousArray<UInt8>(repeating: 0, count: Self.bufferSize + 1)
var yylimit = Self.bufferSize
var yycursor = Self.bufferSize
var yymarker = Self.bufferSize
var token = Self.bufferSize
// Intermediate tag variables must be part of the lexer state passed to YYFILL.
// They don't correspond to tags and should be autogenerated by re2c.
/*!stags:re2c format = " var @@ = Self.tagNone\n"; */
var eof = false
let file: FileHandle
}
extension Input {
mutating func lex() -> [SemVer]? {
var semVers = [SemVer]()
semVers.reserveCapacity(Self.bufferSize)
// Final tag variables available in semantic action.
/*!svars:re2c format = " var @@: Int\n"; */
parse: while true {
self.token = self.yycursor
/*!re2c
re2c:api = record;
re2c:eof = 0;
re2c:tags = 1;
re2c:yyrecord = "self";
re2c:YYFILL = "self.fill() == .ok";
num = [0-9]+;
num @t1 "." @t2 num @t3 ("." @t4 num)? [\n] {
semVers.append(SemVer(
major: self.s2n(self.token..<t1),
minor: self.s2n(t2..<t3),
patch: t4 != Self.tagNone ? self.s2n(t4..<(self.yycursor - 1)) : 0
))
continue parse
}
$ { return semVers }
* { return nil }
*/
}
}
func s2n(_ range: Range<Int>) -> Int {
self.yyinput[range].reduce(0) { accum, digit in
accum * 10 + Int(digit - UInt8(ascii: "0"))
}
}
mutating func fill() -> FillStatus {
guard !self.eof else {
return .eof
}
let shift = self.token
let used = self.yylimit - self.token
let free = Self.bufferSize - used
// Error: Lexeme too long. In the real world we could reallocate a larger buffer.
guard self.token >= 1 else {
return .longLexeme
}
// Shift buffer contents, discarding everything up to the current token.
self.yyinput.replaceSubrange(..<used, with: self.yyinput[shift..<self.yylimit])
self.yylimit -= shift
self.yycursor -= shift
self.yymarker &-= shift // May underflow is marker is unused
self.token = 0
// Tag variables need to be shifted like other input positions. The check
// for `tagNone` is only needed if some tags are nested inside of alternative
// or repetition, so that they can have `tagNone` value.
/*!stags:re2c format = " if self.@@ != Self.tagNone { self.@@ -= shift }\n"; */
// Fill free space at the end of the buffer with new data from file.
do {
if let data = try self.file.read(upToCount: free) {
self.yyinput.replaceSubrange(self.yylimit..<(self.yylimit + data.count), with: data)
self.yylimit += data.count
}
} catch {
fatalError("cannot read from file: \(error.localizedDescription)")
}
self.yyinput[self.yylimit] = 0 // append sentinel
self.eof = self.yylimit < Self.bufferSize
return .ok
}
enum FillStatus {
case ok, eof, longLexeme
}
}
extension SemVer: CustomStringConvertible {
var description: String { "\(self.major).\(self.minor).\(self.patch)" }
}
let fileName = "input"
let semVer = SemVer(major: 1, minor: 22, patch: 333)
let expect = [SemVer](repeating: semVer, count: Input.bufferSize)
// Prepare input file (make sure it exceeds buffer size).
guard FileManager.default.createFile(
atPath: fileName,
contents: Data(String(repeating: "\(semVer)\n", count: Input.bufferSize).utf8)
) else {
fatalError("failed to write file \"\(fileName)\"")
}
// Reopen input file for reading.
guard let file = FileHandle(forReadingAtPath: fileName) else {
throw NSError(domain: NSCocoaErrorDomain, code: CocoaError.fileReadNoSuchFile.rawValue)
}
// Initialize lexer state. Buffer is set to zero, triggering YYFILL.
var `in` = Input(file: file)
// Run the lexer and check the results.
guard let actual = `in`.lex() else {
fatalError("parser error")
}
assert(actual == expect)
// Cleanup: remove input file.
try file.close()
try FileManager.default.removeItem(atPath: fileName)
در اینجا مثالی از استفاده از گروههای گیرانداز برای تجزیه نسخههای معنایی آورده شده است.
// re2swift $INPUT -o $OUTPUT
struct SemVer: Equatable { var major: Int, minor: Int, patch: Int }
func lex(_ yyinput: UnsafePointer<UInt8>) -> SemVer? {
let tagNone = -1
var yycursor = 0, yymarker = 0
// Final tag variables available in semantic action.
/*!svars:re2c format = " var @@: Int\n"; */
// Intermediate tag variables used by the lexer (must be autogenerated).
/*!stags:re2c format = " var @@ = tagNone\n"; */
/*!re2c
re2c:yyfill:enable = 0;
re2c:captvars = 1;
num = [0-9]+;
(num) "." (num) ("." num)? [\x00] {
let _ = yytl0; _ = yytr0 // Some variables are unused.
return SemVer(
major: s2n(yytl1..<yytr1),
minor: s2n(yytl2..<yytr2),
patch: yytl3 != tagNone ? s2n((yytl3 + 1)..<yytr3) : 0)
}
* { return nil }
*/
func s2n(_ range: Range<Int>) -> Int {
range.reduce(0) { accum, idx in
accum * 10 + Int(yyinput[idx] - UInt8(ascii: "0"))
}
}
}
assert(lex("23.34") == SemVer(major: 23, minor: 34, patch: 0))
assert(lex("1.2.999") == SemVer(major: 1, minor: 2, patch: 999))
assert(lex("1.a") == nil)
در اینجا مثالی از بهکارگیری برچسبهای m برای تجزیه یک نسخه با تعداد متغیری از مؤلفهها آورده شده است. متغیرهای برچسب در یک ترای (trie) ذخیره میشوند.
// re2swift $INPUT -o $OUTPUT
import Foundation
// Unbounded number of version components.
struct Ver: Equatable { let components: [Int] }
func parse(_ yyinput: UnsafePointer<UInt8>) -> Ver? {
let tagNone = -1
var yycursor = 0, yymarker = 0
var mTagPool = [MTag]()
// Final tag variables available in semantic action.
/*!svars:re2c format = " let @@: Int\n"; */
/*!mvars:re2c format = " let @@: MTag?\n"; */
// Intermediate tag variables used by the lexer (must be autogenerated).
/*!stags:re2c format = " var @@ = tagNone\n"; */
/*!mtags:re2c format = " var @@: MTag? = nil\n"; */
/*!re2c
re2c:tags = 1;
re2c:yyfill:enable = 0;
re2c:YYSTAGP = "@@ = yycursor";
re2c:YYSTAGN = "@@ = tagNone";
re2c:YYMTAGP = "@@ = add(tag: @@, value: yycursor)";
re2c:YYMTAGN = "@@ = add(tag: @@, value: tagNone)";
num = [0-9]+;
@t1 num @t2 ("." #t3 num #t4)* [\x00] {
var components = [s2n(t1..<t2)]
unfold(&components, t3, t4)
return Ver(components: components)
}
* { return nil }
*/
// Recursively unwind tag histories and collect version components.
func unfold(_ ver: inout [Int], _ x: MTag?, _ y: MTag?) {
if x == nil && y == nil {
// Reached the root of the m-tag tree, stop recursion.
return
}
// Unwind history further.
unfold(&ver, x?.prev, y?.prev)
// Get tag values, tag histories must have equal length.
guard let ex = x?.elem, let ey = y?.elem else {
fatalError("mismatched history length")
}
guard ex != tagNone && ey != tagNone else {
// Both tags are empty, this corresponds to zero repetitions.
assert(ex == ey)
return
}
// Both tags are valid indices, extract component.
ver.append(s2n(ex..<ey))
}
// Pre-parsed string to number.
func s2n(_ range: Range<Int>) -> Int {
range.reduce(0) { accum, idx in
accum * 10 + Int(yyinput[idx] - UInt8(ascii: "0"))
}
}
// Append a single value to an m-tag history.
func add(tag prevTag: MTag?, value: Int) -> MTag {
let tag = MTag(prev: prevTag, element: value)
mTagPool.append(tag)
return tag
}
}
// An m-tag tree is a way to store histories with an O(1) copy operation.
// Histories naturally form a tree, as they have common start and fork at some
// point. The tree is stored as an array of pairs (tag value, link to parent).
// An m-tag is represented with a single link in the tree (array index).
class MTag {
weak var prev: MTag? // Link of predecessor node or root (nil)
var elem: Int // Tag value
init(prev: MTag?, element: Int) {
self.prev = prev
self.elem = element
}
}
assert(parse("1") == Ver(components: [1]))
assert(parse("1.2.3.4.5.6.7") == Ver(components: [1, 2, 3, 4, 5, 6, 7]))
assert(parse("1.2.") == nil)
درک تفاوت میان نقطهکدها (code points) و واحدهای کد (code units) ضروری است. یک نقطهکد، یک شناسه عددی برای یک نماد است. یک واحد کد، کوچکترین واحد ذخیرهسازی در متن کدگذاریشده است. یک نقطهکد منفرد ممکن است با یک یا چند واحد کد نمایش داده شود. در یک کدگذاری با طول ثابت (fixed-length)، تمام نقطهکدها با تعداد یکسانی از واحدهای کد نمایش داده میشوند. در یک کدگذاری با طول متغیر (variable-length)، نقطهکدها ممکن است با تعداد متفاوتی از واحدهای کد نمایش یابند. توجه داشته باشید که قاعده «هر چیز» ("any") یعنی [^] با هر نقطهکدی مطابقت دارد، اما لزوماً با هر واحد کدی مطابقت ندارد (تنها راه برای تطبیق با هر واحد کد بدون در نظر گرفتن نوع کدگذاری، قاعده پیشفرض * است). تحلیلگر لغوی (lexer) تولیدشده با جریانی از واحدهای کد کار میکند: yych یک واحد کد را ذخیره میکند و YYCTYPE نوع واحد کد است. از سوی دیگر، عبارات باقاعده بر حسب نقطهکدها مشخص میشوند. هنگامی که re2swift عبارات باقاعده را به اتوماتا کامپایل میکند، نقطهکدها را به واحدهای کد ترجمه مینماید. این تبدیل عموماً یک نگاشت ساده نیست: در کدگذاریهای با طول متغیر، یک محدوده نقطهکد منفرد ممکن است به یک گراف پیچیده از واحدهای کد تبدیل شود. کدگذاریهای زیر پشتیبانی میشوند:
- ASCII (بهطور پیشفرض فعال است). یک کدگذاری با طول ثابت با فضای کد [0-255] و نقطهکدها و واحدهای کد ۱ بایتی است.
- EBCDIC (با --ebcdic یا re2c:encoding:ebcdic فعال میشود). یک کدگذاری با طول ثابت با فضای کد [0-255] و نقطهکدها و واحدهای کد ۱ بایتی است.
- UCS2 (با --ucs2 یا re2c:encoding:ucs2 فعال میشود). یک کدگذاری با طول ثابت با فضای کد [0-0xFFFF] و نقطهکدها و واحدهای کد ۲ بایتی است.
- UTF8 (با --utf8 یا re2c:encoding:utf8 فعال میشود). یک کدگذاری یونیکد با طول متغیر است. اندازه هر واحد کد ۱ بایت است. نقطهکدها با ۱ تا ۴ واحد کد نمایش داده میشوند.
- UTF16 (با --utf16 یا re2c:encoding:utf16 فعال میشود). یک کدگذاری یونیکد با طول متغیر است. اندازه هر واحد کد ۲ بایت است. نقطهکدها با ۱ تا ۲ واحد کد نمایش داده میشوند.
- UTF32 (با --utf32 یا re2c:encoding:utf32 فعال میشود). یک کدگذاری یونیکد با طول ثابت با فضای کد [0-0x10FFFF] و نقطهکدها و واحدهای کد ۴ بایتی است.
فایلهای include شامل include/unicode_blocks.re، include/unicode_categories.re و include/unicode_properties.re تعاریف re2swift را برای بلوکها، دستهبندیها و ویژگیهای شرحدادهشده در استاندارد Unicode فراهم میکنند.
گزینه --input-encoding کدگذاری فایل مبدأ را مشخص میکند که میتواند برای فعالسازی لیترالهای یونیکد در عبارات باقاعده به کار رود. برای مثال --input-encoding utf8 به re2swift اعلام میکند که فایل مبدأ با فرمت UTF8 است (با --utf8 که کدگذاری متن ورودی را تعیین میکند تفاوت دارد). گزینه --encoding-policy روشی را تعیین میکند که re2swift با جانشینهای یونیکد (نقطهکدهای موجود در محدوده [0xD800-0xDFFF]) رفتار میکند.
در زیر نمونهای از یک تحلیلگر لغوی برای شناسههای یونیکد با کدگذاری UTF8 آورده شده است.
// re2swift $INPUT -o $OUTPUT -8 -i
/*!include:re2c "unicode_categories.re" */
func lex(_ yyinput: UnsafePointer<UInt8>) -> Bool {
var yycursor = 0, yymarker = 0
/*!re2c
re2c:yyfill:enable = 0;
// Simplified "Unicode Identifier and Pattern Syntax"
// (see https://unicode.org/reports/tr31)
id_start = L | Nl | [$_];
id_continue = id_start | Mn | Mc | Nd | Pc | [\u200D\u05F3];
identifier = id_start id_continue*;
identifier { return true }
* { return false }
*/
}
assert(lex("_Ыдентификатор"))
assert(lex("識別子"))
assert(!lex("+"))
فایلهای ضمیمه (Include files)
برنامه re2swift اجازه میدهد تا فایلهای دیگر را با استفاده از بلوکی به قالب /*!include:re2c FILE */ یا %{include FILE %}، یا یک دستورالعمل درونبلوکیِ !include FILE ; ضمیمه کنید، که در آن FILE مسیر فایلی است که باید اضافه شود. re2swift فایلهای include را در دایرکتوری فایلی که شامل این دستور است و همچنین در مکانهای include که با گزینه -I مشخص میشوند جستجو میکند. بلوکها/دستورالعملهای include در re2swift مشابه #include در C/C++ عمل میکنند: محتوای FILE عیناً در جای بلوک/دستورالعمل جایگذاری میشود. فایلهای include ممکن است خودشان نیز شامل موارد include دیگری باشند. برای ردگیری وابستگیهای ساخت فایل خروجی به فایلهای include، از گزینه --depfile استفاده کنید. برنامه re2swift تعدادی فایل include از پیش تعریفشده ارائه میدهد که در زیردایرکتوری include/ پروژه یافت میشوند. این فایلها حاوی تعاریفی هستند که ممکن است برای پروژههای دیگر مفید باشند (مانند دستهبندیهای Unicode) و ساختاری شبیه به یک کتابخانه استاندارد برای re2swift تشکیل میدهند. در زیر مثالی از نحوه استفاده از فایلهای include آورده شده است.
فایل ضمیمه ۱ (definitions.swift)
enum Result { case ok, fail }
/*!re2c
number = [1-9][0-9]*;
*/
// floating-point numbers
frac = [0-9]* "." [0-9]+ | [0-9]+ ".";
exp = 'e' [+-]? [0-9]+;
float = frac exp? | [0-9]+ exp;
float { return .ok }
فایل ورودی (Input file)
// re2swift $INPUT -o $OUTPUT -i
/*!include:re2c "definitions.swift" */
func lex(_ yyinput: UnsafePointer<UInt8>) -> Result {
var yycursor = 0, yymarker = 0
/*!re2c
re2c:yyfill:enable = 0;
* { return .fail }
number { return .ok }
!include "extra_rules.re.inc";
*/
}
assert(lex("123") == .ok)
assert(lex("123.4567") == .ok)
فایلهای سرآیند (Header files)
ابزار re2swift به شما اجازه میدهد با استفاده از گزینهٔ --header یا پیکربندی re2c:header و جفتبلوکهایی به شکل /*!header:re2c:on*/ و /*!header:re2c:off*/، یا %{header:on%} و %{header:off%}، از روی فایل ورودی .re یک فایل سرآیند تولید کنید. بلوک نخست، آغاز فایل سرآیند و بلوک دوم، پایان آن را مشخص میکند. تمامی محتوای میان این بلوکها توسط re2swift پردازش شده و کد تولیدشده در فایلی که با گزینهٔ --header یا پیکربندی re2c:header مشخص شده نوشته میشود (یا در صورت عدم استفاده از گزینه یا پیکربندی، در stdout نوشته خواهد شد). فایل سرآیند تولیدشدهٔ خودکار ممکن است در مواردی نیاز باشد که re2swift برای تولید تعاریفی به کار میرود که باید از سایر واحدهای ترجمه (translation units) قابل مشاهده باشند.
در ادامه مثالی از تولید یک فایل سرآیند آورده شده است که شامل تعریف وضعیت لکسر به همراه متغیرهای برچسب (tag variables) است (تعداد متغیرها به گرامر منظم بستگی دارد و برای برنامهنویس مشخص نیست).
فایل ورودی (Input file)
// re2swift $INPUT -o $OUTPUT -i --header lexer/state.swift
/*!header:re2c:on*/
struct LexerState {
var yyinput: UnsafePointer<UInt8>
var yycursor: Int
/*!stags:re2c format = "var @@ = -1"; */
init(str: UnsafePointer<UInt8>, cur: Int = 0) {
self.yyinput = str
self.yycursor = cur
}
}
/*!header:re2c:off*/
extension LexerState {
mutating func lex() -> Int {
let t: Int
/*!re2c
re2c:header = "lexer/state.swift";
re2c:api = record;
re2c:tags = 1;
re2c:yyfill:enable = 0;
re2c:yyrecord = "self";
[a]* @t [b]* { return t }
*/
}
}
@main struct Program {
static func main() {
let str: StaticString = "ab"
var state = LexerState(str: str.utf8Start)
assert(state.lex() == 1)
}
}
فایل سرآیند (Header file)
/* Generated by re2swift */
struct LexerState {
var yyinput: UnsafePointer<UInt8>
var yycursor: Int
var yyt1 = -1
init(str: UnsafePointer<UInt8>, cur: Int = 0) {
self.yyinput = str
self.yycursor = cur
}
}
با گزینهٔ -S, --skeleton، ابزار re2swift تمام کدهای غیر re2swift را نادیده میگیرد و یک برنامهٔ C خودکفا تولید میکند که میتواند در ادامه کامپایل و اجرا شود. این برنامه از کد تحلیلگر لغوی (lexer) و دادههای ورودی تشکیل شده است. به ازای هر DFA ساختهشده (بلوک یا شرط)، re2swift یک تحلیلگر لغوی مستقل و دو فایل تولید میکند: یک فایل .input با رشتههای مشتقشده از DFA و یک فایل .keys با نتایج تطابق مورد انتظار. برنامه هر تحلیلگر لغوی را روی فایل .input متناظر اجرا کرده و نتایج را با موارد مورد انتظار مقایسه میکند. برنامههای اسکلتی به چند دلیل بسیار مفید هستند:
- آنها میتوانند درستی بهینهسازیهای گوناگون re2swift را بررسی کنند (دادهها در مراحل اولیهٔ فرایند، پیش از اعمال هرگونه تبدیل روی DFA تولید میشوند).
- تولید مجموعهای از دادههای ورودی با پوشش مناسب میتواند هم برای آزمایش و هم برای ارزیابی کارایی (benchmarking) مفید باشد.
- تولید برنامههای اجرایی خودکفا به شخص اجازه میدهد موارد آزمون کمینهشده (minimized test cases) به دست آورد (کد اصلی ممکن است بزرگ باشد یا وابستگیهای زیادی داشته باشد).
دشواریِ تولید دادههای ورودی در این است که بهجز در سادهترین حالتها، تعداد رشتههای ورودیِ ممکن بیش از حد زیاد است (حتی اگر طول رشته محدود باشد). re2swift این دشواری را با تولید رشتههای کافی برای پوشش تقریباً تمامی گذارهای DFA حل میکند. این ابزار از الگوریتم زیر استفاده میکند: ابتدا اسکلتی از DFA را میسازد. برای کدگذاریهایی با اندازه واحد کد 1-بایتی (مانند ASCII، UTF-8 و EBCDIC) اسکلت صرفاً یک رونوشت دقیق از DFA اصلی است. برای کدگذاریهایی با واحدهای کد چندبایتی، اسکلت رونوشتی از DFA با حذف برخی گذارها است: بدین صورت که re2swift حداکثر ۲۵۶ واحد کد را برای هر بازهٔ پیوسته و مجزا که با یک گذار DFA مطابقت دارد برمیگزیند. مقادیر انتخابشده بهطور یکنواخت توزیع شدهاند و شامل کرانهای بازه نیز میشوند. re2swift بهجای تلاش برای پوشش تمام مسیرهای ممکن در اسکلت (که کاری نشدنی است)، مسیرهای کافی برای پوشش همهٔ گذارهای اسکلت تولید میکند تا بدین ترتیب پرشهای شرطی متناظر در تحلیلگر لغوی فعال شوند. پیادهسازی این الگوریتم به حدود 1 گیگابایت گذار محدود شده است و مقدار ثابتی حافظه مصرف میکند (re2swift بهمحض تولید دادهها، آنها را در فایل مینویسد).
مصورسازی و اشکالزدایی (Visualization and debug)
با گزینهٔ -D, --emit-dot، ابزار re2swift هیچ کدی تولید نمیکند؛ در عوض، DFA تولیدشده را در قالب DOT ذخیره (dump) میکند. میتوان این خروجی را با استفاده از Graphviz یا کتابخانهای دیگر به تصویری از DFA تبدیل کرد. توجه داشته باشید که این گزینه، DFA نهایی را پس از گذراندن شماری از بهینهسازیها و تبدیلها نشان میدهد. مراحل اولیه را میتوان با گزینههای اشکالزدایی گوناگون مانند --dump-nfa، --dump-dfa-raw و غیره ذخیره کرد (فهرست کامل گزینهها را ببینید).
همچنین ببینید (SEE ALSO)
میتوانید اطلاعات بیشتری دربارهٔ re2c در وبگاه رسمی بیابید: http://re2c.org. برنامههای مشابه عبارتند از flex(1)، lex(1)، quex(http://quex.sourceforge.net).
نویسندگان (AUTHORS)
re2swift در ابتدا توسط Peter Bumbulis (peter@csg.uwaterloo.ca) در سال ۱۹۹۳ نوشته شد. Marcus Boerger و Dan Nuffer چندین سال وقت صرف کردند تا ایدهٔ اصلی را به یک تولیدکنندهٔ کد آمادهٔ استفاده در محیط عملیاتی تبدیل کنند. از آن زمان، این پروژه توسط داوطلبان متعددی نگهداری و توسعه داده شده است، بهویژه: Brian Young (bayoung@acm.org)، Marcus Boerger، Dan Nuffer (nuffer@users.sourceforge.net)، Ulya Trofimovich (skvadrik@gmail.com)، Serghei Iakovlev، Sergei Trofimovich، Petr Skocik، ligfx raekye و PolarGoose.