| RE2RUST(1) | RE2RUST(1) |
نام (NAME)
re2rust - ساخت تحلیلگرهای لغوی سریع برای Rust
خلاصه دستور (SYNOPSIS)
re2rust [ OPTIONS ] [ WARNINGS ] INPUT
ورودی میتواند یک فایل یا - برای stdin باشد.
مقدمه (INTRODUCTION)
ابزار re2rust بهعنوان یک پیشپردازنده عمل میکند. این ابزار فایل ورودی را (که معمولاً برنامهای به زبان Rust است، اما میتواند هر چیز دیگری باشد) میخواند و به دنبال بلوکهایی از کد میگردد که در نشانگرهای آغاز/پایان با ساختار ویژه محصور شدهاند. متنهای خارج از این بلوکها عیناً به فایل خروجی منتقل میشوند. محتویات این بلوکها توسط re2rust پردازش میشود؛ re2rust آنها را به کد Rust ترجمه کرده و کد تولیدشده را به جای آن بلوک قرار میدهد.
در ادامه مثالی از یک برنامه کوچک آورده شده است که بررسی میکند آیا یک رشته دادهشده حاوی یک عدد دهدهی (decimal) است یا خیر:
// re2rust $INPUT -o $OUTPUT --no-unsafe --api simple
fn lex(yyinput: &[u8]) -> bool {
let mut yycursor = 0;
/*!re2c
re2c:YYCTYPE = u8;
re2c:yyfill:enable = 0;
[1-9][0-9]* { return true; }
* { return false; }
*/
}
fn main() {
assert!(lex(b"1234\0"));
}
در خروجی، re2rust بلوک میانی را با کد تولیدشده جایگزین کرده است:
/* Generated by re2rust */
// re2rust $INPUT -o $OUTPUT --no-unsafe --api simple
fn lex(yyinput: &[u8]) -> bool {
let mut yycursor = 0;
{
#[allow(unused_assignments)]
let mut yych : u8 = 0;
let mut yystate : usize = 0;
'yyl: loop {
match yystate {
0 => {
yych = yyinput[yycursor];
yycursor += 1;
match yych {
0x31 ..= 0x39 => {
yystate = 2;
continue 'yyl;
}
_ => {
yystate = 1;
continue 'yyl;
}
}
}
1 => { return false; },
2 => {
yych = yyinput[yycursor];
match yych {
0x30 ..= 0x39 => {
yycursor += 1;
yystate = 2;
continue 'yyl;
}
_ => {
yystate = 3;
continue 'yyl;
}
}
}
3 => { return true; },
_ => panic!("internal lexer error"),
}
}
}
}
fn main() {
assert!(lex(b"1234\0"));
}
یک برنامه re2rust از دنبالهای از بلوکها آمیخته با کد در زبان مقصد تشکیل شده است. یک بلوک میتواند شامل تعاریف، پیکربندیها، قواعد، کنشها و دستورالعملها به هر ترتیبی باشد:
- name = regular-expression ;
- یک تعریف، name را به regular-expression مقید میکند. نامها میتوانند شامل نویسههای الفبایی-عددی و زیرخط باشند. بخش عبارات باقاعده مروری کلی بر نحو عبارات باقاعده در re2rust ارائه میدهد. پس از تعریف، name میتواند در سایر عبارات باقاعده و قواعد استفاده شود. بازگشت در تعاریف نامگذاریشده مجاز نیست و هر نام باید پیش از استفاده تعریف شده باشد. یک بلوک، تعاریف نامگذاریشده را از حوزه سراسری به ارث میبرد. تعریف مجدد نامی که در حوزه فعلی وجود دارد، خطا است.
- configuration = value ;
- یک پیکربندی امکان تغییر رفتار re2rust و سفارشیسازی کد تولیدشده را فراهم میسازد. برای فهرست کاملی از پیکربندیهای پشتیبانیشده توسط re2rust بخش پیکربندیها را ببینید. بسته به هر پیکربندی خاص، value میتواند یک کلمه کلیدی، یک عدد صحیح غیرمنفی یا یک رشته تکخطی باشد که باید درون علامتهای نقلقول تکی یا دوتایی قرار گیرد، مگر اینکه فقط از نویسههای الفبایی-عددی تشکیل شده باشد. یک بلوک، پیکربندیها را از حوزه سراسری به ارث میبرد و میتواند آنها را بازتعریف کرده یا موارد جدیدی اضافه کند. پیکربندیهای تعریفشده درون یک بلوک، بر کل بلوک تأثیر میگذارند، حتی اگر در انتهای آن قرار گرفته باشند.
- regular-expression code
- یک قاعده، regular-expression را به کنش معنایی آن مقید میکند (یک بلوک کد درون آکوالاد، یا یک بلوک کد که با := آغاز شده و به یک خط جدید ختم میشود که پس از آن هر نویسه غیرفضایخالی بیاید). اگر regular-expression تطبیق یابد، code مربوطه اجرا میشود. اگر چندین قاعده مطابقت داشته باشند، طولانیترین تطبیق اولویت دارد. اگر چندین قاعده با یک رشته تطابق داشته باشند، اولین قاعده اولویت دارد. دو قاعده ویژه وجود دارد: قاعده پیشفرض * و قاعده پایان ورودی $. قاعده پیشفرض همواره باید تعریف شود، صرفنظر از جایگاهش در بلوک کمترین اولویت را دارد و با هر واحد کد تطبیق مییابد (نه لزوماً یک نویسه معتبر، بخش پشتیبانی از کدگذاری را ببینید). قاعده پایان ورودی باید در صورتی تعریف شود که روش متناظر برای مدیریت پایان ورودی استفاده شده باشد. با شرایط شروع، قواعد نحو پیچیدهتری خواهند داشت.
- !action code
- یک کنش، یک بلوک code تعریفشده توسط کاربر را به مکانی خاص در ماشین حالت متناهی تولیدشده مقید میسازد (همانگونه که کنشهای معنایی کد را به حالتهای پایانی پیوند میدهند). برای فهرست کاملی از کنشهای از پیشتعریفشده، بخش کنشها را ببینید.
- !directive ;
- یک دستورالعمل یکی از عبارتهای ویژه از پیشتعریفشده است. هر دستورالعمل هدف منحصربهفردی دارد. برای جزئیات بخش دستورالعملها را ببینید.
بلوکها (Blocks)
نشانگرهای آغاز و پایان بلوک یا /*!re2c و */ هستند، یا %{ و %} (از هر دو سبک پشتیبانی میشود). از نسخه 2.2 بلوکها میتوانند نامهای اختیاری داشته باشند که امکان ارجاع به آنها را در سایر بلوکها فراهم میکند. انواع مختلفی از بلوکها وجود دارند:
- /*!re2c[:<name>] ... */ یا %{[:<name>] ... %}
- یک بلوک سراسری شامل تعاریف، پیکربندیها، قواعد و دستورالعملها است. re2rust عبارات باقاعده مرتبط با هر قاعده را در یک اتوماتون متناهی قطعی کامپایل کرده، آن را به شکل پرشهای شرطی در زبان مقصد کدگذاری میکند و بلوک را با کد تولیدشده جایگزین میسازد. نامها و پیکربندیهای تعریفشده در یک بلوک سراسری به حوزه سراسری اضافه شده و برای بلوکهای پس از آن قابل مشاهده میشوند. در ابتدای برنامه، حوزه سراسری با گزینههای خط فرمان مقداردهی اولیه میشود.
- /*!local:re2c[:<name>] ... */ یا %{local[:<name>] ... %}
- یک بلوک محلی مانند یک بلوک سراسری است، با این تفاوت که نامها و پیکربندیهای درون آن دارای حوزه محلی هستند (بر سایر بلوکها تأثیر نمیگذارند).
- /*!rules:re2c[:<name>] ... */ یا %{rules[:<name>] ... %}
- یک بلوک قواعد شبیه به یک بلوک محلی است، اما بهخودیخود هیچ کدی تولید نمیکند و هیچ تعریفی را به حوزه سراسری اضافه نمینماید -- هدف آن استفاده مجدد در سایر بلوکها است. این روشی برای اشتراکگذاری کد است (جزئیات بیشتر در بخش بلوکهای قابل استفاده مجدد). پیش از نسخه 2.2 re2rust، بلوکهای قواعد به گزینه -r --reusable نیاز داشتند.
- /*!use:re2c[:<name>] ... */ یا %{use[:<name>] ... %}
- یک بلوک استفاده که به یک بلوک قواعدِ از پیشتعریفشده ارجاع میدهد. اگر نام مشخص شده باشد، re2rust به دنبال یک بلوک قواعد با این نام میگردد. در غیر این صورت، از جدیدترین بلوک قواعد استفاده میشود (خواه نامگذاریشده باشد یا بدون نام). یک بلوک استفاده میتواند تعاریف، پیکربندیها و قواعد خاص خود را بیفزاید که به موارد موجود در بلوک قواعد ارجاعشده اضافه میشوند. پیش از نسخه 2.2 re2rust، بلوکهای استفاده به گزینه -r --reusable نیاز داشتند.
- /*!max:re2c[:<name1>[:<name2>...]] ... */ یا %{max[:<name1>[:<name2>...]] ... %}
- بلوکی که تعریف YYMAXFILL را تولید میکند. یک فهرست اختیاری از نامهای بلوک مشخص میکند که در زمان محاسبه مقدار YYMAXFILL کدام بلوکها باید لحاظ شوند (اگر فهرست خالی باشد، همه بلوکها لحاظ میشوند). بهطور پیشفرض کد تولیدشده یک تعریف ماکرو برای C (#define YYMAXFILL <n>) یا یک متغیر سراسری برای Go (var YYMAXFILL int = <n>) است. این مقدار میتواند با یک پیکربندی اختیاری format سفارشیسازی شود که یک رشته الگو را مشخص میکند که در آن @@{max} (یا بهاختصار @@) با مقدار عددی YYMAXFILL جایگزین میشود.
- /*!maxnmatch:re2c[:<name1>[:<name2>...]] ... */ یا %{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>...]] ... */ یا %{stags[:<name1>[:<name2>...]] ... %}, %{mtags[:<name1>[:<name2>...]] ... %{
- بلوکهایی که قطعهکدی الگو را مشخص میکنند که برای هر متغیر s-tag/m-tag تولیدشده توسط re2rust بسط مییابد. یک فهرست اختیاری از نامهای بلوک مشخص میکند که هنگام محاسبه مجموعه متغیرهای برچسب، کدام بلوکها باید لحاظ شوند (اگر فهرست خالی باشد، همه بلوکها لحاظ میشوند). دو پیکربندی اختیاری وجود دارد: format و separator. پیکربندی format یک رشته الگو را مشخص میکند که در آن @@{tag} (یا بهاختصار @@) با نام هر متغیر برچسب جایگزین میشود. پیکربندی separator قطعهکدی را مشخص میکند که برای پیوند دادن قطعات تولیدشده format برای متغیرهای برچسب مختلف به کار میرود.
- /*!svars:re2c[:<name1>[:<name2>...]] ... */, /*!mvars:re2c[:<name1>[:<name2>...]] ... */ یا %{svars[:<name1>[:<name2>...]] ... %}, %{mvars[:<name1>[:<name2>...]] ... %{
- بلوکهایی که قطعهکدی الگو را مشخص میکنند که برای هر s-tag/m-tag که صراحتاً در قواعد ذکر شده باشد (با گزینه --tags) یا بهطور ضمنی توسط re2rust تولید شده باشد (با گزینههای --captvars یا --posix-captvars) بسط مییابد. یک فهرست اختیاری از نامهای بلوک مشخص میکند که هنگام محاسبه مجموعه برچسبها، کدام بلوکها باید لحاظ شوند (اگر فهرست خالی باشد، همه بلوکها لحاظ میشوند). دو پیکربندی اختیاری وجود دارد: format و separator. پیکربندی format یک رشته الگو را مشخص میکند که در آن @@{tag} (یا بهاختصار @@) با نام هر برچسب جایگزین میشود. پیکربندی separator قطعهکدی را مشخص میکند که برای پیوند دادن قطعات تولیدشده format برای برچسبهای مختلف به کار میرود.
- /*!getstate:re2c[:<name1>[:<name2>...]] ... */ یا %{getstate[:<name1>[:<name2>...]] ... %}
- بلوکی که توزیع شرطی را بر اساس وضعیت تحلیلگر واژگانی تولید میکند (به گزینه --storable-state نیاز دارد). یک فهرست اختیاری از نامهای بلوک مشخص میکند که کدام بلوکها باید در توزیع وضعیت لحاظ شوند. انتقال پیشفرض به برچسب شروع اولین بلوک در فهرست میرود. اگر فهرست خالی باشد، تمام بلوکها لحاظ میشوند و انتقال پیشفرض به اولین بلوک در فایلی میرود که برچسب شروع دارد. این نوع بلوک با گزینه --loop-switch ناسازگار است، زیرا نیازمند انتقالهای بینبلوکی است که بدون goto یا فراخوانی توابع پشتیبانی نمیشوند.
- /*!conditions:re2c[:<name1>[:<name2>...]] ... */, /*!types:re2c... */ یا %{conditions[:<name1>[:<name2>...]] ... %}, %{types... %}
- بلوکی که شمارش شرایط را تولید میکند (به گزینه --conditions نیاز دارد). یک فهرست اختیاری از نامهای بلوک مشخص میکند که هنگام محاسبه مجموعه شرایط، کدام بلوکها باید لحاظ شوند (اگر فهرست خالی باشد، همه بلوکها لحاظ میشوند). بهطور پیشفرض کد تولیدشده یک نوع شمارشی YYCONDTYPE است. این مقدار میتواند با پیکربندیهای اختیاری format و separator سفارشیسازی شود. پیکربندی format یک رشته الگو را مشخص میکند که در آن @@{cond} (یا بهاختصار @@) با نام هر شرط، و @@{num} با اندیس عددی آن شرط جایگزین میشود. پیکربندی separator قطعهکدی را مشخص میکند که برای پیوند دادن قطعات تولیدشده format برای شرایط مختلف به کار میرود.
- /*!include:re2c <file> */ یا %{include <file> %}
- این بلوک امکان درج <file> را فراهم میکند، که باید یک مسیر فایل درون علامتهای نقلقول دوتایی باشد. محتویات فایل دقیقاً به جای بلوک جایگزین میشود، درست مانند نحوه کارکرد #include در C/C++. این بلوک میتواند همراه با گزینه --depfile برای تولید وابستگیهای سامانه ساخت بر روی فایلهای گنجاندهشده استفاده شود.
- /*!header:re2c:on*/ یا %{header:on %}
- این بلوک آغاز فایل سرآیند را مشخص میکند. تمام موارد پس از آن و تا بلوک header:off بعدی توسط re2rust پردازش شده و در فایل سرآیند مشخصشده با گزینه -t --type-header نوشته میشود.
- /*!header:re2c:off*/ یا %{header:off %}
- این بلوک پایان فایل سرآیندی را که با بلوک header:on*/ آغاز شده بود، نشان میدهد.
- /*!ignore:re2c ... */ یا %{ignore ... %}
- بلوکی که
محتویات آن
نادیده
گرفته شده و
از فایل
خروجی حذف
میشود.
در اینجا فهرست کاملی از پیکربندیهای پشتیبانیشده توسط re2rust آمده است:
- re2c:api, re2c:input
- مشابه گزینهٔ --api.
- re2c:api:sigil
- نشانگر ("sigil") مورد استفاده برای جاینگهدارهای آرگومان در عناصر پایهٔ API را مشخص میکند. پیشفرض @@ است. یک جاینگهدار با sigil آغاز شده و به دنبال آن نام آرگومان در آکولاد میآید. برای نمونه، اگر sigil روی $ تنظیم شود، جاینگهدارها به شکل ${name} خواهند بود. رابطهای برنامهنویسی تکآرگومانی ممکن است از نشانهگذاری خلاصه بدون نام در آکولاد استفاده کنند. این گزینه میتواند با گزینههای مربوط به هر یک از عناصر پایهٔ API بازنویسی (override) شود، مانند re2c:YYFILL@len برای YYFILL.
- re2c:api:style
- سبک API را مشخص میکند. مقادیر ممکن functions (پیشفرض برای C) و free-form (پیشفرض برای Go و Rust) هستند. در سبک functions، عناصر پایهٔ API با فهرستی از آرگومانها درون پرانتز به دنبال نام عنصر پایه تولید میشوند. آرگومانها تنها برای پارامترهای خودکار تولیدشده فراهم میشوند (مانند تعداد نویسههای ارسالی به YYFILL)، نه برای زمینهٔ کلی تحلیلگر واژگانی (lexer context)، بنابراین عناصر پایه بیشتر شبیه ماکروها در C/C++ یا بستارها (closures) در Go و Rust عمل میکنند. در سبک free-form، عناصر پایهٔ API شکل ثابتی ندارند: آنها باید بهصورت رشتههایی حاوی قطعهکدهای آزاد با متغیرهای درونیابیشده به شکل @@{var} یا @@ تعریف شوند (که با آرگومانها در سبک تابعگونه مطابقت دارند). این پیکربندی ممکن است برای هر یک از عناصر پایهٔ API بازنویسی (override) شود، برای نمونه به پیکربندی 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 محاسباتی استفاده شود، این پیکربندی آستانهٔ پیچیدگی را مشخص میکند که تولید جدولهای پرش (jump tables) را به جای دستورهای if تو در تو و نگاشتهای بیتی (bitmaps) فعال میکند. مقدار پیشفرض 9 است.
- re2c:cond:abort
- اگر روی یک مقدار عددی صحیح و مثبت تنظیم شود، حالت پیشفرض (default case) در توزیع شرطیِ تولیدشده، اجرای برنامه را متوقف میکند.
- re2c:cond:goto
- قطعهکدی را مشخص میکند که برای قواعد میانبُر خودکار تولیدشدهٔ :=> در شرایط استفاده میشود. پیشفرض goto @@; است. جاینگهدار @@ با نام شرط جایگزین میشود (به پیکربندیهای re2c:api:sigil و re2c:cond:goto@cond مراجعه کنید).
- re2c:cond:goto@cond
- نشانگر (sigil) مورد استفاده برای جایگزینی آرگومان را در تعریف re2c:cond:goto مشخص میکند. مقدار پیشفرض @@ است. پیکربندی عمومیتر re2c:api:sigil را بازنویسی (override) میکند.
- re2c:cond:divider
- جداکننده را برای بلوکهای شرط تعریف میکند. مقدار پیشفرض /* *********************************** */ است. جاینگهدارها با نام شرط جایگزین میشوند (به re2c:api;sigil و re2c:cond:divider@cond مراجعه کنید).
- re2c:cond:divider@cond
- نشانگر (sigil) مورد استفاده برای جایگزینی آرگومان را در تعریف re2c:cond:divider مشخص میکند. پیشفرض @@ است. پیکربندی عمومیتر re2c:api:sigil را بازنویسی (override) میکند.
- 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
- نماد نگهبان (sentinel symbol) مورد استفاده با قاعدهٔ پایان ورودی $ را مشخص میکند. مقدار پیشفرض -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
- نام برچسب اختیاریای را که پس از گزینشگر (switch) YYGETSTATE در حالت وضعیت ذخیرهپذیر میآید (فعالشده با re2c:state:nextlabel) مشخص میکند. پیشفرض yyNext است.
- re2c:lookahead, re2c:flags:lookahead
- منسوخشده (گزینهٔ منسوخشدهٔ --no-lookahead را ببینید).
- re2c:monadic
- اگر روی مقداری غیر از صفر تنظیم شود، تحلیلگر واژگانی تولیدشده از نشانهگذاری مونادیک (monadic notation) استفاده خواهد کرد (این پیکربندی مختص Haskell است)..TP 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، اما میتواند بر پایهٔ هر بلوک پیکربندی شود.
- عبارت مورد استفاده برای متغیرهای برچسب را مشخص میکند. بهطور پیشفرض re2rust عبارتهایی به شکل yyt<N> تولید میکند. این موضوع ممکن است ناخوشایند باشد، برای مثال اگر متغیرهای برچسب بهصورت فیلدهایی در یک struct تعریف شده باشند. تمام رخدادهای @@{tag} یا @@ با نام واقعی برچسب جایگزین میشوند. برای مثال، re2c:tags:expression = "s.@@"; منجر به عبارتهایی به شکل s.yyt<N> در کد تولیدشده میشود. همچنین پیکربندی re2c:api:sigil را ببینید.
- عبارت ثابتی را مشخص میکند که برای مقدار برچسب منفی استفاده میشود (معمولاً اگر برچسبها آفستهای عددی صحیح در رشتهٔ ورودی باشند، این مقدار -1 خواهد بود، یا اگر اشارهگر باشند، اشارهگر null است).
- پیشوند نام متغیرهای برچسب را مشخص میکند. مقدار پیشفرض yyt است.
- re2c:sentinel
- نماد دیدهبان (sentinel) مورد استفاده برای بررسیهای پایان ورودی را مشخص میکند (زمانی که بررسیهای محدوده با re2c:yyfill:enable = 0; غیرفعال شده باشند و re2c:eof تنظیم نشده باشد). این پیکربندی بر تولید کد تأثیری ندارد: هدف آن این است که تأیید کند نماد دیدهبان در میانهٔ یک قاعده مجاز نیست، و اطمینان حاصل شود که تحلیلگر لغوی فراتر از انتهای بافر را نخواهد خواند. مقدار پیشفرض -1` است (در این حالت re2rust فرض میکند که دیدهبان صفر است که رایجترین حالت است). تنها اعداد دهدهی شناخته میشوند.
- re2c:state:abort
- اگر روی یک مقدار عددی صحیح مثبت تنظیم شود، حالت پیشفرض (default case) در توزیع وضعیت (state dispatch) تولیدشده، اجرای برنامه را لغو (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 (free-form) است.
- re2c:YYGETSTATE, re2c:define:YYGETSTATE
- عنصر پایهٔ API به نام YYGETSTATE را تعریف میکند.
- re2c:YYGETSTATE:naked, re2c:define:YYGETSTATE:naked
- پیکربندی عمومیتر re2c:api:style را برای YYGETSTATE بازنویسی میکند. مقدار صفر متناظر با سبک آزاد API (free-form) است.
- 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 (free-form) است.
- 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 (free-form) است.
- 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 را تعریف میکند..TP 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
- اگر روی مقداری غیرصفر تنظیم شود، نگاشتهای بیتی برای گزینهٔ --bit-vectors در قالب هگزادسیمال تولید میشوند. مقدار پیشفرض صفر است (نگاشتهای بیتی در قالب دهدهی هستند).
- re2c:yych, re2c:variable:yych
- عنصر اولیهٔ API yych را تعریف میکند.
- re2c:yych:emit, re2c:variable:yych:emit
- اگر روی صفر تنظیم شود، تعریف yych تولید نخواهد شد. مقدار پیشفرض غیرصفر است.
- re2c:yych:conversion, re2c:variable:yych:conversion
- اگر روی غیرصفر تنظیم شود، re2rust در هر بار خواندن yych بهطور خودکار یک تبدیل به YYCTYPE تولید میکند. مقدار پیشفرض صفر است (بدون تبدیل).
- re2c:yych:literals, re2c:variable:yych:literals
- قالب لیترالهایی که yych با آنها مطابقت داده میشود را مشخص میکند. مقادیر ممکن عبارتند از: char (لیترالهای نویسهای درون نقلقول تکی، که نویسههای غیرقابلچاپ در آنها از توالیهای گریز آغازشونده با بکاسلش استفاده میکنند)، hex (اعداد صحیح هگزادسیمال) و char_or_hex (ترکیبی از هر دو، لیترالهای نویسهای برای نویسههای قابلچاپ و اعداد صحیح هگزادسیمال برای موارد دیگر).
- re2c:yyctable, re2c:variable:yyctable
- عنصر اولیهٔ API yyctable را تعریف میکند.
- 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 نباشد (برای نمونه در صورت استفاده از یک نویسهٔ نگهبان). از هشدارها (گزینهٔ -W) و پیکربندی re2c:sentinel برای اطمینان از اینکه تحلیلگر لغوی تولیدشده نمیتواند فراتر از انتهای ورودی را بخواند استفاده کنید. مقدار پیشفرض غیرصفر است (YYFILL فعال است).
- re2c:yyfill:parameter
- اگر روی صفر تنظیم شود، از تولید پارامتر ارسالی به YYFILL جلوگیری میکند. این پارامتر حداقل تعداد نویسههایی است که باید فراهم شوند. مقدار پیشفرض غیرصفر است (پارامتر تولید میشود). این پیکربندی را میتوان با استفاده از re2c:YYFILL:naked یا re2c:api:style بازنویسی کرد.
- re2c:yyfn:sep
- جداکنندهٔ استفادهشده در عناصر YYFN را مشخص میکند (پیشفرض نقطهویرگول است).
- re2c:yyfn:throw
- استثناهای
پرتابشده
توسط تابع
YYFN را مشخص
میکند
(پیشفرض
خالی است،
که به معنی
عدم وجود
استثنا
است).
ابزار re2rust از نحو زیر برای عبارتهای منظم استفاده میکند:
- "foo"
- رشته متنی حساس به حروف بزرگ و کوچک.
- 'foo'
- رشته متنی غیرحساس به حروف بزرگ و کوچک.
- [a-xyz], [^a-xyz]
- کلاس نویسه (احتمالاً نفیشده).
- .
- هر نویسهای به جز خط جدید (newline).
- 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 یک گروه گیرنده (capturing) یا غیرگیرنده (non-capturing) خواهد بود.
- (!R)
- اگر استخراج تطابقهای فرعی فعال باشد، (!R) بسته به گزینه --invert-captures یک گروه غیرگیرنده یا گیرنده خواهد بود.
- R S
- الحاق: R و به دنبال آن S.
- R | S
- انتخاب جایگزین: R یا S.
- R / S
- پیشنگری (Lookahead): 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.
کنشها (Actions)
در اینجا فهرستی از کنشهای از پیش تعریفشده که توسط re2rust پشتیبانی میشوند آمده است:
- !entry code
- کنش ورود، بلوک code تعریفشده توسط کاربر را به وضعیت آغازین ماشین حالت متناهی جاری مقید میکند. اگر از شرایط شروع (start conditions) استفاده شود، کنش ورود را میتوان برای هر شرط بهصورت جداگانه تنظیم کرد. این کنش میتواند برای انجام مقداردهی اولیه استفاده شود، مثلاً برای ذخیره موقعیت شروع یک جزء واژگانی (lexeme).
- !pre_rule code
- کنش پیشقاعده، بلوک code تعریفشده توسط کاربر را به ابتدای کنشهای معنایی تمام قواعد در بلوک جاری (یا شرط، در صورت استفاده از شرایط شروع) اضافه میکند. این کنش میتواند برای مجزا کردن بخش مشترک تمام کنشهای معنایی به کار رود (مانند ذخیره موقعیت پایانی یک جزء واژگانی).
- !post_rule code
- کنش
پسقاعده،
بلوک code
تعریفشده
توسط کاربر
را به
انتهای
کنشهای
معنایی
تمام قواعد
در بلوک
جاری (یا
شرط، در
صورت
استفاده از
شرایط
شروع)
ضمیمه
میکند. این
کنش ممکن
است برای
تولید
دستورات
تله (trap statements) جهت
جلوگیری از
انحراف
جریان
کنترل
ناخواسته
به کار رود.
در اینجا فهرست کاملی از رهنمودهای پشتیبانیشده توسط re2rust آمده است:
- !use:name ;
- یک رهنمود use درونبلوکی که بلوک قواعد از پیش تعریفشده با name مشخص را در بلوک فعلی ادغام میکند. تعاریف نامگذاریشده، پیکربندیها و قواعد بلوک ارجاعدادهشده به موارد فعلی افزوده میشوند. تداخلها میان قواعد و پیکربندیهای همپوشان به روش معمول حلوفصل میشوند: قاعده نخست اولویت دارد و آخرین پیکربندی بر موارد پیشین بازنویسی میشود. یک استثنا، قواعد ویژه *، $ و <!> هستند که برای آنها تعریف محلی بلوک همواره اولویت دارد. رهنمود use میتواند در هر کجای درون یک بلوک قرار گیرد و استفاده از چندین رهنمود use مجاز است.
- !include file ;
- این رهنمود همانند بلوک include عمل میکند: محتویات file را دقیقاً و بدون تغییر بهجای رهنمود درج میکند.
رابط برنامه (Program interface)
کد تولیدشده با کمک پایهها (primitives) که در مجموع با نام API شناخته میشوند با برنامه بیرونی ارتباط برقرار میکند. اینکه کدام پایهها باید برای یک برنامه خاص تعریف شوند به عوامل متعددی بستگی دارد، از جمله پیچیدگی عبارات باقاعده، نحوه نمایش ورودی، بافرینگ و استفاده از قابلیتهای گوناگون. تمام پایههای ضروری باید توسط کاربر در قالب ماکروها، توابع، متغیرها یا هر شکل مناسب دیگری تعریف شوند که کد تولیدشده را از نظر نحوی و معنایی درست سازد. re2rust تعاریف را بررسی نمیکند (و نمیتواند بررسی کند)، بنابراین اگر موردی جا افتاده باشد یا به نادرستی تعریف شده باشد، ممکن است برنامه تولیدشده با خطاهای زمان کامپایل یا زمان اجرا مواجه شود. این راهنما نمونههایی از تعاریف API را در رایجترین موارد ارائه میدهد.
ابزار re2rust دارای سه شیوه API است که مجموعه اصلی پایههای استفادهشده توسط یک برنامه را مشخص میکنند:
- Simple API
- (در نسخه 4.0 افزوده شد) این یک API پایهای است که میتواند با گزینه --api simple یا پیکربندی re2c:api = simple فعال شود. این رابط شامل پایههای زیر است: YYINPUT (که باید بهصورت توالیای از واحدهای کد، مانند یک رشته، تعریف شود) و YYCURSOR، YYMARKER، YYCTXMARKER، YYLIMIT (که باید بهعنوان شاخصهایی در YYINPUT تعریف شوند).
- Record API
- (در نسخه 4.0 افزوده شد) رابط Record API در مواردی مفید است که وضعیت تحلیلگر واژگانی (lexer) باید در یک struct ذخیره شود. این رابط با گزینه --api record یا پیکربندی re2c:api = record فعال میشود. این API شامل یک متغیر yyrecord است (نام آن با re2c:yyrecord قابل بازنویسی است) که باید بهصورت یک struct با فیلدهای yyinput، yycursor، yymarker، yyctxmarker و yylimit تعریف شود (تنها فیلدهایی که توسط کد تولیدشده استفاده میشوند نیاز به تعریف دارند و نام آنها قابل پیکربندی است).
- Generic API
- این انعطافپذیرترین API و رابط پیشفرض برای backend زبان Rust است. این API شامل پایههایی برای عملیاتهای عمومی است: YYPEEK، YYSKIP، YYBACKUP، YYBACKUPCTX، YYSTAGP، YYSTAGN، YYMTAGP، YYMTAGN، YYRESTORE، YYRESTORECTX، YYRESTORETAG، YYSHIFT، YYSHIFTSTAG، YYSHIFTMTAG، YYLESSTHAN، YYEND. به عنوان مثال، اگر ورودی یک برش بایتی buffer: &[u8] باشد، متغیرهای cursor، limit، marker و ctxmarker از نوع usize نشاندهنده موقعیتهای ورودی باشند، و یک ثابت NONE نشاندهنده موقعیت نامعتبر باشد، آنگاه Generic API را میتوان بهصورت زیر تعریف کرد:
/*!re2c
re2c:YYPEEK = "*buffer.get_unchecked(cursor)";
re2c:YYSKIP = "cursor += 1;";
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} = NONE;";
re2c:YYSHIFT = "cursor = (cursor as isize + @@{shift}) as usize;";
re2c:YYSHIFTSTAG = "@@{tag} = (@@{tag} as isize + @@{shift}) as usize;";
*/
در اینجا فهرست کاملی از پایههای 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 تنها در صورتی نیاز است که عملگر پیشنگری / استفاده شده باشد.
- YYFILL
- یک پایه Generic API با یک متغیر len. YYFILL باید دستکم len نویسه ورودی دیگر را تأمین کند یا با شکست مواجه شود. اگر re2c:eof استفاده شود، آنگاه len همواره برابر با 1 است و YYFILL باید همواره به تابع فراخوان بازگردد؛ مقدار بازگشتی صفر نشاندهنده موفقیت است. اگر re2c:eof استفاده نشود، مقدار بازگشتی YYFILL نادیده گرفته میشود و در هنگام شکست نباید بازگردد. بیشینه مقدار len برابر با YYMAXFILL است.
- YYFN
- پایهای که پیشنمونه تابع را در مدل کد --recursive-functions تعریف میکند. مقدار آن باید آرایهای از یک یا چند رشته باشد، که در آن هر رشته شامل دو یا سه جزء است که با رشته مشخصشده در پیکربندی re2c:fn:sep (معمولاً یک نقطهویرگول) از هم جدا شدهاند. اولین عنصر آرایه، نام تابع و نوع بازگشتی آن را تعریف میکند (برای یک تابع void خالی است). عناصر بعدی آرگومانهای تابع را مشخص میکنند: نخست، عبارت برای آرگومان مورد استفاده در بدنه تابع (معمولاً فقط یک نام)؛ دوم، نوع آرگومان؛ سوم، پارامتر صوری اختیاری (پیشفرض آن جزء اول است - معمولاً هر دو آرگومان و پارامتر شناسه یکسانی هستند).
- YYINPUT
- یک مقدار r-value که توالی فعلی نویسههای ورودی (رشته، بافر و غیره) را ذخیره میکند.
- YYMAXFILL
- یک ثابت عددی صحیح برابر با حداکثر مقدار آرگومان YYFILL. این مقدار را میتوان با یک بلوک max تولید کرد.
- YYLESSTHAN
- یک پایه Generic API با یک متغیر len. این پایه باید بهعنوان یک مقدار r-value از نوع بولی تعریف شود که مقدار آن برابر با true است اگر و تنها اگر کمتر از len نویسه ورودی باقی مانده باشد.
- YYEND
- یک پایه Generic API بدون متغیر. باید بهعنوان یک مقدار r-value از نوع بولی تعریف شود که مقدار آن برابر با true است اگر و تنها اگر به پایان منطقی ورودی رسیده باشیم (بدون احتساب هرگونه نویسه لایهگذاری یا نماد نگهبان). YYEND برای پیادهسازی نماد $ در عبارات باقاعده به کار میرود. این پایه با YYLESSTHAN متفاوت است که برای اطمینان از اینکه تحلیلگر واژگانی فراتر از انتهای بافر را نمیخواند استفاده میشود..TP YYPEEK یک جزء اولیه (primitive) عمومی API بدون متغیر. باید بهصورت یک r-value از نوع YYCTYPE تعریف شود که برابر با نویسه در موقعیت فعلی ورودی است.
- YYSKIP
- یک جزء اولیه (primitive) عمومی API که باید موقعیت فعلی ورودی را یک واحد کد (code unit) به جلو ببرد.
- YYBACKUP
- یک جزء اولیه (primitive) عمومی API که باید موقعیت فعلی ورودی را ذخیره کند (تا بعداً با YYRESTORE بازیابی شود).
- YYRESTORE
- یک جزء اولیه (primitive) عمومی API که باید موقعیت فعلی ورودی را به مقدار ذخیرهشده توسط YYBACKUP بازگرداند.
- YYBACKUPCTX
- یک جزء اولیه (primitive) عمومی API که باید موقعیت فعلی ورودی را بهعنوان موقعیت بافت پسین (trailing context) ذخیره کند (تا بعداً با YYRESTORECTX بازیابی شود).
- YYRESTORECTX
- یک جزء اولیه (primitive) عمومی API که باید موقعیت بافت پسین ذخیرهشده با YYBACKUPCTX را بازیابی کند.
- YYRESTORETAG
- یک جزء اولیه (primitive) عمومی API با یک متغیر tag که باید موقعیت بافت پسین را به مقدار tag بازگرداند.
- YYSTAGP
- یک جزء اولیه (primitive) عمومی API با یک متغیر tag، که در آن tag میتواند یک اشارهگر یا آفست در YYINPUT باشد (برای جزئیات به بخش استخراج تطابقهای فرعی مراجعه کنید). YYSTAGP باید tag را روی موقعیت فعلی ورودی تنظیم کند.
- YYSTAGN
- یک جزء اولیه (primitive) عمومی API با یک متغیر tag، که در آن tag میتواند یک اشارهگر یا آفست در YYINPUT باشد (برای جزئیات به بخش استخراج تطابقهای فرعی مراجعه کنید). YYSTAGN باید tag را روی مقداری تنظیم کند که نشاندهندهٔ موقعیت ورودی ناموجود است.
- YYMTAGP
- یک جزء اولیه (primitive) عمومی API با یک متغیر tag. YYMTAGP باید موقعیت فعلی را به تاریخچهٔ تطابقهای فرعی tag اضافه کند (برای جزئیات به بخش استخراج تطابقهای فرعی مراجعه کنید).
- YYMTAGN
- یک جزء اولیه (primitive) عمومی API با یک متغیر tag. YYMTAGN باید مقداری را که نشاندهندهٔ موقعیت ورودی ناموجود است به تاریخچهٔ تطابقهای فرعی tag اضافه کند (برای جزئیات به بخش استخراج تطابقهای فرعی مراجعه کنید).
- YYSHIFT
- یک جزء اولیه (primitive) عمومی API با یک متغیر shift که باید موقعیت فعلی ورودی را به اندازهٔ shift نویسه انتقال دهد (مقدار انتقال ممکن است منفی باشد).
- YYCOPYSTAG
- یک جزء اولیه (primitive) عمومی API با دو متغیر lhs و rhs که باید متغیر s-tag سمت راست (rhs) را در متغیر s-tag سمت چپ (lhs) کپی کند. برای بیشتر زبانها این جزء اولیه دارای یک تعریف پیشفرض است که lhs را به rhs انتساب میدهد.
- YYCOPYMTAG
- یک جزء اولیه (primitive) عمومی API با دو متغیر lhs و rhs که باید متغیر m-tag سمت راست (rhs) را در متغیر m-tag سمت چپ (lhs) کپی کند. برای بیشتر زبانها این جزء اولیه دارای یک تعریف پیشفرض است که lhs را به rhs انتساب میدهد.
- YYSHIFTSTAG
- یک جزء اولیه (primitive) عمومی API با دو متغیر tag و shift که باید tag را به اندازهٔ shift واحد کد (code unit) انتقال دهد (مقدار انتقال ممکن است منفی باشد).
- YYSHIFTMTAG
- یک جزء اولیه (primitive) عمومی API با دو متغیر tag و shift که باید آخرین مقدار در تاریخچهٔ tag را به اندازهٔ shift واحد کد انتقال دهد (مقدار انتقال ممکن است منفی باشد).
- YYMAXNMATCH
- یک ثابت عدد صحیح برابر با حداکثر تعداد گروههای تطابق POSIX در یک قاعده. این مقدار با یک بلوک maxnmatch تولید میشود.
- YYCONDTYPE
- نوع شمارش شرط (condition enum). میتوان آن را با بلوک conditions یا گزینهٔ --header تولید کرد.
- YYGETACCEPT
- یک جزء اولیه (primitive) با یک متغیر var که انتخابکنندهٔ عددی قاعدهٔ پذیرفتهشده را ذخیره میکند. برای بیشتر زبانها این جزء اولیه دارای یک تعریف پیشفرض است که از var میخواند.
- YYSETACCEPT
- یک جزء اولیه (primitive) با دو متغیر: var (یک l-value که انتخابکنندهٔ عددی قاعدهٔ پذیرفتهشده را ذخیره میکند) و val (مقدار انتخابکننده). برای بیشتر زبانها این جزء اولیه دارای تعریفی پیشفرض است که var را به val انتساب میدهد.
- YYGETCOND
- یک r-value از نوع YYCONDTYPE که برابر با شناسهٔ شرط فعلی است.
- YYSETCOND
- یک جزء اولیه (primitive) با یک متغیر cond که باید شناسهٔ شرط فعلی را روی cond تنظیم کند.
- YYGETSTATE
- یک r-value از نوع عدد صحیح که برابر با وضعیت فعلی تحلیلگر لغوی است. مقدار اولیهٔ آن باید -1 باشد.
- YYSETSTATE
- یک جزء اولیه (primitive) با یک متغیر state که باید وضعیت فعلی تحلیلگر لغوی را روی state تنظیم کند.
- YYDEBUG
- این جزء اولیه فقط با گزینهٔ -d، --debug-output تولید میشود. هدف آن افزودن ثبت وقایع (logging) به کد تولیدشده است (تعریف متداول 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
- جدول پرش تولیدشده برای ارسال (dispatch) شرط اولیه (با ترکیب گزینههای --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
- فعالسازی استخراج زیرتطبیق (submatch) با گروههای گیرانداز حریصانهٔ چپترین. نتیجه در یک آرایهٔ yybmatch با ظرفیت 2 * YYMAXNMATCH جمعآوری میشود، و yynmatch به تعداد گروهها برای قاعدهٔ تطبیقیافته تنظیم میگردد.
- --captvars, --leftmost-captvars
- فعالسازی استخراج زیرتطبیق با گروههای گیرانداز حریصانهٔ چپترین. نتیجه برای k-امین گروه گیرانداز در متغیرهای yytl<k> و yytr<k> جمعآوری میشود.
- --case-insensitive
- رفتار با رشتههای محصور در نقلقول تکی و دوتایی بهصورت غیرحساس به حروف کوچک و بزرگ.
- --case-inverted
- معکوس کردن معنای رشتههای با نقلقول تکی و دوتایی: رفتار با رشتههای دارای نقلقول تکی بهصورت حساس به حروف کوچک و بزرگ و رشتههای دارای نقلقول دوتایی بهصورت غیرحساس به حروف.
- --case-ranges
- ادغام caseهای متوالی در دستورهای switch بهصورت یک بازه به شکل low ... high. این نحو، یک افزونهٔ زبان C/C++ است که توسط کامپایلرهایی مانند GCC، Clang و Tcc پشتیبانی میشود. مزیت اصلی نسبت به استفاده از caseهای تکی، تولید کد کوچکتر و زمان تولید سریعتر است، اگرچه برای برخی کامپایلرها مانند Tcc منجر به اندازهٔ باینری کوچکتر نیز میشود.
- --computed-gotos -g
- بهینهسازی پرشهای شرطی با استفاده از افزونهٔ غیراستاندارد "computed goto" (که باید توسط کامپایلر پشتیبانی شود). re2rust جدولهای پرش را تنها در موارد پیچیده با شاخههای شرطی فراوان تولید میکند. آستانهٔ پیچیدگی میتواند با پیکربندی cgoto:threshold تنظیم شود. آفستهای نسبی را میتوان با پیکربندی cgoto:relative فعال کرد. این گزینه مستلزم --bit-vectors است.
- --computed-gotos-relative
- مشابه --computed-gotos، اما در عوض آفستهای نسبی را برای جدولهای پرش تولید میکند (که باید توسط کامپایلر پشتیبانی شود). این گزینه مستلزم --computed-gotos است.
- --conditions --start-conditions -c
- فعالسازی پشتیبانی از "شرطها" مشابه Flex: چندین تحلیلگر لغوی مرتبط درون یک بلوک. این گزینه جایگزینی برای مشخص کردن دستی بلوکهای مختلف re2rust است که با goto یا فراخوانیهای تابع به هم متصل شدهاند.
- --depfile FILE
- نوشتن اطلاعات وابستگی در FILE در قالب یک قاعدهٔ Makefile بهشکل <output-file> : <input-file> [include-file ...]. این کار امکان ردگیری وابستگیهای ساخت را در حضور بلوکها/دستورالعملهای include فراهم میکند، بهطوری که بهروزرسانی فایلهای include باعث تولید مجدد فایل خروجی میشود. این گزینه به گزینهٔ --output وابسته است.
- --ebcdic --ecb -e
- تولید یک تحلیلگر لغوی که ورودی را با کدگذاری EBCDIC میخواند. re2rust فرض میکند که بازهٔ نویسهها 0 -- 0xFF و اندازهٔ هر نویسه ۱ بایت است.
- --empty-class <match-empty | match-none | error>
- تعریف نحوهٔ برخورد re2rust با کلاسهای نویسهٔ خالی. با match-empty (پیشفرض)، کلاس خالی با ورودی خالی تطبیق مییابد (که غیرمنطقی است، اما با نسخههای پیشین سازگار است). با match-none کلاس خالی همیشه در تطبیق ناموفق است. با error، کلاس خالی یک خطای کامپایل ایجاد میکند.
- --encoding-policy <fail | substitute | ignore>
- تعریف نحوهٔ برخورد re2rust با جانشینهای یونیکد (surrogates). با fail هنگام مواجهه با جانشین، re2rust با خطا متوقف میشود. با substitute، re2rust بهطور بیصدا جانشینها را با نقطهٔ کد خطای 0xFFFD جایگزین میکند. با ignore (پیشفرض)، re2rust با جانشینها مانند نقاط کد عادی رفتار میکند. استاندارد یونیکد بیان میکند که جانشینهای مستقل نامعتبر هستند، اما کتابخانهها و برنامههای دنیای واقعی رفتارهای متفاوتی دارند.
- --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 کاربردی است. re2rust به دنبال FILE در دایرکتوری فایل والد و در مکانهای include مشخصشده با گزینهٔ -I میگردد.
- --input <default | custom>
- نام مستعار منسوخشده برای --api. گزینهٔ default معادل simple است (در واقع برای بیشتر بکاندها پیشفرض است، اما نه برای همه). گزینهٔ custom معادل generic است.
- --input-encoding <ascii | utf8>
- مشخص کردن روش تجزیهٔ عبارتهای باقاعده توسط re2rust. با ascii (پیشفرض) re2rust با ورودی مانند کدگذاری ASCII رفتار میکند: هر دنبالهای از واحدهای کد، دنبالهای از نویسههای تکبایتی مستقل است. با utf8، re2rust با ورودی مانند کدگذاری UTF8 رفتار کرده و نویسههای چندبایتی را تشخیص میدهد.
- --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
- دستورالعملهای خط در خروجی درج نشوند. این گزینه ممکن است هنگامی که کد تولیدشده در یک سامانهٔ کنترل نسخه ذخیره میشود مفید باشد (برای جلوگیری از تفاوتهای بزرگ تولیدشده بر اثر تغییرات جزئی).
- --no-generation-date
- جلوگیری از چاپ تاریخ در فایل تولیدشده.
- --no-version
- جلوگیری از چاپ نسخه در فایل تولیدشده.
- --no-unsafe
- تولید نشدن غلاف unsafe بر روی YYPEEK (این گزینه مختص به زبان Rust است). به دلایل کارایی، YYPEEK باید از بررسی مرزها اجتناب کند، زیرا تحلیلگر لغوی پیشاپیش بررسیهای پایان ورودی را به روش کارآمدتری انجام داده است. کاربر میتواند یک تعریف امن برای YYPEEK ارائه دهد، یا تعریفی که تنها در ساختهای انتشار (release) ناامن است؛ در این صورت، گزینهٔ --no-unsafe به جلوگیری از هشدارهای مربوط به بلوکهای unsafe زائد کمک میکند.
- --output -o OUTPUT
- مشخص کردن فایل OUTPUT.
- --posix-captures, -P
- فعالسازی استخراج زیرتطبیق با گروههای گیرانداز به سبک POSIX. نتیجه در یک آرایهٔ yybmatch با ظرفیت 2 * YYMAXNMATCH جمعآوری میشود، و yynmatch به تعداد گروهها برای قاعدهٔ تطبیقیافته تنظیم میگردد..TP --posix-captvars فعالسازی استخراج زیرتطبیق با گروههای ضبط به سبک POSIX. نتیجه در متغیرهای yytl<k> و yytr<k> برای k-امین گروه ضبط ذخیره میشود.
- --recursive-functions
- استفاده از مدل کدی مبتنی بر توابع همبازگشتی، که در آن هر وضعیت DFA یک تابع جداگانه است که میتواند توابع وضعیتهای دیگر یا خودش را فراخوانی کند.
- --reusable -r
- از نسخهٔ 2.2 منسوخ شده است (بلوکهای با قابلیت استفادهٔ مجدد اکنون بهطور پیشفرض مجاز هستند).
- --skeleton -S
- نادیدهگرفتن کد رابط تعریفشده توسط کاربر و تولید یک برنامهٔ "اسکلت" خودکفا. علاوه بر این، تولید فایلهای ورودی با رشتههای بهدستآمده از دستور زبان باقاعده و نتایج تطبیق فشردهشده که برای تأیید رفتار "اسکلت" روی تمام ورودیها به کار میروند. این گزینه برای یافتن باگها در بهینهسازیها و تولید کد مفید است. این گزینه فقط برای C پشتیبانی میشود.
- --storable-state -f
- تولید یک تحلیلگر لغوی که میتواند وضعیت درونی خود را ذخیره کند. این مورد در تحلیلگرهای لغوی مدل push مفید است که وقتی ورودی کافی وجود ندارد توسط یک برنامهٔ بیرونی متوقف شده و سپس هنگام دردسترس قرار گرفتن ورودی بیشتر، از سر گرفته میشوند. در این حالت، کاربران باید علاوه بر این، توابع اولیهٔ YYGETSTATE و YYSETSTATE را تعریف کنند و متغیرهای yych، yyaccept و state باید بخشی از وضعیت ذخیرهشدهٔ تحلیلگر لغوی باشند.
- --syntax FILE
- بارگذاری پیکربندیها از FILE مشخصشده و اعمال آنها بر روی فایل نحو (syntax) پیشفرض. توجه داشته باشید که FILE میتواند تنها چند پیکربندی را تعریف کند (اگر برای اصلاح فایل نحو پیشفرض استفاده شود)، یا میتواند یک بکاند زبان کاملاً جدید را تعریف کند (در حالت دوم توصیه میشود از گزینهٔ --lang none استفاده کنید).
- فعالسازی استخراج زیرتطبیق با برچسبها (tags).
- --ucs2 --wide-chars -w
- تولید تحلیلگر لغوی که ورودی با کدگذاری UCS2 را میخواند. re2rust فرض میکند که محدودهٔ نویسهها 0 -- 0xFFFF و اندازهٔ هر نویسه ۲ بایت است. این گزینه مستلزم --nested-ifs است.
- --utf8 --utf-8 -8
- تولید تحلیلگر لغوی که ورودی را با کدگذاری UTF-8 میخواند. re2rust فرض میکند که محدودهٔ نویسهها 0 -- 0x10FFFF و اندازهٔ هر نویسه ۱ بایت است.
- --utf16 --utf-16 -x
- تولید تحلیلگر لغوی که ورودی با کدگذاری UTF16 را میخواند. re2rust فرض میکند که محدودهٔ نویسهها 0 -- 0x10FFFF و اندازهٔ هر نویسه ۲ بایت است. این گزینه مستلزم --nested-ifs است.
- --utf32 --unicode -u
- تولید تحلیلگر لغوی که ورودی با کدگذاری UTF32 را میخواند. re2rust فرض میکند که محدودهٔ نویسهها 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
- گزینهٔ اشکالزدایی: خروجی دادن آمار مربوط به تعداد وضعیتها در بستار (closure).
- --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 که توسط re2rust استفاده میشود. گزینهٔ 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
است.
برچسبهای
"ثابت"
آنهایی
هستند که در
فاصلهٔ
ثابتی از یک
برچسب دیگر
(به نام
"پایه")
قرار دارند.
در چنین
مواردی فقط
برچسب پایه
نیاز به
ردگیری
دارد و
مقدار
برچسب ثابت
میتواند
برابر با
مقدار
برچسب پایه
بهعلاوهٔ
یک فاصلهٔ
ایستا
محاسبه شود.
برای
برچسبهایی
که در گزینش
یا تکرار
قرار
دارند،
همچنین
لازم است
بررسی شود
که آیا
برچسب پایه
دارای
مقدار عدم
تطابق است
یا خیر (در
این صورت
برچسب ثابت
نیز بدون در
نظر گرفتن
فاصله،
باید برابر
با عدم
تطابق
تنظیم شود).
برای
برچسبهای
موجود در
الحاق سطح
بالا نیازی
به این
بررسی
نیست، زیرا
آنها
همیشه
مطابقت
دارند.
هشدارها را میتوان بهصورت جداگانه فعال یا غیرفعال کرد و یا به یک خطا تبدیل نمود.
- -W
- فعال کردن تمام هشدارها.
- -Werror
- تبدیل هشدارها به خطا. توجه داشته باشید که این گزینه به تنهایی هیچ هشداری را فعال نمیکند؛ بلکه تنها بر هشدارهایی تأثیر میگذارد که تاکنون فعال شدهاند یا بعداً فعال خواهند شد.
- -W<warning>
- فعال کردن warning.
- -Wno-<warning>
- غیرفعال کردن warning.
- -Werror-<warning>
- فعال کردن warning و تلقی کردن آن بهعنوان خطا (این گزینه متضمن -W<warning> است).
- -Wno-error-<warning>
- این warning خاص را بهعنوان خطا تلقی نکن. این گزینه خودِ هشدار را غیرفعال نمیکند.
- -Wcondition-order
- هشدار در صورتی که برنامهٔ تولیدشده فرضیات ضمنی دربارهٔ شمارهگذاری وضعیتها داشته باشد. باید از گزینهٔ --header یا بلوک conditions برای تولید نگاشت نامهای وضعیت به اعداد استفاده کرد و سپس نامهای وضعیت تولیدشده بهصورت خودکار را به کار برد.
- -Wempty-character-class
- هشدار در صورتی که یک عبارت باقاعده شامل یک ردهٔ نویسهای خالی باشد. تلاش برای تطبیق یک ردهٔ نویسهای خالی هیچ مفهومی ندارد: همیشه باید ناموفق باشد. با این حال، به دلایل سازگاری با گذشته، re2rust ردههای نویسهای خالی را مجاز دانسته و با آنها مانند رشتههای خالی رفتار میکند. برای تغییر رفتار پیشفرض از گزینهٔ --empty-class استفاده کنید.
- -Wmatch-empty-string
- هشدار در صورتی که یک قاعده تهیپذیر باشد (با یک رشتهٔ خالی تطبیق یابد). اگر تحلیلگر لغوی در یک حلقه اجرا شود و تطبیق خالی غیرعمدی باشد، ممکن است تحلیلگر لغوی بهطور غیرمنتظرهای در یک حلقهٔ بیپایان متوقف شود.
- -Wswapped-range
- هشدار در صورتی که کران پایین یک بازه از کران بالای آن بزرگتر باشد. رفتار پیشفرض این است که بدون اعلام پیام، جای کرانهای بازه با یکدیگر عوض شود.
- -Wundefined-control-flow
- هشدار در صورتی که برخی از رشتههای ورودی باعث جریان کنترل تعریفنشده در تحلیلگر لغوی شوند (الگوهای معیوب گزارش میشوند). این یک خطای خطرناک و رایج است. میتوان آن را بهسادگی با افزودن قاعدهٔ پیشفرض * برطرف کرد که کمترین اولویت را دارد، با هر واحد کد تطبیق مییابد، و همیشه یک واحد کد منفرد را مصرف میکند.
- -Wunreachable-rules
- هشدار دربارهٔ قواعدی که توسط قواعد دیگر پوشانده شدهاند و هرگز تطبیق نخواهند یافت.
- -Wdeprecated-eof_rule
- هشدار دربارهٔ قواعد مستقل پایان ورودی $ که با تغییرات آینده از کار خواهند افتاد و نیاز به اصلاح دارند. در حال حاضر این قواعد هنگام تداخل با سایر قواعد اولویت دارند، اما پس از معرفی نماد تعمیمیافتهٔ پایان ورودی $ ترتیب اولویت تغییر خواهد کرد و این قواعد توسط سایر قواعد پوشانده خواهند شد.
- -Wuseless-escape
- هشدار در صورتی که یک نماد حاوی گریز باشد در حالی که نباید باشد. بهطور پیشفرض، re2rust چنین گریزهایی را نادیده میگیرد، اما این مورد میتواند بیانگر یک اشتباه تایپی یا خطا در دنبالهٔ گریز باشد.
- -Wnondeterministic-tags
- هشدار در صورتی که یک برچسب دارای درجهٔ n-ام از عدم قطعیت باشد، بهطوری که n بزرگتر از ۱ است.
- -Wsentinel-in-midrule
- هشدار در صورتی که نماد نگهبان در وسط یک قاعده رخ دهد --- این اتفاق ممکن است باعث خواندن فراتر از انتهای بافر، خرابی برنامه یا آسیب به حافظه در تحلیلگر لغوی تولیدشده شود. این هشدار تنها در صورتی قابل اعمال است که از روش نگهبان برای بررسی پایان ورودی استفاده شود. اگر از پیکربندی re2c:sentinel استفاده شود، به یک خطا تبدیل میگردد.
- -Wundefined-syntax-config
- هشدار در
صورتی که
فایل نحو
مشخصشده
با گزینهٔ
--syntax فاقد
تعاریف
برخی از
پیکربندیها
باشد. این
به نگهداری
فایلهای
نحوِ
تعریفشده
توسط کاربر
کمک میکند:
اگر در
نسخهٔ
جدیدی
پیکربندیهایی
اضافه شود،
فایل نحو
قدیمی
هشدار
خواهد داد و
به کاربر
اطلاع داده
میشود. اگر
برخی
پیکربندیها
بلااستفاده
هستند و
نیازی به
تعریف
ندارند،
باید
بهصراحت
روی <undefined>
تنظیم شوند.
پشتیبانی از زبانهای گوناگون در re2c بر پایه ایده فایلهای نحو (syntax files) بنا شده است. یک فایل نحو، یک فایل پیکربندی است که نحو زبان مقصد را تعریف میکند -- نه کل زبان، بلکه بخش کوچکی از آن را که توسط کد تولیدشده استفاده میشود. فایلهای نحو انعطافپذیری بالایی به re2c میبخشند، اما نباید به عنوان جایگزینی برای پیکربندیهای re2c: استفاده شوند: هدف آنها تعریف نحو زبان مقصد است، نه شخصیسازی یک تحلیلگر لغوی (lexer) خاص. تمام زبانهای پشتیبانیشده دارای فایلهای نحو پیشفرضی هستند که بخشی از توزیع به شمار میروند (زیردایرکتوری include/syntax را ببینید)؛ آنها همچنین در باینری re2rust گنجانده شدهاند. کاربران میتوانند یک فایل نحو سفارشی ارائه دهند که چند پیکربندی را برای یکی از زبانهای پشتیبانیشده بازنویسی میکند، یا میتوانند همه پیکربندیها را مجدداً تعریف کنند (در این حالت باید از گزینه --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
- فهرستی از مقاصد تولید کد (codegen) پشتیبانیشده با عناصر ممکن code، dot، skeleton.
- supported_features
- فهرستی از ویژگیهای پشتیبانیشده با عناصر ممکن nested-ifs، bitmaps، computed-gotos، case-ranges، monadic، unsafe، tags، captures، captvars.
پیکربندیهای زبان (Language configurations)
- semicolons
- مقداری غیر صفر اگر زبان پس از دستورها از نقطهویرگول (semicolon) استفاده میکند.
- backtick_quoted_strings
- مقداری غیر صفر اگر زبان دارای رشتههای محصور در بکتیک (backtick) باشد.
- single_quoted_strings
- مقداری غیر صفر اگر زبان دارای رشتههای محصور در کوتیشن تکی (single-quote) باشد.
- 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 ']'
یک الگوی کد دنبالهای از رشتههای لفظی (string literals)، متغیرها، عناصر اختیاری و فهرستها، یا ارجاعی به یک الگوی کد دیگر، یا مقدار ویژه <undefined> است. متغیرها جایگزینهایی (placeholders) هستند که در فاز تولید کد جایگزین میشوند. متغیرهای فهرستی خاص هستند: هنگام بسط الگوهای فهرستی، re2rust عبارتهای سمت راست دونقطه را چند بار تکرار میکند، و هر بار رخدادهای متغیر فهرست را با مقداری مخصوص به این تکرار جایگزین مینماید. فهرستها دارای کرانهای اختیاری هستند (مقادیر منفی از انتها شمرده میشوند، برای نمونه -1 به معنای آخرین عنصر است). نامهای شرطی با یک نقطه شروع میشوند. هم شرطها و هم متغیرها میتوانند محلی (مخصوص الگوی کد دادهشده) یا سراسری (مجاز در تمام الگوهای کد) باشند. هنگامی که re2rust فایل نحو را میخواند، بررسی میکند که هر الگوی کد فقط از متغیرها و شرطهایی استفاده کند که در آن مجاز هستند.
برای نمونه، الگوی کد زیر ساختار 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
}
در اینجا فهرستی از تمام الگوهای کد پشتیبانیشده توسط re2rust به همراه متغیرها و شرطهای محلی آنها آورده شده است. توجه داشته باشید که یک تعریف خاص میتواند، اما لزومی ندارد که از متغیرها و شرطهای محلی استفاده کند. هر الگوی کد استفادهنشده باید بر روی <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 (درست در صورتی که آرایه تغییرناپذیر باشد).
- code:array_global
- مشابه code:array_local، با این تفاوت که در سطح بالایی (top-level) استفاده میشود.
- code:array_elem
- ارجاع به یک عنصر از آرایه (جدول). متغیرهای پشتیبانیشده: array (نام آرایه) و index (اندیس عنصر).
- code:enum
- تعریف یک شمارش (ممکن است با ساختار زبانیِ ویژهای برای شمارشها تعریف شود، یا صرفاً به صورت چند مقدار ثابت مستقل). متغیرهای پشتیبانیشده عبارتند از type (نوع شمارشِ تعریفشده توسط کاربر یا نوع ثابتها)، elem (متغیر فهرستی که به نام هر عضو گسترش مییابد) و init (مقدارده اولیه برای هر عضو). شرایط: .init (درست در صورت وجود مقدارده اولیه).
- code:enum_elem
- عنصر شمارش (یک عضو از نوعِ شمارشیِ تعریفشده توسط کاربر یا نام یک ثابت، بسته به نحوه تعریف code:enum). متغیرهای پشتیبانیشده عبارتند از name (نام عنصر) و type (نوع آن).
- code:assign
- دستور انتساب. متغیرهای پشتیبانیشده عبارتند از lhs (سمت چپ) و rhs (سمت راست).
- code:type_int
- نوع عدد صحیح علامتدار.
- code:type_uint
- نوع عدد صحیح بدون علامت.
- code:type_yybm
- نوع عناصر در جدول yybm.
- code:type_yytarget
- نوع عناصر در جدول yytarget.
- code:type_yyctable
- نوع عناصر در جدول yyctable.
- code:cmp_eq
- عملگر "برابر با".
- code:cmp_ne
- عملگر "نابرابر با".
- code:cmp_lt
- عملگر "کوچکتر از".
- code:cmp_gt
- عملگر "بزرگتر از".
- code:cmp_le
- عملگر "کوچکتر یا مساوی".
- code:cmp_ge
- عملگر "بزرگتر یا مساوی".
- code:if_then_else
- دستور if-then-else با یک یا چند شاخه. متغیرهای پشتیبانیشده: branch (یک متغیر فهرستی که خود کدی تولید نمیکند، اما عبارت فهرست را به تعداد شاخهها بسط میدهد)، cond (شرط شاخه فعلی) و stmt (یک متغیر فهرستی که به تمام دستورات موجود در شاخه فعلی گسترش مییابد). شرایط: .cond (درست در صورتی که شاخه فعلی دارای شرط باشد)، .many (درست در صورتی که بیش از یک شاخه وجود داشته باشد).
- 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
- گروهی از موارد (cases) در دستور switch که به یک بلوک کد واحد نگاشت میشوند. متغیرهای پشتیبانیشده عبارتند از case (یک متغیر فهرستی که به تمام حالتها در این گروه گسترش مییابد) و stmt (یک متغیر فهرستی که به تمام دستورات موجود در بلوک کد گسترش مییابد).
- code:switch_cases_oneline
- شکل خاصی از code:switch_cases برای حالتی که بلوک کد شامل یک دستور تکخطی واحد است. اگر این مقدار <undefined> باشد، به جای آن از code:switch_cases استفاده میشود.
- code:switch_case_range
- یک حالتِ switch منفرد که محدودهای از مقادیر را پوشش میدهد (احتمالاً شامل یک مقدار واحد). متغیر پشتیبانیشده: val (یک متغیر فهرستی که به تمام مقادیر در آن محدوده گسترش مییابد). شرایط پشتیبانیشده: .many (درست اگر بیش از یک مقدار در محدوده وجود داشته باشد) و .char_literals (درست اگر این یک switch روی نویسههای لیترال باشد -- برخی زبانها نحو ویژهای برای این حالت ارائه میدهند).
- code:switch_case_default
- حالت پیشفرض (default) در دستور switch.
- code:loop
- حلقهای که تا ابد اجرا میشود (مگر اینکه اجرای آن از داخل بدنه متوقف شود). متغیرهای پشتیبانیشده: label (برچسب حلقه)، stmt (یک متغیر فهرستی که به تمام دستورات موجود در بدنه حلقه گسترش مییابد).
- code:continue
- دستور continue. متغیرهای پشتیبانیشده: label (برچسبی که اجرای برنامه باید از آن ادامه یابد).
- code:goto
- دستور goto. متغیرهای پشتیبانیشده: label (برچسب مقصد پرش).
- code:cgoto
- دستور goto محاسباتی (computed). متغیرهای پشتیبانیشده: array (جدول حاوی اطلاعات goto محاسباتی)، index (اندیس عنصر در جدول) و base (برچسب پایه، تنها زمانی استفاده میشود که .cgoto.relative درست باشد).
- code:cgoto:data
- عبارت مقداردهی اولیه برای یک عنصر منفرد در جدول goto محاسباتی. متغیرهای پشتیبانیشده: label (برچسبی که برای مقداردهی اولیه عنصر فعلی به کار میرود)، type (نوع اصلی عناصر موجود در جدول) و base (برچسب پایه - تنها زمانی استفاده میشود که .cgoto.relative درست باشد).
- code:fndecl
- اعلان تابع. متغیرهای پشتیبانیشده: name (نام تابع)، type (نوع بازگشتی)، throw (استثناهای پرتابشده توسط این تابع، به پیکربندی re2c:yyfn:throw نگاشت میشود)، arg (یک متغیر فهرستی که خود کدی تولید نمیکند، اما عبارت فهرست را به تعداد آرگومانهای تابع بسط میدهد)، argname (نام آرگومان فعلی)، argtype (نوع آرگومان فعلی). شرط: .type (درست در صورتی که این تابع از نوع non-void باشد).
- code:fndef
- مشابه code:fndecl، اما برای تعریف تابع به کار میرود؛ بنابراین یک متغیر فهرستی اضافی به نام stmt دارد که به تمام دستورات موجود در بدنه تابع گسترش مییابد.
- code:fncall
- دستور فراخوانی تابع. متغیرهای پشتیبانیشده: name (نام تابع)، retval (مقدار l-value که مقدار بازگشتی در آن ذخیره میشود، در صورت وجود) و arg (یک متغیر فهرستی که به تمام آرگومانهای تابع گسترش مییابد). شرایط: .args (درست اگر تابع دارای آرگومان باشد) و .retval (درست اگر ذخیره کردن مقدار بازگشتی الزامی باشد).
- code:tailcall
- دستور فراخوانی انتهایی (tail call). متغیرهای پشتیبانیشده: name (نام تابع)، و arg (یک متغیر فهرستی که به تمام آرگومانهای تابع گسترش مییابد). شرایط: .args (درست اگر تابع دارای آرگومان باشد) و .retval (درست در صورتی که این تابع non-void باشد).
- code:recursive_functions
- بدنه برنامه همراه با مدل کد --recursive-functions. متغیرهای پشتیبانیشده: fn (یک متغیر فهرستی که خود کدی تولید نمیکند، اما عبارت فهرست را به تعداد توابع بسط میدهد)، fndecl (اعلان تابع فعلی) و fndef (تعریف تابع فعلی)..TP code:fingerprint اثرانگشت در بالای فایل خروجی تولیدشده. متغیرهای پشتیبانیشده: ver (نسخهٔ re2rust که برای تولید استفاده شده است) و date (تاریخ تولید).
- code:line_info
- قالب دستورالعملهای خط (اگر روی <undefined> تنظیم شود، هیچ دستورالعملی تولید نمیشود). متغیرهای پشتیبانیشده: line (شمارهٔ خط) و file (نام فایل).
- code:abort
- دستوری که اجرای برنامه را متوقف (abort) میکند.
- code:yydebug
- دستور YYDEBUG، احتمالاً سفارشیشده برای APIهای مختلف. متغیرهای پشتیبانیشده: YYDEBUG، yyrecord، yych (نگاشت به پیکربندیهای متناظر re2c:)، state (شمارهٔ حالت DFA).
- 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 بیشتر از یک باشد)..TP 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 نگاشت میشود).
در اینجا فهرستی از تمام متغیرهای سراسری مجاز در فایلهای نحو آمده است:
در اینجا فهرستی از تمام شرطهای سراسری مجاز در فایلهای نحو آمده است:
- .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
- درست است اگر حلقههای تولیدشده توسط re2rust باید دارای برچسب باشند (re2c:label:yyloop روی یک رشتهٔ غیرخالی تنظیم شده باشد).
- .monadic
- درست است اگر کد تولیدشده باید به شیوهٔ مونادیک باشد (re2c:monadic = 1). این مورد تنها برای زبانهای تابعی محض کاربرد دارد.
- .start_conditions
- درست است اگر شرایط شروع فعال باشند (--start-conditions).
- .storable_state
- درست است اگر وضعیت قابل ذخیرهسازی فعال باشد (--storable-state).
- .unsafe
- درست است اگر re2rust باید از بلوکهای "unsafe" برای تولید کد سریعتر استفاده کند (--unsafe، re2c:unsafe = 1). این مورد تنها برای زبانهایی مرتبط است که ویژگی "unsafe" را دارند.
- .version
- درست است اگر اثرانگشت تولیدشده باید شامل نسخهٔ re2rust باشد.
- .yyfill.enable
- درست است اگر YYFILL فعال باشد (re2c:yyfill:enable = 1).
- .yyfn.throw
- درست است اگر پیکربندی re2c:yyfn:throw روی یک رشتهٔ غیرخالی تعریف شده باشد.
یکی از مسائل اصلی برای تحلیلگر لغوی (lexer)، دانستن زمان توقف است. چندین شرط پایانی وجود دارد:
- تحلیلگر لغوی ممکن است با قاعدهای تطابق یابد (شامل قاعده پیشفرض *) و به یک وضعیت نهایی برسد
- تحلیلگر لغوی ممکن است در تطابق با هر قاعدهای شکست بخورد و به یک وضعیت پیشفرض برسد
- تحلیلگر لغوی ممکن است به پایان ورودی برسد
دو شرط اول، تحلیلگر لغوی را به روشی "طبیعی" خاتمه میدهند: تحلیلگر به وضعیتی بدون گذارهای خروجی میرسد و تطابق بهطور خودکار متوقف میشود. شرط سوم، یعنی پایان ورودی، متفاوت است: ممکن است در هر وضعیتی رخ دهد و تحلیلگر لغوی باید قادر به مدیریت آن باشد. بررسی پایان ورودی، جریان کار عادی تحلیلگر لغوی را مختل کرده و شاخههای شرطی را به برنامهٔ تولیدشده اضافه میکند؛ بنابراین به حداقل رساندن تعداد چنین بررسیهایی ضروری است. re2rust از چند روش مختلف برای مدیریت پایان ورودی پشتیبانی میکند. اینکه از کدام روش استفاده شود به پیچیدگی عبارات منظم، نیاز به بافرسازی، ملاحظات کارایی و سایر عوامل بستگی دارد. در اینجا فهرستی از روشها آمده است:
- دیدهبان (Sentinel). این روش نیاز به بررسیهای پایان ورودی را بهطور کلی حذف میکند. این شیوه ساده و کارآمد است، اما محدود به حالتی است که یک نویسهٔ "دیدهبان" طبیعی وجود داشته باشد که هرگز در ورودی معتبر رخ ندهد. این نویسه همچنان ممکن است در ورودی نامعتبر رخ دهد، اما نباید توسط عبارات منظم مجاز شناخته شود، مگر شاید به عنوان آخرین نویسهٔ یک قاعده. نویسهٔ دیدهبان به انتهای ورودی الحاق شده و به عنوان سیگنال توقف عمل میکند: زمانی که تحلیلگر لغوی این نویسه را میخواند، یا خطای نحوی رخ داده یا پایان ورودی است. در هر دو حالت، تحلیلگر لغوی باید متوقف شود. این روش در صورتی استفاده میشود که YYFILL با re2c:yyfill:enable = 0; غیرفعال شده باشد و re2c:eof مقدار پیشفرض -1 را داشته باشد.
- دیدهبان با بررسی کرانهها (Sentinel with bounds checks). این روش عمومی است: اجازه میدهد هر ورودی بدون محدودیت روی عبارات منظم مدیریت شود. ایده این است که با انجام بررسیهای پایان ورودی تنها روی نویسههایی خاص، تعداد آنها کاهش یابد. مشابه روش "دیدهبان"، یکی از نویسهها به عنوان "دیدهبان" انتخاب شده و به انتهای ورودی الحاق میگردد. با این حال، هیچ محدودیتی در محل وقوع دیدهبان وجود ندارد (در واقع، هر نویسهای میتواند به عنوان دیدهبان انتخاب شود). هنگامی که تحلیلگر لغوی این نویسه را میخواند، علاوه بر آن یک بررسی کرانهها را انجام میدهد. اگر موقعیت فعلی در محدودهٔ مجاز باشد، تحلیلگر لغوی تطابق را از سر گرفته و با دیدهبان مانند یک نویسهٔ معمولی رفتار میکند. در غیر این صورت، YYFILL را فراخوانی میکند (مگر اینکه غیرفعال شده باشد). اگر ورودی بیشتری ارائه شود، تحلیلگر لغوی نویسهٔ آخر را دوباره تطبیق داده و به گونهای ادامه میدهد که گویی دیدهبان در آنجا نبوده است. در غیر این صورت، باید پایان واقعی ورودی باشد و تحلیلگر لغوی متوقف میشود. این روش زمانی استفاده میشود که re2c:eof مقداری غیرمنفی داشته باشد (باید روی مقدار عددی دیدهبان تنظیم شود). استفاده از YYFILL اختیاری است.
- بررسی کرانهها با پدینگ (Bounds checks with padding). این روش عمومی است و ممکن است سریعتر از روش "دیدهبان با بررسی کرانهها" باشد، اما پیچیدهتر نیز هست. ایده این است که وضعیتهای DFA به مؤلفههای قویاً همبند (SCCها) افراز شوند و برای هر SCC یک بررسی منفرد برای نویسههای کافی جهت پوشش طولانیترین مسیر بدون حلقه در آن SCC ایجاد گردد. این کار تعداد بررسیها را کاهش میدهد، اما مشکلی در مورد لکسمهای (lexeme) کوتاه در انتهای ورودی وجود دارد، زیرا بررسی نیازمند نویسههای کافی برای پوشش طولانیترین لکسم است. این مشکل میتواند با پدینگ (padding) ورودی با چند نویسهٔ ساختگی که پسوند لکسم معتبری را تشکیل نمیدهند (به طوری که تحلیلگر لغوی نتواند آنها را تطبیق دهد) برطرف شود. طول پدینگ باید YYMAXFILL باشد که با یک بلوک max تولید میشود. اگر ورودی کافی وجود نداشته باشد، تحلیلگر لغوی YYFILL را فراخوانی میکند که باید حداقل تعداد نویسههای مورد نیاز را فراهم کند یا بازنگردد. این روش در صورتی استفاده میشود که YYFILL فعال باشد و re2c:eof برابر با -1 باشد (این پیکربندی پیشفرض است).
- بررسیهای سفارشی (Custom checks). رابط برنامهنویسی عمومی (Generic API) امکان بازنویسی عملیات پایه مانند خواندن نویسه را فراهم میکند، که گنجاندن بررسیهای پایان ورودی را به عنوان بخشی از آنها ممکن میسازد. این رویکرد مستعد خطا است و باید با احتیاط استفاده شود. برای استفاده از یک روش سفارشی، رابط عمومی را با --api custom یا re2c:api = custom; فعال کرده و بررسیهای پیشفرض کرانهها را با re2c:yyfill:enable = 0; یا re2c:yyfill:check = 0; غیرفعال کنید.
زیربخشهای بعدی شامل یک مثال از هر روش هستند..SS نگهبان (Sentinel)
این مثال از یک نویسه نگهبان (sentinel) برای مدیریت پایان ورودی استفاده میکند. این برنامه کلمات جداشده با فاصله را در یک رشته پایانیافته با تهی (null-terminated) شمارش میکند. نگهبان، null است: این آخرین کاراکتر هر رشته ورودی است و توسط هیچیک از قواعد در میانهی یک نشانه (lexeme) مجاز دانسته نمیشود (بهویژه، در بازههای نویسهای که نادیدهگرفتن آن آسان است گنجانده نشده است). اگر یک null در میانهی یک رشته رخ دهد، این یک خطای نحو است و تحلیلگر لغوی قاعده پیشفرض * را مطابقت میدهد، اما فراتر از پایان ورودی را نخواهد خواند و دچار فروپاشی (crash) نخواهد شد (از هشدار -Wsentinel-in-midrule و پیکربندی re2c:sentinel برای بررسی این موضوع استفاده کنید). پیکربندی re2c:yyfill:enable = 0; از تولید بررسیهای مرزی و فراخوانیهای YYFILL جلوگیری میکند.
// re2rust $INPUT -o $OUTPUT --api simple
fn lex(yyinput: &[u8]) -> isize {
// The input must be null-terminated, otherwise the function has UB.
assert_eq!(yyinput.last(), Some(&0));
let mut yycursor = 0;
let mut count = 0;
'lex: loop { /*!re2c
re2c:YYCTYPE = u8;
re2c:yyfill:enable = 0;
* { return -1; }
[\x00] { return count; }
[a-z]+ { count += 1; continue 'lex; }
[ ]+ { continue 'lex; }
*/}
}
fn main() {
assert_eq!(lex(b"\x00"), 0);
assert_eq!(lex(b"one two three\x00"), 3);
assert_eq!(lex(b"f0ur\x00"), -1);
}
نگهبان همراه با بررسیهای مرزی (Sentinel with bounds checks)
این مثال از نگهبان به همراه بررسیهای مرزی برای مدیریت پایان ورودی استفاده میکند (این روش در نگارش 1.2 اضافه شد). این برنامه رشتههای محصور در تککوتیشن که با فاصله جدا شدهاند را شمارش میکند. نویسه نگهبان، null است که با پیکربندی re2c:eof = 0; مشخص میشود. همانند روش sentinel، کاراکتر null آخرین نویسه هر رشته ورودی است، اما در میانهی یک قاعده مجاز است (برای مثال، 'aaa\0aa'\0 ورودی معتبری است، اما 'aaa\0 یک خطای نحو است). بررسیهای مرزی در هر حالتی که با یک نویسه ورودی مطابقت داشته باشد تولید میشوند، اما محدوده آنها به شاخهای که null را مدیریت میکند محدود است. بررسیهای مرزی به شکل YYLIMIT <= YYCURSOR یا با API عمومی به صورت YYLESSTHAN(1) هستند. اگر شرط بررسی برقرار باشد، تحلیلگر لغوی به پایان ورودی رسیده است و باید متوقف شود (YYFILL با re2c:yyfill:enable = 0; غیرفعال شده است زیرا ورودی در یک بافر جا میشود؛ برای مثالی که از YYFILL استفاده میکند به بخش YYFILL with sentinel مراجعه کنید). رسیدن به انتهای ورودی سه احتمال را ایجاد میکند: اگر تحلیلگر در حالت اولیه باشد با قاعده پایان ورودی $ مطابقت مییابد، در غیر این صورت ممکن است به یک قاعده تطبیقیافته قبلی برگردد (از جمله قاعده پیشفرض *) یا به یک حالت پیشفرض برود که موجب -Wundefined-control-flow میشود.
// re2rust $INPUT -o $OUTPUT --api simple
fn lex(yyinput: &[u8]) -> isize {
// The input must be null-terminated, otherwise the function has UB.
assert_eq!(yyinput.last(), Some(&0));
let (mut yycursor, mut yymarker) = (0, 0);
let yylimit = yyinput.len() - 1; // null-terminator not included
let mut count = 0;
'lex: loop { /*!re2c
re2c:YYCTYPE = u8;
re2c:yyfill:enable = 0;
re2c:eof = 0;
str = ['] ([^'\\] | [\\][^])* ['];
* { return -1; }
$ { return count; }
str { count += 1; continue 'lex; }
[ ]+ { continue 'lex; }
*/}
}
fn main() {
assert_eq!(lex(b"\0"), 0);
assert_eq!(lex(b"'qu\0tes' 'are' 'fine: \\'' \0"), 3);
assert_eq!(lex(b"'unterminated\\'\0"), -1);
}
این مثال از بررسیهای مرز همراه با پدینگ برای مدیریت انتهای ورودی استفاده میکند (این روش بهطور پیشفرض فعال است). این برنامه رشتههای محصور در علامت نقلقول تکی که با فاصله از هم جدا شدهاند را شمارش میکند. پدینگی متشکل از YYMAXFILL نویسهٔ null به انتهای ورودی ضمیمه شده است، که مقدار YYMAXFILL در آن بهطور خودکار توسط یک بلوک max تولید میشود. نیازی نیست که حتماً از null برای پدینگ استفاده شود --- هر نویسهای میتواند استفاده شود تا زمانی که پسوند معتبری برای لکسم (lexeme) نسازد (در این مثال پدینگ نباید شامل علامتهای نقلقول تکی باشد، چرا که ممکن است بهاشتباه بهعنوان پسوند یک رشتهٔ درون نقلقول تکی در نظر گرفته شود). یک قاعدهٔ "stop" وجود دارد که با نخستین نویسهٔ پدینگ (null) مطابقت مییابد و لکسر (lexer) را متوقف میکند (توجه داشته باشید که این قاعده بررسی میکند آیا null در ابتدای پدینگ قرار دارد یا خیر، در غیر این صورت یک خطای نحوی رخ میدهد). بررسیهای مرز تنها در وضعیتهای خاصی تولید میشوند که توسط مؤلفههای قویاً همبند خودکارِ پایه تعیین شدهاند. این بررسیها دارای قالب (YYLIMIT - YYCURSOR) < n یا YYLESSTHAN(n) در API عمومی هستند، که در آن n حداقل تعداد نویسههای لازم برای ادامهٔ کار لکسر است (این همچنین بدین معناست که بررسی مرز بعدی حداکثر پس از n نویسه رخ خواهد داد). اگر شرط بررسی برقرار باشد، لکسر به انتهای ورودی رسیده است و YYFILL(n) را فراخوانی خواهد کرد که باید یا حداقل n نویسهٔ ورودی را تأمین کند یا بازگشت نکند. در این مثال، YYFILL همواره با شکست مواجه میشود و لکسر را با یک خطا متوقف میکند (که مشکلی ایجاد نمیکند زیرا تمام ورودی درون یک بافر جای میگیرد). برای مشاهدهٔ مثالی که بافر ورودی را با YYFILL دوباره پر میکند، بخش YYFILL with padding را ببینید.
// re2rust $INPUT -o $OUTPUT --api simple
/*!max:re2c*/
fn lex(s: &[u8]) -> isize {
let mut count = 0;
let mut yycursor = 0;
let yylimit = s.len() + YYMAXFILL;
// Copy string to a buffer and add YYMAXFILL zero padding.
let mut yyinput = Vec::with_capacity(yylimit);
yyinput.extend_from_slice(s);
yyinput.extend([0 as u8; YYMAXFILL]);
'lex: loop { /*!re2c
re2c:YYCTYPE = u8;
re2c:YYFILL = "return -1;";
str = ['] ([^'\\] | [\\][^])* ['];
[\x00] {
// Check that it is the sentinel, not some unexpected null.
return if yycursor == s.len() + 1 { count } else { -1 }
}
str { count += 1; continue 'lex; }
[ ]+ { continue 'lex; }
* { return -1; }
*/}
}
fn main() {
assert_eq!(lex(b""), 0);
assert_eq!(lex(b"'qu\0tes' 'are' 'fine: \\'' "), 3);
assert_eq!(lex(b"'unterminated\\'"), -1);
assert_eq!(lex(b"'unexpected \0 null"), -1);
}
بررسیهای سفارشی (Custom checks)
این مثال از یک روش سفارشی برای مدیریت انتهای ورودی بر پایهٔ generic API استفاده میکند. این برنامه رشتههای محصور در علامت نقلقول تکی که با فاصله جدا شدهاند را شمارش میکند. این مثال مشابه با مثال sentinel است، با این تفاوت که ورودی به null ختم نمیشود. برای جبران نبودِ نویسهٔ نگهبان در انتهای ورودی، YYPEEK بازتعریف شده است تا پیش از خواندن نویسهٔ بعدی ورودی، یک بررسی مرز انجام دهد. این روش ناکارآمد است زیرا بررسیها بهطور مکرر انجام میشوند. اگر شرط بررسی ناموفق باشد، YYPEEK نویسهٔ واقعی را برمیگرداند، در غیر این صورت یک نویسهٔ نگهبان ساختگی را برمیگرداند.
// re2rust $INPUT -o $OUTPUT
// Expect a string without terminating null.
fn lex(s: &[u8]) -> isize {
let mut count = 0;
let mut cur = 0;
let lim = s.len();
'lex: loop {/*!re2c
re2c:YYCTYPE = u8;
re2c:YYPEEK = "if cur < lim {*s.get_unchecked(cur)} else {0}";
re2c:YYSKIP = "cur += 1;";
re2c:yyfill:enable = 0;
* { return -1; }
[\x00] { return count; }
[a-z]+ { count += 1; continue 'lex; }
[ ]+ { continue 'lex; }
*/}
}
fn main() {
assert_eq!(lex(b""), 0);
assert_eq!(lex(b"one two three "), 3);
assert_eq!(lex(b"f0ur"), -1);
}
نیاز به بافربندی زمانی رخ میدهد که ورودی را نتوان به طور یکجا در حافظه نگاشت کرد: یا داده بسیار حجیم است، یا به صورت جریانی (مانند خواندن از یک سوکت) دریافت میشود. روش معمول در چنین مواردی تخصیص یک بافر حافظه با اندازهٔ ثابت و پردازش ورودی در تکههایی (chunks) است که در این بافر جا میگیرند. هنگامی که تکهٔ جاری پردازش شد، به بیرون منتقل شده و دادههای جدید وارد میشوند. در عمل این روند تا حدی پیچیدهتر است، زیرا وضعیت تحلیلگر واژگانی (lexer) تنها از یک موقعیت ورودی تشکیل نشده، بلکه مجموعهای از موقعیتهای مرتبط با یکدیگر است:
- مکاننما (cursor): نویسهٔ ورودی بعدی که باید خوانده شود (YYCURSOR در C pointer API یا YYSKIP/YYPEEK در generic API)
- حد (limit): موقعیتِ پس از آخرین نویسهٔ موجود ورودی (YYLIMIT در C pointer API، که به صورت ضمنی توسط YYLESSTHAN در generic API مدیریت میشود)
- نشانهگذار (marker): موقعیت تازهترین تطابق، در صورت وجود (YYMARKER در API پیشفرض یا YYBACKUP/YYRESTORE در generic API)
- توکن (token): ابتدای لکسم (lexeme) فعلی (در re2rust API به صورت ضمنی است، زیرا برای عملکرد عادی لکسر نیازی به آن نیست و میتواند توسط کاربر تعریف و بهروزرسانی شود)
- نشانهگذار زمینه (context marker): موقعیت زمینهٔ انتهایی (YYCTXMARKER در C pointer API یا 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 در C pointer API و YYLESSTHAN() در generic API فعال میشود. مقدار بازگشتی غیرصفر به این معنی است که YYFILL با شکست مواجه شده است. یک فراخوانی موفق YYFILL باید حداقل یک نویسه فراهم کرده و موقعیتهای ورودی را بر همان اساس تنظیم کند. حد (limit) باید همیشه یک موقعیت بعد از آخرین ورودی در بافر تنظیم شود، و نویسهٔ موجود در موقعیت limit باید نماد نگهبان (sentinel) مشخصشده با پیکربندی 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 استفاده میکند.
// re2rust $INPUT -o $OUTPUT
use std::fs::File;
use std::io::{Read, Write};
const BUFSIZE: usize = 4096;
struct State {
file: File,
yyinput: [u8; BUFSIZE],
yylimit: usize,
yycursor: usize,
yymarker: usize,
token: usize,
eof: bool,
}
#[derive(PartialEq)]
enum Fill { Ok, Eof, LongLexeme }
fn fill(st: &mut State) -> Fill {
if st.eof { return Fill::Eof; }
// Error: lexeme too long. In real life could reallocate a larger buffer.
if st.token < 1 { return Fill::LongLexeme; }
// Shift buffer contents (discard everything up to the current token).
st.yyinput.copy_within(st.token..st.yylimit, 0);
st.yylimit -= st.token;
st.yycursor -= st.token;
st.yymarker = st.yymarker.overflowing_sub(st.token).0; // may underflow if marker is unused
st.token = 0;
// Fill free space at the end of buffer with new data from file.
match st.file.read(&mut st.yyinput[st.yylimit..BUFSIZE - 1]) { // -1 for sentinel
Ok(n) => {
st.yylimit += n;
st.eof = n == 0; // end of file
st.yyinput[st.yylimit] = 0; // append sentinel
}
Err(why) => panic!("cannot read from file: {}", why)
}
return Fill::Ok;
}
fn lex(yyrecord: &mut State) -> isize {
let mut count: isize = 0;
'lex: loop {
yyrecord.token = yyrecord.yycursor;
/*!re2c
re2c:api = record;
re2c:YYCTYPE = u8;
re2c:YYFILL = "fill(yyrecord) == Fill::Ok";
re2c:eof = 0;
str = ['] ([^'\\] | [\\][^])* ['];
* { return -1; }
$ { return count; }
str { count += 1; continue 'lex; }
[ ]+ { continue 'lex; }
*/}
}
fn main() {
let fname = "input";
let content = b"'qu\0tes' 'are' 'fine: \\'' ";
// Prepare input file: a few times the size of the buffer, containing
// strings with zeroes and escaped quotes.
match File::create(fname) {
Err(why) => panic!("cannot open {}: {}", fname, why),
Ok(mut file) => match file.write_all(&content.repeat(BUFSIZE)) {
Err(why) => panic!("cannot write to {}: {}", fname, why),
Ok(_) => {}
}
};
let count = 3 * BUFSIZE; // number of quoted strings written to file
// Reopen input file for reading.
let file = match File::open(fname) {
Err(why) => panic!("cannot read file {}: {}", fname, why),
Ok(file) => file,
};
// Initialize lexer state: all offsets are at the end of buffer.
let yylimit = BUFSIZE - 1;
let mut st = State {
file: file,
// Sentinel (at `yylimit` offset) is set to null, which triggers YYFILL.
yyinput: [0; BUFSIZE],
yylimit: yylimit,
yycursor: yylimit,
yymarker: yylimit,
token: yylimit,
eof: false,
};
// Run the lexer.
assert_eq!(lex(&mut st), count as isize);
// Cleanup: remove input file.
match std::fs::remove_file(fname) {
Err(why) => panic!("cannot remove {}: {}", fname, why),
Ok(_) => {}
}
}
در حالت پیشفرض (زمانی که از قاعدهٔ EOF استفاده نمیشود)، YYFILL یک سازوکار پایهای (primitive) تابعمانند است که یک آرگومان میپذیرد و هیچ مقداری را بازنمیگرداند. فراخوانی YYFILL با شرط (YYLIMIT - YYCURSOR) < n در API اشارهگر C و YYLESSTHAN(n) در API عمومی راهاندازی میشود. آرگومان ارائهشده به YYFILL کمینه تعداد نویسههایی است که باید تأمین شوند. اگر نتواند این کار را انجام دهد، YYFILL نباید کنترل را به لکسر بازگرداند (به همین دلیل بهتر است به صورت ماکرویی پیادهسازی شود که در صورت شکست، از تابع فراخواننده خارج شود). در صورت فراخوانی موفقیتآمیز 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 را در قطعات ۴۰۹۶ بایتی میخواند و از بررسی حدود به همراه لایهگذاری استفاده میکند.
// re2rust $INPUT -o $OUTPUT
use std::fs::File;
use std::io::{Read, Write};
/*!max:re2c*/
const BUFSIZE: usize = 4096;
struct State {
file: File,
yyinput: [u8; BUFSIZE],
yylimit: usize,
yycursor: usize,
yymarker: usize,
token: usize,
eof: bool,
}
#[derive(PartialEq)]
enum Fill { Ok, Eof, LongLexeme }
fn fill(st: &mut State, need: usize) -> Fill {
if st.eof { return Fill::Eof; }
// Error: lexeme too long. In real life can reallocate a larger buffer.
if st.token < need { return Fill::LongLexeme; }
// Shift buffer contents (discard everything up to the current token).
st.yyinput.copy_within(st.token..st.yylimit, 0);
st.yylimit -= st.token;
st.yycursor -= st.token;
st.yymarker = st.yymarker.overflowing_sub(st.token).0; // underflows if marker is unused
st.token = 0;
// Fill free space at the end of buffer with new data from file.
let n = match st.file.read(&mut st.yyinput[st.yylimit..BUFSIZE - YYMAXFILL]) {
Ok(n) => n,
Err(why) => panic!("cannot read from file: {}", why)
};
st.yylimit += n;
// If read zero characters, this is end of input => add zero padding
// so that the lexer can access characters at the end of buffer.
if n == 0 {
st.eof = true;
for i in 0..YYMAXFILL { st.yyinput[st.yylimit + i] = 0; }
st.yylimit += YYMAXFILL;
}
return Fill::Ok;
}
fn lex(yyrecord: &mut State) -> isize {
let mut count: isize = 0;
'lex: loop {
yyrecord.token = yyrecord.yycursor;
/*!re2c
re2c:api = record;
re2c:YYCTYPE = u8;
re2c:YYFILL = "if fill(yyrecord, @@) != Fill::Ok { return -1; }";
str = ['] ([^'\\] | [\\][^])* ['];
[\x00] {
// Check that it is the sentinel, not some unexpected null.
return if yyrecord.token == yyrecord.yylimit - YYMAXFILL { count } else { -1 }
}
str { count += 1; continue 'lex; }
[ ]+ { continue 'lex; }
* { return -1; }
*/}
}
fn main() {
let fname = "input";
let content = b"'qu\0tes' 'are' 'fine: \\'' ";
// Prepare input file: a few times the size of the buffer, containing
// strings with zeroes and escaped quotes.
match File::create(fname) {
Err(why) => panic!("cannot open {}: {}", fname, why),
Ok(mut file) => match file.write_all(&content.repeat(BUFSIZE)) {
Err(why) => panic!("cannot write to {}: {}", fname, why),
Ok(_) => {}
}
};
let count = 3 * BUFSIZE; // number of quoted strings written to file
// Reopen input file for reading.
let file = match File::open(fname) {
Err(why) => panic!("cannot read file {}: {}", fname, why),
Ok(file) => file,
};
// Initialize lexer state: all offsets are at the end of buffer.
// This immediately triggers YYFILL, as the YYLESSTHAN condition is true.
let yylimit = BUFSIZE - YYMAXFILL;
let mut st = State {
file: file,
yyinput: [0; BUFSIZE],
yylimit: yylimit,
yycursor: yylimit,
yymarker: yylimit,
token: yylimit,
eof: false,
};
// Run the lexer.
assert_eq!(lex(&mut st), count as isize);
// Cleanup: remove input file.
match std::fs::remove_file(fname) {
Err(why) => panic!("cannot remove {}: {}", fname, why),
Ok(_) => {}
}
}
چندین بلوک (Multiple blocks)
گاهی اوقات لازم است چندین تحلیلگر لغویِ (lexer) مرتبط با یکدیگر داشته باشیم (برای نمونه، زمانی که یک ماشین وضعیت سطح بالا وجود دارد که بین حالتهای مختلف تحلیلگر جابهجا میشود). این کار میتواند با استفاده از چندین بلوک متصل re2rust پیادهسازی شود. گزینهٔ دیگر استفاده از start conditions است.
پیادهسازی ارتباط بین بلوکها به زبان مقصد بستگی دارد. در زبانهایی که دستور goto دارند (مانند C/C++ و Go)، میتوان همهٔ بلوکها را در یک تابع قرار داد و پیش از هر بلوک یک برچسب (label) تعریف کرد. انتقال از یک بلوک به بلوک دیگر با یک goto ساده انجام میشود. در زبانهایی که فاقد goto هستند (مانند Rust)، استفاده از یک حلقه همراه با switch روی یک متغیر وضعیت ضروری است؛ مشابه حلقه/switch متغیر yystate که توسط re2rust تولید میشود، و یا اینکه هر بلوک درون یک تابع محصور شده و از فراخوانی توابع استفاده شود.
مثال زیر از چندین بلوک برای تجزیهٔ اعداد دودویی (binary)، هشتهشتی (octal)، دهدهی (decimal) و شانزدهشانزدهی (hexadecimal) استفاده میکند. هر مبنا بلوک مخصوص به خود را دارد. بلوک اولیه مبنا را تعیین کرده و به بلوکهای دیگر هدایت میکند. پیکربندیهای مشترک در بلوکی جداگانه در ابتدای برنامه تعریف شدهاند؛ این تنظیمات توسط بلوکهای دیگر به ارث برده میشوند.
// re2rust $INPUT -o $OUTPUT
// Store u32 number in u64 during parsing to simplify overflow handling.
struct State<'a> {
yyinput: &'a [u8],
yycursor: usize,
yymarker: usize,
num: u64,
}
/*!re2c // Common re2c definitions shared between all functions.
re2c:api = record;
re2c:yyrecord = st;
re2c:yyfill:enable = 0;
re2c:YYCTYPE = u8;
*/
const ERROR: u64 = std::u32::MAX as u64 + 1; // overflow
macro_rules! maybe { // Convert the number from u64 to optional u32.
($n:expr) => { if $n < ERROR { Some($n as u32) } else { None } }
}
// Add digit with the given base, checking for overflow.
fn add(st: &mut State, offs: u8, base: u64) {
let digit = unsafe { st.yyinput.get_unchecked(st.yycursor - 1) } - offs;
st.num = std::cmp::min(st.num * base + digit as u64, ERROR);
}
fn parse_u32(s: & [u8]) -> Option<u32> {
assert_eq!(s.last(), Some(&0)); // expect null-terminated input
let mut st = State {yyinput: s, yycursor: 0, yymarker: 0, num: 0};
/*!re2c
'0b' / [01] { return parse_bin(&mut st); }
"0" { return parse_oct(&mut st); }
"" / [1-9] { return parse_dec(&mut st); }
'0x' / [0-9a-fA-F] { return parse_hex(&mut st); }
* { return None; }
*/
}
fn parse_bin(st: &mut State) -> Option<u32> {
'bin: loop {/*!re2c
[01] { add(st, 48, 2); continue 'bin; }
* { return maybe!(st.num); }
*/}
}
fn parse_oct(st: &mut State) -> Option<u32> {
'oct: loop {/*!re2c
[0-7] { add(st, 48, 8); continue 'oct; }
* { return maybe!(st.num); }
*/}
}
fn parse_dec(st: &mut State) -> Option<u32> {
'dec: loop {/*!re2c
[0-9] { add(st, 48, 10); continue 'dec; }
* { return maybe!(st.num); }
*/}
}
fn parse_hex(st: &mut State) -> Option<u32> {
'hex: loop {/*!re2c
[0-9] { add(st, 48, 16); continue 'hex; }
[a-f] { add(st, 87, 16); continue 'hex; }
[A-F] { add(st, 55, 16); continue 'hex; }
* { return maybe!(st.num); }
*/}
}
fn main() {
assert_eq!(parse_u32(b"\0"), None);
assert_eq!(parse_u32(b"1234567890\0"), Some(1234567890));
assert_eq!(parse_u32(b"0b1101\0"), Some(13));
assert_eq!(parse_u32(b"0x7Fe\0"), Some(2046));
assert_eq!(parse_u32(b"0644\0"), Some(420));
assert_eq!(parse_u32(b"9999999999\0"), None);
}
شرایط شروع با گزینهٔ --start-conditions فعال میشوند. آنها روشی را برای کدگذاری چندین خودکارهٔ (اتوماتای) مرتبط با یکدیگر در یک بلوک re2rust فراهم میکنند.
هر شرط متناظر با یک خودکارهٔ واحد است و نام یکتایی دارد که توسط کاربر مشخص میشود و شمارهٔ داخلی یکتایی دارد که توسط re2rust تعریف میشود. این شمارهها برای جابهجایی میان شرایط به کار میروند: کد تولیدشده از اولیههای 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 dispatch) به شروع شرط پرش کرد).
- < > => condition code
- مشابه قاعدهٔ قبلی، با این تفاوت که condition بعدی را تعیین میکند.
- < > :=> condition
- مشابه قاعدهٔ قبلی، با این تفاوت که کد مرتبطی ندارد و بلافاصله به condition پرش میکند.
کدی که re2rust برای شرایط تولید میکند بستگی به این دارد که re2rust از رویکرد goto/label یا رویکرد loop/switch برای کدگذاری خودکارهها استفاده کند.
در زبانهایی که دارای دستور goto هستند (مانند C/C++ و Go)، شرایط بهطور طبیعی بهصورت بلوکهایی از کد با برچسبهایی به شکل yyc_<cond> پیادهسازی میشوند، که در آن cond نام شرط است (پیشوند برچسب را میتوان با re2c:cond:prefix تغییر داد). انتقالها بین شرایط با استفاده از goto و برچسبهای شرط پیادهسازی میشوند. پیش از همهٔ شرایط، re2rust یک switch اولیه بر روی YYGETSTATE تولید میکند که به وضعیت شروع شرط فعلی پرش میکند. قواعد میانبر :=> از switch اولیه عبور کرده و مستقیماً به شرط مشخصشده پرش میکنند (از re2c:cond:goto میتوان برای تغییر رفتار پیشفرض استفاده کرد). قواعد دارای عملهای معنایی بهطور خودکار به شرط بعدی پرش نمیکنند؛ این کار باید توسط کد عمل تعریفشده توسط کاربر انجام شود.
در زبانهایی که فاقد goto هستند (مانند Rust)، re2rust متغیر yystate را برای ذخیرهٔ شمارههای شرط بازاستفاده میکند. هر شرط یک شناسهٔ عددی برابر با شمارهٔ وضعیت شروع خود دریافت میکند، و جابهجایی میان شرایط تفاوتی با جابهجایی میان وضعیتهای DFA در یک شرط واحد ندارد. نیازی به یک switch اولیهٔ جداگانه برای شرایط نیست. (از آنجا که رویکرد یکسانی برای پیادهسازی وضعیتهای قابل ذخیره استفاده میشود، در صورت استفادهٔ همزمان از وضعیتهای قابل ذخیره و شرایط، YYGETCOND/YYSETCOND زائد خواهند بود).
برنامهٔ زیر از شرایط شروع برای تجزیهٔ اعداد دودویی (باینری)، هشتهشتی (اوکتال)، دهدهی (دسیمال) و شانزدهشانزدهی (هگزادسیمال) استفاده میکند. یک بلوک واحد وجود دارد که در آن هر مبنا شرط مخصوص به خود را دارد و شرط اولیه به همهٔ آنها متصل است. متغیر تعریفشده توسط کاربر cond شمارهٔ شرط فعلی را ذخیره میکند؛ این متغیر با شمارهٔ شرط اولیه که توسط بلوک conditions تولید شده است مقداردهی اولیه میشود.
// re2rust $INPUT -o $OUTPUT -c --api simple
/*!conditions:re2c*/
const ERROR: u64 = std::u32::MAX as u64 + 1; // overflow
// Add digit with the given base, checking for overflow.
fn add(num: &mut u64, str: &[u8], cur: usize, offs: u8, base: u64) {
let digit = unsafe { str.get_unchecked(cur - 1) } - offs;
*num = std::cmp::min(*num * base + digit as u64, ERROR);
}
fn parse_u32(yyinput: &[u8]) -> Option<u32> {
assert_eq!(yyinput.last(), Some(&0)); // expect null-terminated input
let (mut yycursor, mut yymarker) = (0, 0);
let mut yycond = YYC_INIT;
let mut num = 0u64; // Store number in u64 to simplify overflow checks.
'lex: loop { /*!re2c
re2c:YYCTYPE = u8;
re2c:yyfill:enable = 0;
<INIT> '0b' / [01] :=> BIN
<INIT> "0" :=> OCT
<INIT> "" / [1-9] :=> DEC
<INIT> '0x' / [0-9a-fA-F] :=> HEX
<INIT> * { return None; }
<BIN> [01] { add(&mut num, yyinput, yycursor, 48, 2); continue 'lex; }
<OCT> [0-7] { add(&mut num, yyinput, yycursor, 48, 8); continue 'lex; }
<DEC> [0-9] { add(&mut num, yyinput, yycursor, 48, 10); continue 'lex; }
<HEX> [0-9] { add(&mut num, yyinput, yycursor, 48, 16); continue 'lex; }
<HEX> [a-f] { add(&mut num, yyinput, yycursor, 87, 16); continue 'lex; }
<HEX> [A-F] { add(&mut num, yyinput, yycursor, 55, 16); continue 'lex; }
<BIN, OCT, DEC, HEX> * {
return if num < ERROR { Some(num as u32) } else { None };
}
*/}
}
fn main() {
assert_eq!(parse_u32(b"\0"), None);
assert_eq!(parse_u32(b"1234567890\0"), Some(1234567890));
assert_eq!(parse_u32(b"0b1101\0"), Some(13));
assert_eq!(parse_u32(b"0x7Fe\0"), Some(2046));
assert_eq!(parse_u32(b"0644\0"), Some(420));
assert_eq!(parse_u32(b"9999999999\0"), None);
}
با گزینهٔ --storable-state، ابزار re2rust تحلیلگری لغوی تولید میکند که میتواند وضعیت فعلی خود را ذخیره کرده، به فراخوان بازگردد و بعداً عملیات را دقیقاً از همان جایی که متوقف شده بود از سر بگیرد. حالت پیشفرض عملکرد در re2rust یک مدل «کِششی» ("pull") است که در آن، تحلیلگر لغوی هر زمان که به ورودی بیشتری نیاز داشته باشد آن را دریافت میکند. این امر ممکن است در مواردی که ورودی به صورت تکهتکه در دسترس قرار میگیرد غیرقابلقبول باشد (برای نمونه، اگر تحلیلگر لغوی توسط تجزیهکننده فراخوانی شود، یا اگر برنامهٔ تحلیلگر لغوی از طریق یک پروتکل سوکت با برنامهٔ دیگری ارتباط برقرار کند که باید پیش از ارسال پیام بعدی منتظر پاسخی از تحلیلگر لغوی بماند). قابلیت وضعیت ذخیرهپذیر دقیقاً برای چنین مواردی در نظر گرفته شده است: این ویژگی امکان تولید تحلیلگرهای لغوی را فراهم میکند که در یک مدل «فشاری» ("push") کار میکنند. هنگامی که تحلیلگر لغوی به ورودی بیشتری نیاز پیدا کند، وضعیت خود را ذخیره کرده و به فراخوان بازمیگردد. سپس، هنگامی که ورودی بیشتری در دسترس قرار گیرد، فراخوان تحلیلگر لغوی را دقیقاً از جایی که متوقف شده بود ادامه میدهد. در مقایسه با مدل «کِششی» ("pull")، چند تغییر لازم است:
- تعریف عملگرهای پایهای YYSETSTATE() و YYGETSTATE(state).
- تعریف متغیرهای yych، yyaccept (در صورت استفاده) و state به عنوان بخشی از وضعیت ماندگار تحلیلگر لغوی. متغیر state باید با -1 مقداردهی اولیه شود.
- دستور YYFILL باید به جای تلاش برای تأمین ورودی بیشتر، به برنامهٔ بیرونی بازگردد. کد بازگشتی باید نشان دهد که تحلیلگر لغوی به ورودی بیشتری نیاز دارد.
- برنامهٔ بیرونی باید شرایطی را که تحلیلگر لغوی به ورودی بیشتری نیاز دارد تشخیص داده و واکنش مناسب نشان دهد.
- استفادهٔ اختیاری از بلوک getstate برای تولید سوئیچ YYGETSTATE جدا از تحلیلگر لغوی اصلی. این مورد تنها برای زبانهایی کار میکند که دارای goto هستند (نه در حالت --loop-switch).
- استفاده از re2c:eof و روش sentinel with bounds checks برای مدیریت پایان ورودی. روش مبتنی بر لایهگذاری (padding) ممکن است کار نکند، چرا که زمان اضافه کردن لایهگذاری مشخص نیست: پایان فعلی ورودی ممکن است پایان نهایی ورودی نباشد، و افزودن بیش از حد زودهنگام لایهگذاری ممکن است یک نشانهٔ (lexeme) حریصانه را که تا حدی خوانده شده قطع کند. علاوه بر این، به دلیل منطق سطحبالای برنامه، دریافت ورودی بیشتر ممکن است به پردازش نشانه در انتهای بافر وابسته باشد (که از قبل به دلیل شرط پایان ورودی مسدود شده است).
در اینجا مثالی از یک تحلیلگر لغوی با مدل «فشاری» ("push") آورده شده است که خواندن بستهها از یک سوکت را شبیهسازی میکند. تحلیلگر لغوی حلقه میزند تا زمانی که به پایان ورودی برسد و به تابع فراخوان بازگردد. تابع فراخوان با «ارسال» بستهٔ بعدی ورودی بیشتری را فراهم کرده و تحلیل لغوی را از سر میگیرد. این فرایند زمانی متوقف میشود که تمام بستهها ارسال شده باشند یا خطایی رخ دهد.
// re2rust $INPUT -o $OUTPUT -f
use std::fs::File;
use std::io::{Read, Write};
const DEBUG: bool = false;
macro_rules! log {
($($fmt:expr)? $(, $args:expr)*) => {
if DEBUG { println!($($fmt)? $(, $args)*) }
}
}
// Use a small buffer to cover the case when a lexeme doesn't fit.
// In real world use a larger buffer.
const BUFSIZE: usize = 10;
struct State {
file: File,
yyinput: [u8; BUFSIZE],
yylimit: usize,
yycursor: usize,
yymarker: usize,
token: usize,
yystate: isize,
}
#[derive(Debug, PartialEq)]
enum Status {End, Ready, Waiting, BadPacket, BigPacket}
fn fill(st: &mut State) -> Status {
// Error: lexeme too long. In real life can reallocate a larger buffer.
if st.token < 1 { return Status::BigPacket; }
// Shift buffer contents (discard everything up to the current lexeme).
st.yyinput.copy_within(st.token..st.yylimit, 0);
st.yylimit -= st.token;
st.yycursor -= st.token;
st.yymarker = st.yymarker.overflowing_sub(st.token).0; // underflows if marker is unused
st.token = 0;
// Fill free space at the end of buffer with new data.
match st.file.read(&mut st.yyinput[st.yylimit..BUFSIZE - 1]) { // -1 for sentinel
Ok(n) => {
st.yylimit += n;
st.yyinput[st.yylimit] = 0; // append sentinel symbol
},
Err(why) => panic!("cannot read from file: {}", why)
}
return Status::Ready;
}
fn lex(yyrecord: &mut State, recv: &mut usize) -> Status {
let mut yych;
'lex: loop {
yyrecord.token = yyrecord.yycursor;
/*!re2c
re2c:api = record;
re2c:eof = 0;
re2c:YYCTYPE = "u8";
re2c:YYFILL = "return Status::Waiting;";
packet = [a-z]+[;];
* { return Status::BadPacket; }
$ { return Status::End; }
packet { *recv += 1; continue 'lex; }
*/}
}
fn test(packets: Vec<&[u8]>, expect: Status) {
// Create a pipe (open the same file for reading and writing).
let fname = "pipe";
let mut fw: File = match File::create(fname) {
Err(why) => panic!("cannot open {}: {}", fname, why),
Ok(file) => file,
};
let fr: File = match File::open(fname) {
Err(why) => panic!("cannot read file {}: {}", fname, why),
Ok(file) => file,
};
// Initialize lexer state: `state` value is -1, all offsets are at the end
// of buffer, the character at `yylimit` offset is the sentinel (null).
let yylimit = BUFSIZE - 1;
let mut state = State {
file: fr,
// Sentinel (at `yylimit` offset) is set to null, which triggers YYFILL.
yyinput: [0; BUFSIZE],
yylimit: yylimit,
yycursor: yylimit,
yymarker: yylimit,
token: yylimit,
yystate: -1,
};
// 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.
let mut status;
let mut send = 0;
let mut recv = 0;
loop {
status = lex(&mut state, &mut recv);
if status == Status::End {
log!("done: got {} packets", recv);
break;
} else if status == Status::Waiting {
log!("waiting...");
if send < packets.len() {
log!("sent packet {}", send);
match fw.write_all(packets[send]) {
Err(why) => panic!("cannot write to {}: {}", fname, why),
Ok(_) => send += 1,
}
}
status = fill(&mut state);
log!("queue: '{}'", String::from_utf8_lossy(&state.yyinput));
if status == Status::BigPacket {
log!("error: packet too big");
break;
}
assert_eq!(status, Status::Ready);
} else {
assert_eq!(status, Status::BadPacket);
log!("error: ill-formed packet");
break;
}
}
// Check results.
assert_eq!(status, expect);
if status == Status::End { assert_eq!(recv, send); }
// Cleanup: remove input file.
match std::fs::remove_file(fname) {
Err(why) => panic!("cannot remove {}: {}", fname, why),
Ok(_) => {}
}
}
fn main() {
test(vec![], Status::End);
test(vec![b"zero;", b"one;", b"two;", b"three;", b"four;"], Status::End);
test(vec![b"zer0;"], Status::BadPacket);
test(vec![b"goooooooooogle;"], Status::BigPacket);
}
بلوکهای با قابلیت استفاده مجدد به شکل /*!rules:re2c[:<name>] ... */ یا %{rules[:<name>] ... %} میتوانند به هر تعداد بار مجدداً استفاده شده و با دیگر بلوکهای re2rust ترکیب شوند. بخش <name> اختیاری است. یک بلوک rules میتواند در یک بلوک یا دستورالعمل use به کار رود. کد مربوط به یک بلوک rules در هر نقطه از استفاده تولید میشود.
بلوکهای use با /*!use:re2c[:<name>] ... */ یا %{use[:<name>] ... %} تعریف میشوند. بخش <name> اختیاری است: اگر مشخص نشود، بلوک rules مرتبط، جدیدترین بلوک خواهد بود (خواه نامدار باشد یا بینام). یک بلوک use میتواند تعاریف نامدار، پیکربندیها و قواعد مختص به خود را اضافه کند. یک مورد کاربرد مهم برای بلوکهای use، تحلیلگر لغوی (lexer) با پشتیبانی از چندین کدگذاری ورودی است: همان بلوک rules چندین بار با پیکربندیهای خاص هر کدگذاری مجدداً استفاده میشود (مثال زیر را ببینید).
دستورالعمل درونبلوکی !use:<name>; میتواند از داخل یک بلوک re2rust استفاده شود. این دستور، بلوک ارجاعشدهی <name> را با بلوک فعلی ادغام میکند. اگر برخی از قواعد و پیکربندیهای ادغامشده با موارد تعریفشدهی پیشین همپوشانی داشته باشند، تداخلها به شیوه معمول حل میشوند: نخستین قاعده اولویت دارد و آخرین پیکربندی بر موارد پیشین اعمال میشود. یک استثنا، قواعد ویژه *، $ و (در حالت شرطی) <!> هستند که برای آنها تعریف محلی بلوک بر هر تعریف به ارثرسیدهای ارجحیت دارد. دستورالعمل use امکان ترکیب بلوکهای مختلف re2rust را در یک بلوک فراهم میسازد (مثال زیر را ببینید).
بلوکهای نامدار و دستورالعمل درونبلوکی use در نگارش 2.2 به re2rust اضافه شدند. از آن نگارش به بعد، بلوکهای با قابلیت استفاده مجدد بهطور پیشفرض مجاز هستند (به گزینه خاصی نیاز نیست). پیش از نگارش 2.2، حالت استفاده مجدد با گزینه -r --reusable فعال میشد. پیش از نگارش 1.2، بلوکهای با قابلیت استفاده مجدد نمیتوانستند با بلوکهای عادی ترکیب شوند.
مثالی از دستورالعمل !use
// re2rust $INPUT -o $OUTPUT --api simple
// 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.
#[derive(Debug, PartialEq)]
enum Ans { Color, Fish, Dunno }
/*!rules:re2c:colors
* { panic!("ah"); }
"red" | "salmon" | "magenta" { return Ans::Color; }
*/
/*!rules:re2c:fish
* { panic!("oh"); }
"haddock" | "salmon" | "eel" { return Ans::Fish; }
*/
fn lex(yyinput: &[u8]) -> Ans {
assert!(yyinput.len() > 0); // expect nonempty input
let (mut yycursor, mut yymarker) = (0, 0);
/*!re2c
re2c:yyfill:enable = 0;
re2c:YYCTYPE = u8;
!use:fish;
!use:colors;
* { return Ans::Dunno; } // overrides inherited '*' rules
*/
}
fn main() {
assert_eq!(lex(b"salmon"), Ans::Fish);
assert_eq!(lex(b"what?"), Ans::Dunno);
}
// re2rust $INPUT -o $OUTPUT --input-encoding utf8 --api simple
// 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 Some(yycursor); }
* { return None; }
*/
fn lex_utf8(yyinput: &[u8]) -> Option<usize> {
assert!(yyinput.len() > 0); // expect nonempty input
let (mut yycursor, mut yymarker) = (0, 0);
/*!use:re2c
re2c:encoding:utf8 = 1;
re2c:YYCTYPE = u8;
*/
}
fn lex_utf32(yyinput: &[u32]) -> Option<usize> {
assert!(yyinput.len() > 0); // expect nonempty input
let (mut yycursor, mut yymarker) = (0, 0);
/*!use:re2c
re2c:encoding:utf32 = 1;
re2c:YYCTYPE = u32;
*/
}
fn main() {
let s8 = vec![0xe2, 0x88, 0x80, 0x78, 0x20, 0xe2, 0x88, 0x83, 0x79];
assert_eq!(lex_utf8(&s8), Some(s8.len()));
let s32 = vec![0x2200, 0x78, 0x20, 0x2203, 0x79];
assert_eq!(lex_utf32(&s32), Some(s32.len()));
}
استخراج زیرتطبیق (Submatch extraction)
ابزار re2rust دو گزینه برای استخراج زیرتطبیق دارد.
- Tags
- گزینه نخست استفاده از برچسبها (tags) مستقل به شکل @stag یا #mtag است، که در آن stag و mtag نامهای دلخواه تعریفشده توسط کاربر هستند. برچسبها با گزینه -T --tags یا پیکربندی re2c:tags = 1 فعال میشوند. از نظر معنایی، برچسبها نشانگرهای موقعیت هستند: آنها را میتوان در هر کجای یک عبارت باقاعده درج کرد، و به موقعیت مربوطه (یا چندین موقعیت) در رشته ورودی متصل میشوند. برچسبهای s (یا S-tags) به آخرین موقعیت تطبیقیافته متصل میشوند، و برچسبهای m (یا m-tags) به فهرستی از موقعیتها متصل میگردند (آنها ممکن است در زیرعبارتهای تکرار استفاده شوند، جایی که یک موقعیت واحد در عبارت باقاعده با چندین موقعیت در رشته ورودی متناظر است). تمام برچسبها باید توسط کاربر، به صورت دستی یا به کمک بلوکهای 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] باشد. برای تعریف YYMAXNMATCH از بلوک maxnmatch استفاده کنید، ثابتی که برابر با بیشینه مقدار yynmatch در میان تمام قواعد است.
- Captvars
- روش دیگر برای استفاده از گروههای گیرانداز، گزینه --captvars یا پیکربندی re2c:captvars = 1 است. تنها تفاوت با --captures در نحوه ذخیرهسازی نتایج زیرتطبیق توسط کد تولیدشده است: به جای yynmatch و yypmatch، ابزار re2rust متغیرهای 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 به حداقل رساندن بار اضافی (overhead) در استخراج زیرتطبیق است. در حالت نهایی، اگر هیچ برچسب یا گیراندازی در یک عبارت باقاعده وجود نداشته باشد، TDFA صرفاً یک DFA معمولی است. اگر تعداد برچسبها متعادل باشد، بار اضافی به سختی قابل توجه خواهد بود. اتوماتای TDFA تولیدشده از تعدادی متغیر برچسب (tag variables) استفاده میکند که مستقیماً به برچسبها نگاشت نمیشوند: ممکن است یک متغیر واحد برای برچسبهای متفاوتی استفاده شود، و یک برچسب ممکن است به چندین متغیر برای نگهداری تمام مقادیر ممکن خود نیاز داشته باشد. در نهایت ابهام رفع میشود، و برای هر برچسب تنها یک متغیر نهایی باقی میماند. متغیرهای برچسب باید با استفاده از بلوکهای stags یا mtags تعریف شوند. اگر وضعیت تحلیلگر لغوی ذخیره میشود، متغیرهای برچسب باید بخشی از آن باشند. آنها همچنین باید توسط YYFILL بهروزرسانی شوند.
برچسبهای S-tag از عملیاتهای زیر پشتیبانی میکنند:
- ذخیره موقعیت ورودی در یک s-tag: مقدار t = YYCURSOR با API اشارهگر C یا یک عملیات تعریفشده توسط کاربر YYSTAGP(t) با API عمومی (generic)
- ذخیره مقدار پیشفرض در یک s-tag: مقدار t = NULL با API اشارهگر C یا یک عملیات تعریفشده توسط کاربر YYSTAGN(t) با API عمومی (generic)
- رونوشت از یک s-tag به دیگری: t1 = t2
برچسبهای M-tag از عملیاتهای زیر پشتیبانی میکنند:
- الحاق موقعیت ورودی به یک m-tag: یک عملیات تعریفشده توسط کاربر YYMTAGP(t) با هر دو API پیشفرض و عمومی (generic)
- الحاق مقدار پیشفرض به یک m-tag: یک عملیات تعریفشده توسط کاربر YYMTAGN(t) با هر دو API پیشفرض و عمومی (generic)
- رونوشت از یک m-tag به دیگری: t1 = t2
برچسبهای S-tag میتوانند به عنوان مقادیر اسکالر (اشارهگرها یا آفستها) پیادهسازی شوند. برچسبهای M-tag به نمایشی پیچیدهتر نیاز دارند، زیرا باید دنبالهای از مقادیر برچسب را ذخیره کنند. سادهلوحانهترین و ناکارآمدترین نمایش از یک m-tag، فهرستی (آرایه، بردار) از مقادیر برچسب است؛ نمایش کارآمدتر ذخیره همه m-tagها در یک درخت پیشوندی (prefix-tree) است که به صورت آرایهای از گرههای (v, p) نمایش داده میشود، که در آن v مقدار برچسب و p اشارهگری به گره والد است.
در اینجا نمونهای ساده از کاربرد s-tagها برای تجزیه نسخهبندی معنایی متشکل از سه بخش عددی آورده شده است: major، minor و patch (بخش آخر اختیاری است). برای نمونهای پیچیدهتر که از YYFILL استفاده میکند، بخشهای زیر را ببینید.
// re2rust $INPUT -o $OUTPUT --api simple
#[derive(Debug, PartialEq)]
struct SemVer(u32, u32, u32); // version: (major, minor, patch)
const NONE: usize = std::usize::MAX;
fn s2n(str: &[u8]) -> u32 { // convert a pre-parsed string to a number
let mut n = 0;
for i in str { n = n * 10 + *i as u32 - 48; }
return n;
}
fn parse(yyinput: &[u8]) -> Option<SemVer> {
assert_eq!(yyinput.last(), Some(&0)); // expect null-terminated input
let (mut yycursor, mut yymarker) = (0, 0);
// Final tag variables available in semantic action.
/*!svars:re2c format = '#[allow(unused_mut)]\nlet mut @@;\n'; */
// Intermediate tag variables used by the lexer (must be autogenerated).
/*!stags:re2c format = 'let mut @@ = NONE;'; */
/*!re2c
re2c:YYCTYPE = u8;
re2c:yyfill:enable = 0;
re2c:tags = 1;
num = [0-9]+;
@t1 num @t2 "." @t3 num @t4 ("." @t5 num)? [\x00] {
let major = s2n(&yyinput[t1..t2]);
let minor = s2n(&yyinput[t3..t4]);
let patch = if t5 != NONE {s2n(&yyinput[t5..yycursor - 1])} else {0};
return Some(SemVer(major, minor, patch));
}
* { return None; }
*/
}
fn main() {
assert_eq!(parse(b"23.34\0"), Some(SemVer(23, 34, 0)));
assert_eq!(parse(b"1.2.99999\0"), Some(SemVer(1, 2, 99999)));
assert_eq!(parse(b"1.a\0"), None);
}
// re2rust $INPUT -o $OUTPUT
use std::fs::File;
use std::io::{Read, Write};
const BUFSIZE: usize = 4096;
const NONE: usize = usize::MAX;
struct State {
file: File,
yyinput: [u8; BUFSIZE],
yylimit: usize,
yycursor: usize,
yymarker: usize,
token: usize,
// 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 = "@@: usize,\n"; */
eof: bool,
}
#[derive(PartialEq)]
enum Fill { Ok, Eof, LongLexeme }
#[derive(Debug, PartialEq)]
struct SemVer(u32, u32, u32); // version: (major, minor, patch)
fn s2n(str: &[u8]) -> u32 { // convert a pre-parsed string to a number
let mut n = 0;
for i in str { n = n * 10 + *i as u32 - 48; }
return n;
}
macro_rules! shift { // ignore overflow, marker and tags may not be set yet
($x:expr, $y:expr) => { $x = $x.overflowing_sub($y).0 }
}
fn fill(st: &mut State) -> Fill {
if st.eof { return Fill::Eof; }
// Error: lexeme too long. In real life could reallocate a larger buffer.
if st.token < 1 { return Fill::LongLexeme; }
// Shift buffer contents (discard everything up to the current token).
st.yyinput.copy_within(st.token..st.yylimit, 0);
st.yylimit -= st.token;
st.yycursor -= st.token;
shift!(st.yymarker, st.token);
// Tag variables need to be shifted like other input positions. The check
// for NONE is only needed if some tags are nested inside of alternative or
// repetition, so that they can have NONE value.
/*!stags:re2c format = "if st.@@ != NONE { shift!(st.@@, st.token); }\n"; */
st.token = 0;
// Fill free space at the end of buffer with new data from file.
match st.file.read(&mut st.yyinput[st.yylimit..BUFSIZE - 1]) {
Ok(n) => {
st.yylimit += n;
st.eof = n == 0;
st.yyinput[st.yylimit] = 0;
}
Err(why) => panic!("cannot read from file: {}", why)
}
return Fill::Ok;
}
fn parse(st: &mut State) -> Option<Vec::<SemVer>> {
let mut vers = Vec::new();
// Final tag variables available in semantic action.
/*!svars:re2c format = 'let mut @@;\n'; */
'parse: loop {
st.token = st.yycursor;
/*!re2c
re2c:api = record;
re2c:eof = 0;
re2c:tags = 1;
re2c:yyrecord = st;
re2c:YYCTYPE = u8;
re2c:YYFILL = "fill(st) == Fill::Ok";
num = [0-9]+;
num @t1 "." @t2 num @t3 ("." @t4 num)? [\n] {
let major = s2n(&st.yyinput[st.token..t1]);
let minor = s2n(&st.yyinput[t2..t3]);
let patch = if t4 != NONE {s2n(&st.yyinput[t4..st.yycursor - 1])} else {0};
vers.push(SemVer(major, minor, patch));
continue 'parse;
}
$ { return Some(vers); }
* { return None; }
*/
}
}
fn main() {
let fname = "input";
let verstr = b"1.22.333\n";
let expect = (0..BUFSIZE).map(|_| SemVer(1, 22, 333)).collect();
// Prepare input file (make sure it exceeds buffer size).
match File::create(fname) {
Err(why) => panic!("cannot open {}: {}", fname, why),
Ok(mut file) => match file.write_all(&verstr.repeat(BUFSIZE)) {
Err(why) => panic!("cannot write to {}: {}", fname, why),
Ok(_) => {}
}
};
// Reopen input file for reading.
let file = match File::open(fname) {
Err(why) => panic!("cannot read file {}: {}", fname, why),
Ok(file) => file,
};
// Initialize lexer state.
let yylimit = BUFSIZE - 1;
let mut st = State {
file: file,
yyinput: [0; BUFSIZE], // sentinel is set to zero, which triggers YYFILL
yylimit: yylimit,
yycursor: yylimit,
yymarker: yylimit,
token: yylimit,
/*!stags:re2c format = "@@: NONE,\n"; */
eof: false,
};
// Run the lexer and check results.
assert_eq!(parse(&mut st), Some(expect));
// Cleanup: remove input file.
match std::fs::remove_file(fname) {
Err(why) => panic!("cannot remove {}: {}", fname, why),
Ok(_) => {}
}
}
در ادامه مثالی از بهکارگیری گروههای تسخیر (capturing groups) برای تجزیهٔ نسخهبندیهای معنایی آورده شده است.
// re2rust $INPUT -o $OUTPUT --api simple
#[derive(Debug, PartialEq)]
struct SemVer(u32, u32, u32); // version: (major, minor, patch)
const NONE: usize = std::usize::MAX;
fn s2n(str: &[u8]) -> u32 { // convert a pre-parsed string to a number
let mut n = 0;
for i in str { n = n * 10 + *i as u32 - 48; }
return n;
}
fn parse(yyinput: &[u8]) -> Option<SemVer> {
assert_eq!(yyinput.last(), Some(&0)); // expect null-terminated input
let (mut yycursor, mut yymarker) = (0, 0);
// Final tag variables available in semantic action.
/*!stags:re2c format = 'let mut @@ = NONE;'; */
// Intermediate tag variables used by the lexer (must be autogenerated).
/*!svars:re2c format = '#[allow(unused_mut)]\nlet mut @@;\n'; */
/*!re2c
re2c:YYCTYPE = u8;
re2c:yyfill:enable = 0;
re2c:captvars = 1;
num = [0-9]+;
(num) "." (num) ("." num)? [\x00] {
assert!(yytl0 == 0 && yytr0 == yyinput.len());
let major = s2n(&yyinput[yytl1..yytr1]);
let minor = s2n(&yyinput[yytl2..yytr2]);
let patch = if yytl3 == NONE {0} else {s2n(&yyinput[yytl3 + 1..yytr3])};
return Some(SemVer(major, minor, patch));
}
* { return None; }
*/
}
fn main() {
assert_eq!(parse(b"23.34\0"), Some(SemVer(23, 34, 0)));
assert_eq!(parse(b"1.2.99999\0"), Some(SemVer(1, 2, 99999)));
assert_eq!(parse(b"1.a\0"), None);
}
// re2rust $INPUT -o $OUTPUT --api simple
const NONE: usize = std::usize::MAX;
const MTAG_ROOT: usize = NONE - 1;
// 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).
type MtagTrie = Vec::<MtagElem>;
struct MtagElem {
elem: usize, // tag value
pred: usize, // index of the predecessor node or root
}
// Append a single value to an m-tag history.
fn add_mtag(trie: &mut MtagTrie, mtag: usize, value: usize) -> usize {
trie.push(MtagElem{elem: value, pred: mtag});
return trie.len() - 1;
}
// Recursively unwind tag histories and collect version components.
fn unwind(trie: &MtagTrie, x: usize, y: usize, str: &[u8], ver: &mut Ver) {
// Reached the root of the m-tag tree, stop recursion.
if x == MTAG_ROOT && y == MTAG_ROOT { return; }
// Unwind history further.
unwind(trie, trie[x].pred, trie[y].pred, str, ver);
// Get tag values. Tag histories must have equal length.
assert!(x != MTAG_ROOT && y != MTAG_ROOT);
let (ex, ey) = (trie[x].elem, trie[y].elem);
if ex != NONE && ey != NONE {
// Both tags are valid string indices, extract component.
ver.push(s2n(&str[ex..ey]));
} else {
// Both tags are NONE (this corresponds to zero repetitions).
assert!(ex == NONE && ey == NONE);
}
}
type Ver = Vec::<u32>; // unbounded number of version components
fn s2n(str: &[u8]) -> u32 { // convert a pre-parsed string to a number
let mut n = 0;
for i in str { n = n * 10 + *i as u32 - 48; }
return n;
}
fn parse(yyinput: &[u8]) -> Option<Ver> {
assert_eq!(yyinput.last(), Some(&0)); // expect null-terminated input
let (mut yycursor, mut yymarker) = (0, 0);
let mut mt: MtagTrie = Vec::new();
// Final tag variables available in semantic action.
/*!svars:re2c format = 'let @@;\n'; */
/*!mvars:re2c format = 'let @@;\n'; */
// Intermediate tag variables used by the lexer (must be autogenerated).
/*!stags:re2c format = 'let mut @@ = NONE;'; */
/*!mtags:re2c format = 'let mut @@ = MTAG_ROOT;'; */
/*!re2c
re2c:YYCTYPE = u8;
re2c:YYMTAGP = "@@ = add_mtag(&mut mt, @@, yycursor);";
re2c:YYMTAGN = "@@ = add_mtag(&mut mt, @@, NONE);";
re2c:yyfill:enable = 0;
re2c:tags = 1;
num = [0-9]+;
@t1 num @t2 ("." #t3 num #t4)* [\x00] {
let mut ver: Ver = Vec::new();
ver.push(s2n(&yyinput[t1..t2]));
unwind(&mt, t3, t4, yyinput, &mut ver);
return Some(ver);
}
* { return None; }
*/
}
fn main() {
assert_eq!(parse(b"1\0"), Some(vec![1]));
assert_eq!(parse(b"1.2.3.4.5.6.7\0"), Some(vec![1, 2, 3, 4, 5, 6, 7]));
assert_eq!(parse(b"1.2.\0"), None);
}
درک تفاوت میان code pointها (نقاط کد) و code unitها (واحدهای کد) ضروری است. یک code point شناسهٔ عددی یک نماد است. یک code unit کوچکترین واحد ذخیرهسازی در متن کدگذاریشده است. یک code point منفرد ممکن است با یک یا چند code unit نمایش داده شود. در یک کدگذاری با طول ثابت (fixed-length)، تمام code pointها با تعداد یکسانی از code unitها نمایش داده میشوند. در یک کدگذاری با طول متغیر (variable-length)، ممکن است code pointها با تعداد متفاوتی از code unitها نمایش داده شوند. توجه داشته باشید که قاعدهٔ "any" یعنی [^] با هر code point مطابقت دارد، اما لزوماً با هر code unit تطبیق نمییابد (تنها راه برای تطبیق با هر code unit بدون در نظر گرفتن نوع کدگذاری، استفاده از قاعدهٔ پیشفرض * است). تحلیلگر لغوی تولیدشده با جریانی از code unitها کار میکند: متغیر yych یک code unit را ذخیره میکند و YYCTYPE نوع code unit است. از سوی دیگر، عبارتهای منظم بر حسب code pointها تعریف میشوند. هنگامی که re2rust عبارتهای منظم را به ماشینهای خودکار (automata) کامپایل میکند، code pointها را به code unitها ترجمه مینماید. این کار معمولاً یک نگاشت ساده نیست: در کدگذاریهای با طول متغیر، یک بازهٔ منفرد از code pointها ممکن است به گرافی پیچیده از code unitها ترجمه شود. کدگذاریهای زیر پشتیبانی میشوند:
- ASCII (بهطور پیشفرض فعال است). یک کدگذاری با طول ثابت با فضای کد [0-255] و code pointها و code unitهای ۱ بایتی است.
- EBCDIC (فعالسازی با --ebcdic یا re2c:encoding:ebcdic). یک کدگذاری با طول ثابت با فضای کد [0-255] و code pointها و code unitهای ۱ بایتی است.
- UCS2 (فعالسازی با --ucs2 یا re2c:encoding:ucs2). یک کدگذاری با طول ثابت با فضای کد [0-0xFFFF] و code pointها و code unitهای ۲ بایتی است.
- UTF8 (فعالسازی با --utf8 یا re2c:encoding:utf8). یک کدگذاری با طول متغیر یونیکد است. اندازهٔ هر code unit برابر با ۱ بایت است. هر code point با ۱ تا ۴ code unit بازنمایی میشود.
- UTF16 (فعالسازی با --utf16 یا re2c:encoding:utf16). یک کدگذاری با طول متغیر یونیکد است. اندازهٔ هر code unit برابر با ۲ بایت است. هر code point با ۱ تا ۲ code unit بازنمایی میشود.
- UTF32 (فعالسازی با --utf32 یا re2c:encoding:utf32). یک کدگذاری با طول ثابت یونیکد با فضای کد [0-0x10FFFF] و code pointها و code unitهای ۴ بایتی است.
فایلهای ضمیمهٔ include/unicode_blocks.re، include/unicode_categories.re و include/unicode_properties.re تعاریف re2rust را برای بلوکها، دستهها و ویژگیهای شرحدادهشده در استاندارد Unicode ارائه میدهند.
گزینهٔ --input-encoding کدگذاری فایل منبع را مشخص میکند که میتواند برای فعال کردن لیترالهای یونیکد در عبارتهای منظم به کار رود. برای مثال --input-encoding utf8 به re2rust اعلام میکند که فایل منبع با قالب UTF8 است (با --utf8 که کدگذاری متن ورودی را مشخص میکند تفاوت دارد). گزینهٔ --encoding-policy شیوهٔ مدیریت نویسههای جانشین یونیکد (Unicode surrogates؛ code pointها در بازهٔ [0xD800-0xDFFF]) توسط re2rust را تعیین میکند.
در زیر نمونهای از یک تحلیلگر لغوی برای شناسههای یونیکد کدگذاریشده با UTF8 آمده است.
// re2rust $INPUT -o $OUTPUT --utf8 --api simple
/*!include:re2c "unicode_categories.re" */
fn lex(yyinput: &[u8]) -> bool {
assert_eq!(yyinput.last(), Some(&0)); // expect null-terminated input
let (mut yycursor, mut yymarker) = (0, 0);
/*!re2c
re2c:YYCTYPE = u8;
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; }
*/
}
fn main() {
assert!(lex("_Ыдентификатор\0".as_bytes()));
}
ابزار re2rust به شما امکان میدهد تا فایلهای دیگر را با استفاده از بلوکی به قالب /*!include:re2c FILE */ یا %{include FILE %}، یا یک دستور درونبلوکی !include FILE ; ضمیمه کنید، که در آن FILE مسیر فایل مورد نظر برای ضمیمه شدن است. re2rust فایلهای include را در دایرکتوری فایلی که عمل ضمیمهسازی را انجام میدهد و همچنین در مکانهای include که با گزینهٔ -I مشخص میشوند، جستجو میکند. بلوکها/دستورهای include در re2rust دقیقاً مانند #include در C/C++ عمل میکنند: محتوای FILE عیناً در محل بلوک/دستور جایگذاری میشود. فایلهای include ممکن است خود دارای includeهای بیشتری باشند. برای ردیابی وابستگیهای ساخت فایل خروجی به فایلهای include، از گزینهٔ --depfile استفاده کنید. ابزار re2rust تعدادی فایل include از پیش تعریفشده ارائه میدهد که در زیردایرکتوری include/ پروژه قابل دسترسی هستند. این فایلها شامل تعاریفی هستند که ممکن است برای پروژههای دیگر مفید باشند (مانند دستهبندیهای یونیکد) و چیزی شبیه به یک کتابخانهٔ استاندارد برای re2rust تشکیل میدهند. در ادامه مثالی از بهکارگیری فایلهای include آورده شده است.
فایل ضمیمه ۱ (definitions.rs)
#[derive(Debug, PartialEq)]
enum Num { Int, Float, NaN }
/*!re2c
number = [1-9][0-9]*;
*/
فایل ضمیمه ۲ (extra_rules.re.inc)
// floating-point numbers
frac = [0-9]* "." [0-9]+ | [0-9]+ ".";
exp = 'e' [+-]? [0-9]+;
float = frac exp? | [0-9]+ exp;
float { return Num::Float; }
فایل ورودی (Input file)
// re2rust $INPUT -o $OUTPUT --api simple
/*!include:re2c "definitions.rs" */
fn lex(yyinput: &[u8]) -> Num {
assert_eq!(yyinput.last(), Some(&0)); // expect null-terminated input
let mut yycursor = 0;
let mut yymarker = 0;
/*!re2c
re2c:yyfill:enable = 0;
re2c:YYCTYPE = u8;
* { return Num::NaN; }
number { return Num::Int; }
!include "extra_rules.re.inc";
*/
}
fn main() {
assert_eq!(lex(b"123\0"), Num::Int);
assert_eq!(lex(b"123.4567\0"), Num::Float);
}
ابزار re2rust این امکان را فراهم میکند که با استفاده از گزینهٔ --header یا پیکربندی re2c:header و جفتبلوکهایی به شکل /*!header:re2c:on*/ و /*!header:re2c:off*/، یا %{header:on%} و %{header:off%}، از فایل ورودی .re فایل سرآیند تولید شود. نخستین بلوک نشاندهندهٔ آغاز فایل سرآیند و دومین بلوک نشاندهندهٔ پایان آن است. هر آنچه میان این بلوکها قرار گیرد توسط re2rust پردازش شده و کد تولیدشده در فایلی که با گزینهٔ --header یا پیکربندی re2c:header مشخص شده است (یا در صورت عدم استفاده از گزینه یا پیکربندی، در stdout) نوشته میشود. فایل سرآیند خودکار تولیدشده ممکن است در مواردی نیاز باشد که از re2rust برای تولید تعاریفی استفاده میشود که باید از سایر واحدهای ترجمه (translation units) قابل مشاهده باشند.
در اینجا مثالی از تولید یک فایل سرآیند آورده شده است که شامل تعریف وضعیت تحلیلگر لغوی (lexer state) با متغیرهای برچسب (tag variables) است (تعداد متغیرها به گرامر منظم وابسته است و برای برنامهنویس مشخص نیست).
فایل ورودی (Input file)
// re2rust $INPUT -o $OUTPUT --header lexer/state.rs
mod lexer;
use lexer::state::State; // the module is generated by re2c
/*!header:re2c:on*/
pub struct State<'a> {
pub yyinput: &'a [u8],
pub yycursor: usize,
/*!stags:re2c format = "pub @@: usize,"; */
}
/*!header:re2c:off*/
fn lex(yyrecord: &mut State) -> usize {
assert_eq!(yyrecord.yyinput.last(), Some(&0)); // expect null-terminated input
let t: usize;
/*!re2c
re2c:header = "lexer/state.rs";
re2c:yyfill:enable = 0;
re2c:api = record;
re2c:YYCTYPE = "u8";
re2c:tags = 1;
[a]* @t [b]* { return t; }
*/
}
fn main() {
let mut st = State {
yyinput: b"ab\0",
yycursor: 0,
/*!stags:re2c format = "@@: 0,"; */
};
assert_eq!(lex(&mut st), 1);
}
فایل سرآیند (Header file)
/* Generated by re2c */
pub struct State<'a> {
pub yyinput: &'a [u8],
pub yycursor: usize,
pub yyt1: usize,
}
با گزینهٔ -S, --skeleton، ابزار re2rust تمامی کدهای غیر re2rust را نادیده گرفته و یک برنامهٔ مستقل به زبان C تولید میکند که میتواند در ادامه کامپایل و اجرا شود. این برنامه از کد تحلیلگر لغوی و دادههای ورودی تشکیل شده است. به ازای هر DFA ساختهشده (بلوک یا شرط)، re2rust یک تحلیلگر لغوی مستقل و دو فایل تولید میکند: یک فایل .input حاوی رشتههای مشتقشده از DFA، و یک فایل .keys حاوی نتایج تطابق مورد انتظار. این برنامه هر تحلیلگر لغوی را روی فایل .input متناظر اجرا کرده و نتایج را با مقادیر مورد انتظار مقایسه میکند. برنامههای اسکلت به دلایل متعددی بسیار مفید هستند:
- میتوانند درستی بهینهسازیهای گوناگون re2rust را بررسی کنند (دادهها در مراحل ابتدایی فرآیند و پیش از اعمال هرگونه تبدیل روی DFA تولید میشوند).
- تولید مجموعهای از دادههای ورودی با پوشش مناسب میتواند هم برای آزمودن و هم برای محکزنی مفید باشد.
- تولید برنامههای اجرایی مستقل این امکان را فراهم میسازد که به نمونههای آزمون کمینه (minimized test cases) دسترسی پیدا کرد (چرا که کد اصلی ممکن است حجیم بوده یا وابستگیهای فراوانی داشته باشد).
دشواری تولید دادههای ورودی در این است که به جز در سادهترین حالات، تعداد رشتههای ورودی ممکن بیش از حد زیاد است (حتی اگر طول رشته محدود باشد). re2rust این مشکل را با تولید رشتههای کافی برای پوشش تقریباً تمامی گذارهای DFA حل میکند. این ابزار از الگوریتم زیر استفاده میکند: نخست، یک اسکلت از DFA میسازد. برای رمزگذاریهایی با اندازهٔ واحد کد ۱ بایتی (مانند ASCII، UTF-8 و EBCDIC)، این اسکلت صرفاً یک رونوشت دقیق از DFA اصلی است. برای رمزگذاریهایی با واحدهای کد چندبایتی، اسکلت یک رونوشت از DFA است که برخی از گذارها در آن حذف شدهاند؛ بدین صورت که re2rust برای هر بازهٔ پیوسته و مجزا که با یک گذار DFA مطابقت دارد، حداکثر ۲۵۶ واحد کد را برمیگزیند. مقادیر انتخابشده به طور یکنواخت توزیع شده و شامل کرانهای بازه نیز هستند. re2rust به جای تلاش برای پوشش دادن همهٔ مسیرهای ممکن در اسکلت (که کاری ناممکن است)، به اندازهٔ کافی مسیر تولید میکند تا تمامی گذارهای اسکلت را پوشش دهد، و بدین ترتیب پرشهای شرطی متناظر را در تحلیلگر لغوی فعال میسازد. پیادهسازی این الگوریتم به حدود ۱ گیگابایت گذار محدود شده و مقدار ثابتی از حافظه را مصرف میکند (re2rust دادهها را به محض تولید، در فایل مینویسد).
بصریسازی و اشکالزدایی (Visualization and debug)
با گزینهٔ -D, --emit-dot، ابزار re2rust هیچ کدی تولید نمیکند؛ در عوض، 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)
ابزار re2rust در ابتدا توسط 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.