| RE2ZIG(1) | RE2ZIG(1) |
نام (NAME)
re2zig - تولید تحلیلگرهای لغوی سریع برای Zig
خلاصه دستور (SYNOPSIS)
re2zig [ OPTIONS ] [ WARNINGS ] INPUT
ورودی میتواند یک فایل یا - برای stdin باشد.
مقدمه (INTRODUCTION)
ابزار re2zig به عنوان یک پیشپردازنده عمل میکند. این ابزار فایل ورودی را (که معمولاً یک برنامه به زبان Zig است، اما میتواند هر چیز دیگری نیز باشد) خوانده و به دنبال بلوکهای کدی میگردد که در نشانگرهای آغاز/پایان با شکل خاص محصور شدهاند. متن خارج از این بلوکها عیناً به فایل خروجی رونویسی میشود. محتویات بلوکها توسط re2zig پردازش میشوند. این ابزار آنها را به کد Zig ترجمه کرده و کد تولیدشده را به جای بلوک در خروجی قرار میدهد.
در ادامه مثالی از یک برنامه کوچک آمده است که بررسی میکند آیا یک رشته دادهشده شامل یک عدد دهدهی است یا خیر:
// re2zig $INPUT -o $OUTPUT
const std = @import("std");
fn lex(yyinput: [:0]const u8) bool {
var yycursor: u32 = 0;
%{
re2c:yyfill:enable = 0;
[1-9][0-9]* { return true; }
* { return false; }
%}
}
test {
try std.testing.expect(lex("1234"));
}
در خروجی، re2zig بلوک میانی را با کد تولیدشده جایگزین کرده است:
// Generated by re2zig
// re2zig $INPUT -o $OUTPUT
const std = @import("std");
fn lex(yyinput: [:0]const u8) bool {
var yycursor: u32 = 0;
var yych: u8 = 0;
var yystate: u32 = 0;
yyl: while (true) {
switch (yystate) {
0 => {
yych = yyinput[yycursor];
yycursor += 1;
switch (yych) {
0x31...0x39 => {
yystate = 2;
continue :yyl;
},
else => {
yystate = 1;
continue :yyl;
},
}
},
1 => { return false; },
2 => {
yych = yyinput[yycursor];
switch (yych) {
0x30...0x39 => {
yycursor += 1;
yystate = 2;
continue :yyl;
},
else => {
yystate = 3;
continue :yyl;
},
}
},
3 => { return true; },
else => { @panic("internal lexer error"); },
}
}
}
test {
try std.testing.expect(lex("1234"));
}
مفاهیم پایه (BASICS)
یک برنامه re2zig از دنبالهای از بلوکها آمیخته با کد زبان مقصد تشکیل میشود. یک بلوک میتواند شامل تعاریف، پیکربندیها، قواعد، کنشها و دستورالعملها با هر ترتیبی باشد:
- name = regular-expression ;
- یک تعریف، نام name را به عبارت منظم regular-expression مقید میکند. نامها میتوانند شامل نویسههای الفباییعددی و خط زیر باشند. بخش regular expressions مروری کلی از نحو re2zig برای عبارتهای منظم ارائه میدهد. پس از تعریف، name میتواند در دیگر عبارتهای منظم و در قواعد استفاده شود. بازگشت در تعاریف نامگذاریشده مجاز نیست و هر نام باید پیش از استفاده تعریف شود. یک بلوک تعاریف نامگذاریشده را از حوزه سراسری به ارث میبرد. تعریف مجدد نامی که در حوزه کنونی وجود دارد، یک خطا است.
- configuration = value ;
- یک پیکربندی امکان تغییر رفتار re2zig و سفارشیسازی کد تولیدشده را فراهم میسازد. برای مشاهده فهرست کامل پیکربندیهای پشتیبانیشده توسط re2zig بخش configurations را ببینید. بسته به هر پیکربندی خاص، مقدار value میتواند یک کلیدواژه، یک عدد صحیح غیرمنفی یا یک رشته تکخطی باشد که در صورتی که فقط از نویسههای الفباییعددی تشکیل نشده باشد، باید داخل گیومه تکی یا دوتایی قرار گیرد. یک بلوک پیکربندیها را از حوزه سراسری به ارث میبرد و میتواند آنها را بازتعریف کرده یا موارد جدیدی بیفزاید. پیکربندیهای تعریفشده درون یک بلوک بر کل بلوک تأثیر میگذارند، حتی اگر در انتهای آن آمده باشند.
- regular-expression code
- یک قاعده، عبارت منظم regular-expression را به کنش معنایی آن (یک بلوک کد در آکولادها، یا یک بلوک کد که با := آغاز شده و به یک خط جدید و به دنبال آن هر نویسه غیرفضایخالی ختم میشود) مقید میکند. اگر regular-expression تطابق یابد، code مرتبط اجرا میشود. اگر چندین قاعده مطابقت داشته باشند، طولانیترین تطابق اولویت دارد. اگر چندین قاعده با یک رشته تطابق داشته باشند، قاعدهای که زودتر آمده اولویت دارد. دو قاعده ویژه وجود دارد: قاعده پیشفرض * و قاعده پایان ورودی $. قاعده پیشفرض همواره باید تعریف شود، صرفنظر از جایگاهش در بلوک دارای کمترین اولویت است، و با هر واحد کد مطابقت مییابد (نه لزوماً یک نویسه معتبر، بخش encoding support را ببینید). قاعده پایان ورودی در صورتی باید تعریف شود که روش متناظر برای handling the end of input استفاده شده باشد. با start conditions قواعد از نحو پیچیدهتری برخوردار خواهند بود.
- !action code
- یک کنش، یک بلوک code تعریفشده توسط کاربر را به نقطه خاصی در ماشین حالت متناهی تولیدشده مقید میسازد (دقیقاً همانطور که کنشهای معنایی کد را به وضعیتهای پایانی مقید میکنند). برای مشاهده فهرست کامل کنشهای از پیش تعریفشده بخش actions را ببینید.
- !directive ;
- یک دستورالعمل یکی از گزارههای ویژه از پیش تعریفشده است. هر دستورالعمل هدف منحصربهفردی دارد. برای جزئیات بیشتر بخش directives را ببینید.
بلوکها (Blocks)
نشانگرهای آغاز و پایان بلوک یا /*!re2c و */ هستند، یا %{ و %} (از هر دو سبک پشتیبانی میشود). از نسخهٔ ۲.۲ به بعد، بلوکها میتوانند نامهای اختیاری داشته باشند که امکان ارجاع به آنها را در سایر بلوکها فراهم میسازد. انواع مختلفی از بلوکها وجود دارند:
- /*!re2c[:<name>] ... */ یا %{[:<name>] ... %}
- یک بلوک سراسری شامل تعاریف، پیکربندیها، قواعد و دستورالعملها است. re2zig عبارتهای باقاعده مرتبط با هر قاعده را به یک اتوماتون متناهی معین (DFA) کامپایل میکند، آن را در قالب پرشهای شرطی در زبان مقصد کُدگذاری کرده و بلوک را با کد تولیدشده جایگزین مینماید. نامها و پیکربندیهای تعریفشده در یک بلوک سراسری به حوزهٔ سراسری افزوده شده و برای بلوکهای بعدی قابل مشاهده خواهند بود. در آغاز برنامه، حوزهٔ سراسری با گزینههای خط فرمان مقداردهی اولیه میشود.
- /*!local:re2c[:<name>] ... */ یا %{local[:<name>] ... %}
- یک بلوک محلی مانند یک بلوک سراسری است، اما نامها و پیکربندیهای درون آن دارای حوزهٔ محلی هستند (بر سایر بلوکها تأثیری نمیگذارند).
- /*!rules:re2c[:<name>] ... */ یا %{rules[:<name>] ... %}
- یک بلوک قواعد مانند یک بلوک محلی است، اما به خودی خود هیچ کدی تولید نمیکند و هیچ تعریفی را نیز به حوزهٔ سراسری اضافه نمینماید -- هدف آن، استفادهٔ مجدد در سایر بلوکها است. این روشی برای اشتراکگذاری کد است (جزئیات بیشتر در بخش بلوکهای قابل استفادهٔ مجدد). پیش از نسخهٔ ۲.۲ re2zig، بلوکهای قواعد نیازمند گزینهٔ -r --reusable بودند.
- /*!use:re2c[:<name>] ... */ یا %{use[:<name>] ... %}
- یک بلوک use که به یک بلوک قواعد قبلاً تعریفشده ارجاع میدهد. اگر نام مشخص شده باشد، re2zig به دنبال بلوک قواعدی با این نام میگردد؛ در غیر این صورت، از جدیدترین بلوک قواعد (خواه نامگذاریشده یا بدون نام) استفاده میشود. یک بلوک use میتواند تعاریف، پیکربندیها و قواعد مختص به خود را داشته باشد که به موارد بلوک قواعد ارجاعشده اضافه میشوند. پیش از نسخهٔ ۲.۲ re2zig، بلوکهای use نیازمند گزینهٔ -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 تولیدشده توسط re2zig بسط مییابد. یک فهرست اختیاری از نام بلوکها مشخص میکند که هنگام محاسبهٔ مجموعهٔ متغیرهای برچسب، چه بلوکهایی باید لحاظ شوند (اگر فهرست خالی باشد، همهٔ بلوکها لحاظ میشوند). دو پیکربندی اختیاری وجود دارد: 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) یا بهطور ضمنی توسط re2zig تولید شده است (با گزینههای --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> را فراهم میکند، که باید یک مسیر فایل درون نقلقول جفتی (double-quoted) باشد. محتویات فایل دقیقاً بهجای بلوک جایگزین میشود؛ درست همانگونه که #include در C/C++ عمل میکند. این بلوک میتواند همراه با گزینهٔ --depfile برای تولید وابستگیهای سامانهٔ ساخت به فایلهای گنجاندهشده به کار رود.
- /*!header:re2c:on*/ یا %{header:on %}
- این بلوک نشاندهندهٔ آغاز فایل سرآیند است. همهچیز پس از آن و تا بلوک بعدیِ header:off توسط re2zig پردازش شده و در فایل سرآیندِ مشخصشده با گزینهٔ -t --type-header نوشته میشود.
- /*!header:re2c:off*/ یا %{header:off %}
- این بلوک نشاندهندهٔ پایان فایل سرآیندی است که با بلوک header:on*/ آغاز شده بود.
- /*!ignore:re2c ... */ یا %{ignore ... %}
- بلوکی که محتویات آن نادیده گرفته شده و از فایل خروجی حذف میشود.
پیکربندیها (Configurations)
در اینجا فهرست کاملی از پیکربندیهای پشتیبانیشده توسط re2zig آورده شده است:
- re2c:api, re2c:input
- مشابه گزینهٔ --api.
- re2c:api:sigil
- نشانگری ("sigil") را مشخص میکند که برای جاینگهدارهای آرگومان در اولیههای API استفاده میشود. پیشفرض @@ است. یک جاینگهدار با نشانه شروع شده و به دنبال آن نام آرگومان درون آکولاد قرار میگیرد. برای مثال، اگر نشانه روی $ تنظیم شود، جاینگهدارها به صورت ${name} خواهند بود. APIهای تکآرگومانی میتوانند از نشانهگذاری خلاصهشده بدون قرار دادن نام در آکولاد استفاده کنند. این گزینه میتواند توسط گزینههای مربوط به اولیههای تکی API بازنویسی شود، مانند re2c:YYFILL@len برای YYFILL.
- re2c:api:style
- سبک API را تعیین میکند. مقادیر ممکن عبارتند از functions (پیشفرض برای C) و free-form (پیشفرض برای Go و Rust). در سبک functions، اولیههای API همراه با فهرستی از آرگومانها درون پرانتز به دنبال نام اولیه تولید میشوند. آرگومانها تنها برای پارامترهای خودکار تولیدشده (مانند تعداد نویسههای ارسالشده به YYFILL) ارائه میشوند، اما نه برای بافت کلی تحلیلگر واژگانی، بنابراین اولیهها رفتاری شبیه به ماکروها در C/C++ یا بستارها (closures) در Go و Rust دارند. در سبک free-form، اولیههای API شکل ثابتی ندارند: آنها باید به صورت رشتههایی شامل قطعهکدهای آزاد با متغیرهای جایگذاریشده به شکل @@{var} یا @@ تعریف شوند (که با آرگومانها در سبک تابعگونه مطابقت دارند). این پیکربندی میتواند برای اولیههای تکی API بازنویسی شود؛ برای مثال پیکربندی re2c:YYFILL:naked را برای YYFILL ببینید.
- re2c:bit-vectors, re2c:flags:bit-vectors, re2c:flags:b
- مشابه گزینهٔ --bit-vectors، اما میتواند بر پایهٔ هر بلوک پیکربندی شود.
- re2c:captures, re2c:leftmost-captures
- مشابه گزینهٔ --leftmost-captures، اما میتواند بر پایهٔ هر بلوک پیکربندی شود.
- re2c:captvars, re2c:leftmost-captvars
- مشابه گزینهٔ --leftmost-captvars، اما میتواند بر پایهٔ هر بلوک پیکربندی شود.
- re2c:case-insensitive, re2c:flags:case-insensitive
- مشابه گزینهٔ --case-insensitive، اما میتواند بر پایهٔ هر بلوک پیکربندی شود.
- re2c:case-inverted, re2c:flags:case-inverted
- مشابه گزینهٔ --case-inverted، اما میتواند بر پایهٔ هر بلوک پیکربندی شود.
- re2c:case-ranges, re2c:flags:case-ranges
- مشابه گزینهٔ --case-ranges، اما میتواند بر پایهٔ هر بلوک پیکربندی شود.
- re2c:computed-gotos, re2c:flags:computed-gotos, re2c:flags:g
- مشابه گزینهٔ --computed-gotos، اما میتواند بر پایهٔ هر بلوک پیکربندی شود.
- re2c:computed-gotos:relative, re2c:cgoto:relative
- مشابه گزینهٔ --computed-gotos-relative، اما میتواند بر پایهٔ هر بلوک پیکربندی شود.
- re2c:computed-gotos:threshold, re2c:cgoto:threshold
- اگر gotoی محاسبهشده استفاده شود، این پیکربندی آستانهٔ پیچیدگی را تعیین میکند که باعث تولید جدولهای پرش به جای دستورهای if تودرتو و نقشههای بیتی میشود. مقدار پیشفرض 9 است.
- re2c:cond:abort
- اگر روی یک مقدار عدد صحیح مثبت تنظیم شود، حالت پیشفرض در توزیع شرط تولیدشده، اجرای برنامه را متوقف (abort) میکند.
- re2c:cond:goto
- تکهکدی را مشخص میکند که برای قواعد میانبر تولیدشدهٔ خودکار :=> در شرطها استفاده میشود. مقدار پیشفرض goto @@; است. جاینگهدار @@ با نام شرط جایگزین میشود (پیکربندیهای re2c:api:sigil و re2c:cond:goto@cond را ببینید).
- re2c:cond:goto@cond
- نشانگر مورد استفاده برای جایگزینی آرگومان را در تعریف re2c:cond:goto مشخص میکند. مقدار پیشفرض @@ است. پیکربندی عمومیتر re2c:api:sigil را بازنویسی میکند.
- re2c:cond:divider
- جداکنندهٔ بلوکهای شرط را تعریف میکند. مقدار پیشفرض /* *********************************** */ است. جاینگهدارها با نام شرط جایگزین میشوند (پیکربندیهای re2c:api;sigil و re2c:cond:divider@cond را ببینید).
- re2c:cond:divider@cond
- نشانگر مورد استفاده برای جایگزینی آرگومان در تعریف re2c:cond:divider را مشخص میکند. پیشفرض @@ است. پیکربندی عمومیتر re2c:api:sigil را بازنویسی میکند.
- re2c:cond:prefix, re2c:condprefix
- پیشوند مورد استفاده برای برچسبهای شرط را مشخص میکند. پیشفرض yyc_ است.
- re2c:cond:enumprefix, re2c:condenumprefix
- پیشوند مورد استفاده برای شناسههای شرط را مشخص میکند. پیشفرض yyc است.
- re2c:debug-output, re2c:flags:debug-output, re2c:flags:d
- مشابه گزینهٔ --debug-output، اما میتواند بر پایهٔ هر بلوک پیکربندی شود.
- re2c:empty-class, re2c:flags:empty-class
- مشابه گزینهٔ --empty-class، اما میتواند بر پایهٔ هر بلوک پیکربندی شود.
- re2c:encoding:ebcdic, re2c:flags:ecb, re2c:flags:e
- مشابه گزینهٔ --ebcdic، اما میتواند بر پایهٔ هر بلوک پیکربندی شود.
- re2c:encoding:ucs2, re2c:flags:wide-chars, re2c:flags:w
- مشابه گزینهٔ --ucs2، اما میتواند بر پایهٔ هر بلوک پیکربندی شود.
- re2c:encoding:utf8, re2c:flags:utf-8, re2c:flags:8
- مشابه گزینهٔ --utf8، اما میتواند بر پایهٔ هر بلوک پیکربندی شود.
- re2c:encoding:utf16, re2c:flags:utf-16, re2c:flags:x
- مشابه گزینهٔ --utf16، اما میتواند بر پایهٔ هر بلوک پیکربندی شود.
- re2c:encoding:utf32, re2c:flags:unicode, re2c:flags:u
- مشابه گزینهٔ --utf32، اما میتواند بر پایهٔ هر بلوک پیکربندی شود.
- re2c:encoding-policy, re2c:flags:encoding-policy
- مشابه گزینهٔ --encoding-policy، اما میتواند بر پایهٔ هر بلوک پیکربندی شود.
- re2c:eof
- نماد نگهبان مورد استفاده با قاعدهٔ پایان ورودی $ را تعیین میکند. مقدار پیشفرض -1 است (قاعدهٔ $ استفاده نمیشود). سایر مقادیر ممکن شامل تمام واحدهای کد معتبر است. تنها اعداد دهدهی شناسایی میشوند.
- re2c:header, re2c:flags:type-header, re2c:flags:t
- نام فایل سرآیند تولیدشده را به صورت نسبی به دایرکتوری فایل خروجی مشخص میکند. مشابه گزینهٔ --header، با این تفاوت که مسیر فایل نسبی است.
- re2c:indent:string
- رشتهٔ مورد استفاده برای تورفتگی را مشخص میکند. پیشفرض یک نویسهٔ تب منفرد "\t" است. رشتهٔ تورفتگی باید تنها شامل نویسههای فضای خالی باشد. برای غیرفعال کردن کامل تورفتگی، این پیکربندی را روی یک رشتهٔ خالی تنظیم کنید.
- re2c:indent:top
- حداقل مقدار تورفتگی مورد استفاده را مشخص میکند. مقدار پیشفرض صفر است. مقدار باید یک عدد صحیح غیرمنفی باشد.
- re2c:invert-captures
- مشابه گزینهٔ --invert-captures، اما میتواند بر پایهٔ هر بلوک پیکربندی شود.
- re2c:label:prefix, re2c:labelprefix
- پیشوند مورد استفاده برای برچسبهای وضعیت DFA را مشخص میکند. مقدار پیشفرض yy است.
- re2c:label:start, re2c:startlabel
- تولید برچسب آغاز بلوک را کنترل میکند. مقدار پیشفرض صفر است، به این معنی که برچسب آغاز تنها در صورتی تولید میشود که استفاده شده باشد. یک مقدار عدد صحیح بزرگتر از صفر، تولید برچسب آغاز را حتی در صورتی که توسط تحلیلگر واژگانی استفاده نشده باشد اجباری میکند. یک مقدار رشتهای نیز تولید برچسب آغاز را اجباری کرده و نام برچسب را روی رشتهٔ مشخصشده قرار میدهد. این پیکربندی تنها برای بلوک جاری اعمال میشود (برای بلوک بعدی به مقدار پیشفرض بازنشانی میشود).
- re2c:label:yyFillLabel
- پیشوند برچسبهای YYFILL مورد استفاده با re2c:eof و در حالت وضعیت قابلذخیره را مشخص میکند.
- re2c:label:yyloop
- نام برچسب مشخصکنندهٔ آغاز حلقهٔ تحلیلگر واژگانی همراه با گزینهٔ --loop-switch را تعیین میکند. مقدار پیشفرض yyloop است.
- re2c:label:yyNext
- نام برچسب اختیاری را که به دنبال گزینشگر YYGETSTATE در حالت وضعیت قابلذخیره میآید (فعالشده با re2c:state:nextlabel) مشخص میکند. پیشفرض yyNext است.
- re2c:lookahead, re2c:flags:lookahead
- منسوخشده (گزینهٔ منسوخشدهٔ --no-lookahead را ببینید).
- re2c:monadic
- اگر روی مقداری غیر از صفر تنظیم شود، تحلیلگر واژگانی تولیدشده از نشانهگذاری مونادیک استفاده خواهد کرد (این پیکربندی مختص به زبان Haskell است).
- re2c:nested-ifs, re2c:flags:nested-ifs, re2c:flags:s
- مشابه گزینهٔ --nested-ifs، اما میتواند بر پایهٔ هر بلوک پیکربندی شود.
- re2c:posix-captures, re2c:flags:posix-captures, re2c:flags:P
- مشابه گزینهٔ --posix-captures، اما میتواند بر پایهٔ هر بلوک پیکربندی شود.
- re2c:posix-captvars
- مشابه گزینهٔ --posix-captvars، اما میتواند بر پایهٔ هر بلوک پیکربندی شود.
- مشابه گزینهٔ --tags، اما میتواند بر پایهٔ هر بلوک پیکربندی شود.
- عبارت مورد استفاده برای متغیرهای برچسب را مشخص میکند. بهطور پیشفرض re2zig عبارتهایی به فرم yyt<N> را تولید میکند. این موضوع ممکن است ناخوشایند باشد، برای مثال اگر متغیرهای برچسب به عنوان فیلدهایی درون یک struct تعریف شده باشند. تمام رخدادهای @@{tag} یا @@ با نام واقعی برچسب جایگزین میشوند. برای نمونه، re2c:tags:expression = "s.@@"; منجر به ایجاد عبارتهایی به شکل s.yyt<N> در کد تولیدشده خواهد شد. همچنین پیکربندی re2c:api:sigil را ببینید..TP re2c:tags:negative عبارت ثابتی را مشخص میکند که برای مقدار منفی برچسب استفاده میشود (بهطور معمول در صورتی که برچسبها آفستهای عددی صحیح در رشته ورودی باشند، این مقدار -1، و در صورتی که اشارهگر باشند، یک اشارهگر تهی (null pointer) خواهد بود).
- پیشوند نام متغیرهای برچسب را مشخص میکند. مقدار پیشفرض yyt است.
- re2c:sentinel
- نماد سنتینل مورد استفاده برای بررسیهای پایان ورودی را مشخص میکند (هنگامی که بررسیهای مرز با re2c:yyfill:enable = 0; غیرفعال شده باشند و re2c:eof تنظیم نشده باشد). این پیکربندی بر تولید کد تأثیری ندارد: هدف آن اطمینان از این است که نماد سنتینل در میانه یک قاعده مجاز نباشد، و تضمین اینکه تحلیلگر لغوی فراتر از پایان بافر را نخواند. مقدار پیشفرض -1` است (در این حالت re2zig فرض میکند که سنتینل صفر است، که معمولترین حالت است). تنها اعداد دهدهی شناسایی میشوند.
- re2c:state:abort
- اگر روی یک مقدار عدد صحیح مثبت تنظیم شود، حالت پیشفرض در توزیع وضعیت تولیدشده اجرای برنامه را لغو (abort) میکند، و یک حالت صریح -1 حاوی انتقال به ابتدای بلوک خواهد بود.
- re2c:state:nextlabel
- کنترل میکند که آیا به دنبال عبارت switch مربوط به YYGETSTATE برچسب yyNext بیاید یا خیر (مقدار پیشفرض صفر است که نشاندهنده نبود برچسب است). بهطور جایگزین میتوان از re2c:label:start برای تولید یک برچسب شروع مشخص، یا از یک بلوک صریح getstate برای تولید عبارت switch مربوط به YYGETSTATE بهطور جداگانه از بلوک تحلیلگر لغوی استفاده کرد.
- re2c:unsafe, re2c:flags:unsafe
- مشابه گزینه --no-unsafe است، اما میتواند بر مبنای هر بلوک پیکربندی شود. اگر روی صفر تنظیم شود، از تولید پوششهای unsafe پیرامون YYPEEK جلوگیری میکند. پیشفرض غیرصفر است (پوششها تولید میشوند). این پیکربندی مختص به Rust است.
- re2c:YYBACKUP, re2c:define:YYBACKUP
- عنصر پایه عمومی API با نام YYBACKUP را تعریف میکند.
- re2c:YYBACKUPCTX, re2c:define:YYBACKUPCTX
- عنصر پایه عمومی API با نام YYBACKUPCTX را تعریف میکند.
- re2c:YYCONDTYPE, re2c:define:YYCONDTYPE
- عنصر پایه API با نام YYCONDTYPE را تعریف میکند.
- re2c:YYCTYPE, re2c:define:YYCTYPE
- عنصر پایه API با نام YYCTYPE را تعریف میکند.
- re2c:YYCTXMARKER, re2c:define:YYCTXMARKER
- عنصر پایه API با نام YYCTXMARKER را تعریف میکند.
- re2c:YYCURSOR, re2c:define:YYCURSOR
- عنصر پایه API با نام YYCURSOR را تعریف میکند.
- re2c:YYDEBUG, re2c:define:YYDEBUG
- عنصر پایه API با نام YYDEBUG را تعریف میکند.
- re2c:YYFILL, re2c:define:YYFILL
- عنصر پایه API با نام YYFILL را تعریف میکند.
- re2c:YYFILL@len, re2c:define:YYFILL@len
- نماد (sigil) مورد استفاده برای جایگزینی آرگومان در تعریف YYFILL را مشخص میکند. پیشفرض @@ است. جایگزین پیکربندی عمومیتر re2c:api:sigil میشود.
- re2c:YYFILL:naked, re2c:define:YYFILL:naked
- جایگزین پیکربندی عمومیتر re2c:api:style برای YYFILL میشود. مقدار صفر متناظر با سبک آزاد API (free-form) است.
- re2c:YYFN
- عنصر پایه API با نام YYFN را تعریف میکند.
- re2c:YYINPUT
- عنصر پایه API با نام YYINPUT را تعریف میکند.
- re2c:YYGETCOND, re2c:define:YYGETCONDITION
- عنصر پایه API با نام YYGETCOND را تعریف میکند.
- re2c:YYGETCOND:naked, re2c:define:YYGETCONDITION:naked
- جایگزین پیکربندی عمومیتر re2c:api:style برای YYGETCOND میشود. مقدار صفر متناظر با سبک آزاد API است.
- re2c:YYGETSTATE, re2c:define:YYGETSTATE
- عنصر پایه API با نام YYGETSTATE را تعریف میکند.
- re2c:YYGETSTATE:naked, re2c:define:YYGETSTATE:naked
- جایگزین پیکربندی عمومیتر re2c:api:style برای YYGETSTATE میشود. مقدار صفر متناظر با سبک آزاد API است.
- re2c:YYGETACCEPT, re2c:define:YYGETACCEPT
- عنصر پایه API با نام YYGETACCEPT را تعریف میکند.
- re2c:YYLESSTHAN, re2c:define:YYLESSTHAN
- عنصر پایه عمومی API با نام YYLESSTHAN را تعریف میکند.
- re2c:YYLIMIT, re2c:define:YYLIMIT
- عنصر پایه API با نام YYLIMIT را تعریف میکند.
- re2c:YYMARKER, re2c:define:YYMARKER
- عنصر پایه API با نام YYMARKER را تعریف میکند.
- re2c:YYMTAGN, re2c:define:YYMTAGN
- عنصر پایه عمومی API با نام YYMTAGN را تعریف میکند.
- re2c:YYMTAGP, re2c:define:YYMTAGP
- عنصر پایه عمومی API با نام YYMTAGP را تعریف میکند.
- re2c:YYPEEK, re2c:define:YYPEEK
- عنصر پایه عمومی API با نام YYPEEK را تعریف میکند.
- re2c:YYRESTORE, re2c:define:YYRESTORE
- عنصر پایه عمومی API با نام YYRESTORE را تعریف میکند.
- re2c:YYRESTORECTX, re2c:define:YYRESTORECTX
- عنصر پایه عمومی API با نام YYRESTORECTX را تعریف میکند.
- re2c:YYRESTORETAG, re2c:define:YYRESTORETAG
- عنصر پایه عمومی API با نام YYRESTORETAG را تعریف میکند.
- re2c:YYSETCOND, re2c:define:YYSETCONDITION
- عنصر پایه API با نام YYSETCOND را تعریف میکند.
- re2c:YYSETCOND@cond, re2c:define:YYSETCONDITION@cond
- نماد (sigil) مورد استفاده برای جایگزینی آرگومان در تعریف YYSETCOND را مشخص میکند. مقدار پیشفرض @@ است. جایگزین پیکربندی عمومیتر re2c:api:sigil میشود.
- re2c:YYSETCOND:naked, re2c:define:YYSETCONDITION:naked
- جایگزین پیکربندی عمومیتر re2c:api:style برای YYSETCOND میشود. مقدار صفر متناظر با سبک آزاد API است.
- re2c:YYSETSTATE, re2c:define:YYSETSTATE
- عنصر پایه API با نام YYSETSTATE را تعریف میکند.
- re2c:YYSETSTATE@state, re2c:define:YYSETSTATE@state
- نماد (sigil) مورد استفاده برای جایگزینی آرگومان در تعریف YYSETSTATE را مشخص میکند. مقدار پیشفرض @@ است. جایگزین پیکربندی عمومیتر re2c:api:sigil میشود.
- re2c:YYSETSTATE:naked, re2c:define:YYSETSTATE:naked
- جایگزین پیکربندی عمومیتر re2c:api:style برای YYSETSTATE میشود. مقدار صفر متناظر با سبک آزاد API است.
- re2c:YYSETACCEPT, re2c:define:YYSETACCEPT
- عنصر پایه API با نام YYSETACCEPT را تعریف میکند.
- re2c:YYSKIP, re2c:define:YYSKIP
- عنصر پایه عمومی API با نام YYSKIP را تعریف میکند.
- re2c:YYSHIFT, re2c:define:YYSHIFT
- عنصر پایه عمومی API با نام YYSHIFT را تعریف میکند.
- re2c:YYCOPYMTAG, re2c:define:YYCOPYMTAG
- عنصر پایه عمومی API با نام YYCOPYMTAG را تعریف میکند.
- re2c:YYCOPYSTAG, re2c:define:YYCOPYSTAG
- عنصر پایه عمومی API با نام YYCOPYSTAG را تعریف میکند.
- re2c:YYSHIFTMTAG, re2c:define:YYSHIFTMTAG
- عنصر پایه عمومی API با نام YYSHIFTMTAG را تعریف میکند.
- re2c:YYSHIFTSTAG, re2c:define:YYSHIFTSTAG
- عنصر پایه عمومی API با نام YYSHIFTSTAG را تعریف میکند.
- re2c:YYSTAGN, re2c:define:YYSTAGN
- عنصر پایه عمومی API با نام YYSTAGN را تعریف میکند.
- re2c:YYSTAGP, re2c:define:YYSTAGP
- عنصر پایه عمومی API با نام YYSTAGP را تعریف میکند.
- re2c:yyaccept, re2c:variable:yyaccept
- عنصر پایه API با نام yyaccept را تعریف میکند.
- re2c:yybm, re2c:variable:yybm
- عنصر پایه API با نام yybm را تعریف میکند.
- re2c:yybm:hex, re2c:variable:yybm:hex
- اگر روی غیرصفر تنظیم شود، نقشههای بیتی (bitmaps) برای گزینه --bit-vectors در قالب هگزادسیمال تولید میشوند. پیشفرض صفر است (نقشههای بیتی در قالب دهدهی هستند).
- re2c:yych, re2c:variable:yych
- عنصر پایه API با نام yych را تعریف میکند.
- re2c:yych:emit, re2c:variable:yych:emit
- اگر روی صفر تنظیم شود، تعریف yych تولید نمیشود. پیشفرض غیرصفر است.
- re2c:yych:conversion, re2c:variable:yych:conversion
- اگر روی غیرصفر تنظیم شود، re2zig در هر بار خوانده شدن yych، بهطور خودکار یک تبدیل به YYCTYPE تولید میکند. مقدار پیشفرض صفر است (بدون تبدیل).
- re2c:yych:literals, re2c:variable:yych:literals
- شکل لیترالهایی که yych در برابر آنها تطبیق داده میشود را مشخص میکند. مقادیر ممکن عبارتند از: char (لیترالهای نویسهای درون گیومه تکی؛ نویسههای غیرقابلچاپ از توالیهای گریز که با بکاسلش آغاز میشوند استفاده میکنند)، hex (اعداد صحیح هگزادسیمال) و char_or_hex (ترکیبی از هر دو، لیترالهای نویسهای برای نویسههای قابلچاپ و اعداد صحیح هگزادسیمال برای بقیه).
- re2c:yyctable, re2c:variable:yyctable
- عنصر پایه API با نام yyctable را تعریف میکند..TP re2c:yynmatch, re2c:variable:yynmatch پریمیتیو API با نام yynmatch را تعریف میکند.
- re2c:yypmatch, re2c:variable:yypmatch
- پریمیتیو API با نام yypmatch را تعریف میکند.
- re2c:yytarget, re2c:variable:yytarget
- پریمیتیو API با نام yytarget را تعریف میکند.
- re2c:yystable, re2c:variable:yystable
- منسوخ شده است.
- re2c:yystate, re2c:variable:yystate
- پریمیتیو API با نام yystate را تعریف میکند.
- re2c:yyfill, re2c:variable:yyfill
- پریمیتیو API با نام yyfill را تعریف میکند.
- re2c:yyfill:check
- در صورت تنظیم روی صفر، از تولید بررسی پیش از YYFILL برای تعداد نویسههای ورودی (تعریف YYLESSTHAN در API عمومی و مقایسه مبتنی بر YYLIMIT در API اشارهگر C) جلوگیری میکند. مقدار پیشفرض غیرصفر است (بررسی تولید میشود).
- re2c:yyfill:enable
- در صورت تنظیم روی صفر، از تولید YYFILL (همراه با بررسی آن) جلوگیری میکند. این مورد باید زمانی استفاده شود که کل ورودی در یک قطعه از حافظه قرار میگیرد (نیازی به بافرسازی نیست) و بررسیهای پایان ورودی به بررسیهای YYFILL وابسته نیستند (برای مثال در صورتی که از یک نویسه نگهبان (sentinel) استفاده شده باشد). از هشدارها (گزینه -W) و پیکربندی re2c:sentinel استفاده کنید تا مطمئن شوید تحلیلگر واژگانی (lexer) تولیدشده نمیتواند فراتر از پایان ورودی را بخواند. مقدار پیشفرض غیرصفر است (YYFILL فعال است).
- re2c:yyfill:parameter
- در صورت تنظیم روی صفر، از تولید پارامتر ارسالی به YYFILL جلوگیری میکند. این پارامتر حداقل تعداد نویسههایی است که باید فراهم شوند. پیشفرض مقدار غیرصفر است (پارامتر تولید میشود). این پیکربندی میتواند توسط re2c:YYFILL:naked یا re2c:api:style بازنویسی شود.
- re2c:yyfn:sep
- جداکننده مورد استفاده در عناصر YYFN را تعیین میکند (پیشفرض نقطه-ویرگول است).
- re2c:yyfn:throw
- استثناهای پرتابشده توسط تابع YYFN را مشخص میکند (پیشفرض خالی است، که به معنای عدم وجود هیچ استثنایی است).
عبارات باقاعده (Regular expressions)
re2zig از نحو زیر برای عبارات باقاعده استفاده میکند:
- "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؛ از پرانتز برای تغییر اولویت تقدم استفاده میشود. اگر استخراج زیرتطابق (submatch) فعال باشد، بسته به گزینه --invert-captures، (R) یک گروه ضبطکننده یا غیرضبطکننده خواهد بود.
- (!R)
- اگر استخراج زیرتطابق فعال باشد، بسته به گزینه --invert-captures، (!R) یک گروه غیرضبطکننده یا ضبطکننده خواهد بود.
- 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)
در اینجا فهرستی از کنشهای از پیش تعریفشده که توسط re2zig پشتیبانی میشوند آمده است:
- !entry code
- کنش ورودی (Entry action) یک بلوک از code تعریفشده توسط کاربر را به وضعیت آغازین ماشین حالت متناهی فعلی مقید میکند. اگر از start conditions استفاده شود، کنش ورودی میتواند بهطور جداگانه برای هر وضعیت تنظیم گردد. این کنش ممکن است برای انجام مقداردهی اولیه استفاده شود، مثلاً برای ذخیره مکان آغازین یک واژه (lexeme).
- !pre_rule code
- کنش پیشقاعده (Pre-rule action) یک بلوک از code تعریفشده توسط کاربر را به ابتدای کنشهای معنایی تمامی قواعد در بلوک فعلی (یا وضعیت، در صورت استفاده از start conditions) میافزاید. این کنش ممکن است برای جداسازی بخش مشترک همه کنشهای معنایی (مانند ذخیره موقعیت پایان یک واژه) به کار رود.
- !post_rule code
- کنش پسقاعده (Post-rule action) یک بلوک از code تعریفشده توسط کاربر را به انتهای کنشهای معنایی تمامی قواعد در بلوک فعلی (یا وضعیت، در صورت استفاده از start conditions) الحاق میکند. این کنش میتواند برای درج دستورهای تله (trap statements) جهت جلوگیری از جریان کنترل ناخواسته استفاده شود.
دستورالعملها (Directives)
در اینجا فهرست کاملی از دستورالعملهای پشتیبانیشده توسط re2zig آورده شده است:
- !use:name ;
- یک دستورالعمل use درونبلوکی که یک بلوک قواعد از پیش تعریفشده با name مشخصشده را در بلوک فعلی ادغام میکند. تعاریف نامدار، پیکربندیها و قواعد بلوک ارجاعشده به موارد فعلی افزوده میشوند. تداخلها میان قواعد و پیکربندیهای همپوشان به روش معمول برطرف میشوند: نخستین قاعده اولویت دارد و آخرین پیکربندی بر پیکربندیهای قبلی بازنویسی میشود. یک استثنا، قواعد ویژه *، $ و <!> هستند که برای آنها تعریف محلی بلوک همواره اولویت دارد. یک دستورالعمل use میتواند در هر کجای درون یک بلوک قرار گیرد و استفاده از چندین دستورالعمل use مجاز است.
- !include file ;
- این دستورالعمل همانند بلوک include است: محتویات file را دقیقاً در محل دستورالعمل درج میکند.
رابط برنامه (Program interface)
کد تولیدشده به کمک بنیادینهها (primitives)، که در مجموع با نام API شناخته میشوند، با برنامه بیرونی ارتباط برقرار میکند. اینکه چه بنیادینههایی باید برای یک برنامه خاص تعریف شوند به عوامل متعددی از جمله پیچیدگی عبارات باقاعده، شیوه نمایش ورودی، بافرینگ و استفاده از ویژگیهای گوناگون بستگی دارد. تمام بنیادینههای لازم باید توسط کاربر در قالب ماکروها، توابع، متغیرها یا هر شکل مناسب دیگری که کد تولیدشده را از نظر نحوی و معنایی صحیح سازد، تعریف شوند. ابزار re2zig تعاریف را بررسی نمیکند (و نمیتواند بررسی کند)، بنابراین اگر چیزی جا افتاده باشد یا به نادرستی تعریف شده باشد، برنامه تولیدشده ممکن است در زمان کامپایل یا اجرا دچار خطا شود. این راهنما مثالهایی از تعاریف API را در رایجترین موارد ارائه میدهد.
ابزار re2zig دارای سه سبک API است که مجموعه اصلی بنیادینههای مورد استفاده یک برنامه را مشخص میکنند:
- Simple API
- این API پیشفرض برای بکاند Zig است. این رابط شامل بنیادینههای YYCURSOR، YYMARKER، YYCTXMARKER و YYLIMIT است که باید به عنوان اشارهگرهایی از نوع YYCTYPE* تعریف شوند.
- Record API
- رابط Record API در مواردی مفید است که وضعیت تحلیلگر لغوی باید درون یک struct ذخیره شود. این رابط با گزینه --api record یا پیکربندی re2c:api = record فعال میشود. این API شامل متغیر yyrecord است (نام آن میتواند با re2c:yyrecord بازنویسی شود) که باید به عنوان یک struct با فیلدهای yycursor، yymarker، yyctxmarker و yylimit تعریف شود (تنها فیلدهایی که توسط کد تولیدشده استفاده میشوند نیاز به تعریف دارند، و نامهای آنها قابل پیکربندی است).
- Generic API
- این انعطافپذیرترین API است. این رابط با گزینه --api generic یا پیکربندی re2c:api = generic فعال میشود. این رابط شامل بنیادینههایی برای عملیاتهای عمومی است: YYPEEK، YYSKIP، YYBACKUP، YYBACKUPCTX، YYSTAGP، YYSTAGN، YYMTAGP، YYMTAGN، YYRESTORE، YYRESTORECTX، YYRESTORETAG، YYSHIFT، YYSHIFTSTAG، YYSHIFTMTAG، YYLESSTHAN، YYEND.
در اینجا فهرست کاملی از بنیادینههای API که ممکن است توسط کد تولیدشده جهت ارتباط با برنامه بیرونی به کار روند آمده است.
- YYCTYPE
- نوع نویسههای ورودی (code units). برای کدگذاریهای 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). در صورتی که تطبیق طولانیتر ناموفق باشد و تحلیلگر لغوی نیاز به عقبگرد (rollback) داشته باشد، برای بازیابی موقعیت YYCURSOR استفاده میشود. نیازی به مقداردهی اولیه نیست.
- YYCTXMARKER
- یک l-value که موقعیت بافت پسین (trailing context) را ذخیره میکند (یک اشارهگر یا آفست عددی در YYINPUT). نیازی به مقداردهی اولیه نیست. YYCTXMARKER تنها در صورتی نیاز است که از عملگر پیشنگری / استفاده شود.
- YYFILL
- یک بنیادینه عمومی 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
- یک بنیادینه عمومی API با یک متغیر len. باید به عنوان یک r-value از نوع boolean تعریف شود که برابر با true است اگر و تنها اگر کمتر از len نویسه ورودی باقی مانده باشد.
- YYEND
- یک بنیادینه عمومی API بدون متغیر. باید به صورت یک r-value از نوع boolean تعریف شود که برابر با true است اگر و تنها اگر پایان منطقی ورودی فرا رسیده باشد (بدون در نظر گرفتن هرگونه نماد فاصلهساز یا نگهبان). YYEND برای پیادهسازی نماد $ در عبارات باقاعده استفاده میشود. این بنیادینه با YYLESSTHAN، که برای اطمینان از اینکه تحلیلگر فراتر از پایان بافر نخواند به کار میرود، تفاوت دارد.
- YYPEEK
- یک بنیادینه عمومی API بدون متغیر. باید به عنوان یک r-value از نوع YYCTYPE تعریف شود که برابر با نویسه در موقعیت فعلی ورودی است.
- YYSKIP
- یک بنیادینه عمومی API که باید موقعیت فعلی ورودی را به اندازه یک واحد کد (code unit) جلو ببرد.
- YYBACKUP
- یک بنیادینه عمومی API که باید موقعیت فعلی ورودی را ذخیره کند (تا بعداً با YYRESTORE بازیابی شود).
- YYRESTORE
- یک بنیادینه عمومی API که باید موقعیت فعلی ورودی را به مقدار ذخیرهشده توسط YYBACKUP بازگرداند.
- YYBACKUPCTX
- یک بنیادینه عمومی API که باید موقعیت فعلی ورودی را به عنوان موقعیت بافت پسین (trailing context) ذخیره کند (تا بعداً با YYRESTORECTX بازیابی شود)..TP YYRESTORECTX یک سازهٔ اولیه عمومی API که باید موقعیت بافت پایانی ذخیرهشده با YYBACKUPCTX را بازیابی کند.
- YYRESTORETAG
- یک سازهٔ اولیه عمومی API با یک متغیر tag که باید موقعیت بافت پایانی را به مقدار tag بازگرداند.
- YYSTAGP
- یک سازهٔ اولیه عمومی API با یک متغیر tag، که در آن tag میتواند یک اشارهگر یا آفست در YYINPUT باشد (برای جزئیات به بخش استخراج زیرتطابق مراجعه کنید). YYSTAGP باید مقدار tag را روی موقعیت فعلی ورودی تنظیم کند.
- YYSTAGN
- یک سازهٔ اولیه عمومی API با یک متغیر tag، که در آن tag میتواند یک اشارهگر یا آفست در YYINPUT باشد (برای جزئیات به بخش استخراج زیرتطابق مراجعه کنید). YYSTAGN باید tag را روی مقداری تنظیم کند که نشاندهندهٔ موقعیت ورودی ناموجود است.
- YYMTAGP
- یک سازهٔ اولیه عمومی API با یک متغیر tag. YYMTAGP باید موقعیت فعلی را به تاریخچهٔ زیرتطابقِ tag ضمیمه کند (برای جزئیات به بخش استخراج زیرتطابق مراجعه کنید).
- YYMTAGN
- یک سازهٔ اولیه عمومی API با یک متغیر tag. YYMTAGN باید مقداری را که نشاندهندهٔ موقعیت ورودی ناموجود است به تاریخچهٔ زیرتطابقِ tag ضمیمه کند (برای جزئیات به بخش استخراج زیرتطابق مراجعه کنید).
- YYSHIFT
- یک سازهٔ اولیه عمومی API با یک متغیر shift که باید موقعیت فعلی ورودی را به اندازهٔ shift نویسه جابهجا کند (مقدار جابهجایی میتواند منفی باشد).
- YYCOPYSTAG
- یک سازهٔ اولیه عمومی API با دو متغیر، lhs و rhs که باید متغیر s-tag سمت راست rhs را در متغیر s-tag سمت چپ lhs کپی کند. برای اکثر زبانها این سازهٔ اولیه تعریفی پیشفرض دارد که lhs را برابر با rhs قرار میدهد.
- YYCOPYMTAG
- یک سازهٔ اولیه عمومی API با دو متغیر، lhs و rhs که باید متغیر m-tag سمت راست rhs را در متغیر m-tag سمت چپ lhs کپی کند. برای اکثر زبانها این سازهٔ اولیه تعریفی پیشفرض دارد که lhs را برابر با rhs قرار میدهد.
- YYSHIFTSTAG
- یک سازهٔ اولیه عمومی API با دو متغیر، tag و shift که باید tag را به اندازهٔ shift واحد کد جابهجا کند (مقدار جابهجایی میتواند منفی باشد).
- YYSHIFTMTAG
- یک سازهٔ اولیه عمومی API با دو متغیر، tag و shift که باید آخرین مقدار در تاریخچهٔ tag را به اندازهٔ shift واحد کد جابهجا کند (مقدار جابهجایی میتواند منفی باشد).
- YYMAXNMATCH
- یک ثابت عددی صحیح برابر با حداکثر تعداد گروههای ضبطکنندهٔ POSIX در یک قاعده. این مقدار با یک بلوک maxnmatch تولید میشود.
- YYCONDTYPE
- نوع شمارش (enum) شرط. میتواند با بلوک conditions یا گزینهٔ --header تولید شود.
- YYGETACCEPT
- سازهای اولیه با یک متغیر var که انتخابگر عددی قاعدهٔ پذیرفتهشده را ذخیره میکند. برای اکثر زبانها این سازه تعریفی پیشفرض دارد که مقدار را از var میخواند.
- YYSETACCEPT
- سازهای اولیه با دو متغیر: var (یک l-value که انتخابگر عددی قاعدهٔ پذیرفتهشده را ذخیره میکند) و val (مقدار انتخابگر). برای اکثر زبانها این سازه تعریفی پیشفرض دارد که var را برابر با val قرار میدهد.
- YYGETCOND
- یک r-value از نوع YYCONDTYPE که برابر با شناسهٔ شرط فعلی است.
- YYSETCOND
- سازهای اولیه با یک متغیر cond که باید شناسهٔ شرط فعلی را روی cond تنظیم کند.
- YYGETSTATE
- یک r-value از نوع عدد صحیح که برابر با وضعیت فعلی لکسر است. باید با -1 مقداردهی اولیه شود.
- YYSETSTATE
- سازهای اولیه با یک متغیر state که باید وضعیت فعلی لکسر را روی state تنظیم کند.
- YYDEBUG
- این سازهٔ اولیه تنها با گزینهٔ -d، --debug-output تولید میشود. هدف آن افزودن ثبت وقایع به کد تولیدشده است (تعریف معمول YYDEBUG یک دستور چاپ است). دستورات YYDEBUG در هر وضعیت تولید میشوند و دارای دو متغیر هستند: state (یا نمایهٔ وضعیت DFA یا -1) و symbol (نماد ورودی فعلی).
- yyaccept
- یک l-value از نوع عدد صحیح بدون علامت که شمارهٔ آخرین قاعدهٔ منطبقشده را ذخیره میکند. تعریف توسط کاربر تنها با گزینهٔ --storable-state الزامی است.
- yybm
- جدولی شامل نقشههای بیتی فشردهشده برای حداکثر ۸ گذار (همراه با گزینهٔ --bitmaps استفاده میشود). این جدول شامل ۲۵۶ عنصر است و با واحدهای کد ۱ بایتی اندیسگذاری میشود. هر عنصر ۸ بیتی مقادیر بولی را برای حداکثر ۸ گذار ترکیب میکند. بیت k-ام از عنصر n-ام درست (true) است اگر و تنها اگر واحد کد n-ام در محدودهٔ گذار k-ام باشد. ایدهٔ این نقشه بیتی جایگزینی چندین شاخهٔ if یا موارد switch با بررسی یک بیت منفرد در جدول است.
- yych
- یک l-value از نوع YYCTYPE که نویسهٔ ورودی فعلی را ذخیره میکند. تعریف توسط کاربر تنها با گزینهٔ -f --storable-state الزامی است.
- yyctable
- جدول پرش تولیدشده برای توزیع شرط اولیه (که با ترکیب گزینههای --conditions و --computed-gotos فعال میشود).
- yyfill
- یک l-value که نتیجهٔ فراخوانی YYFILL را ذخیره میکند (این امر ممکن است برای زبانهای کاملاً تابعی که در آنها YYFILL یک تابع مونادیک با مقدار بازگشتی پیچیده است، ضروری باشد).
- yynmatch
- یک l-value از نوع عدد صحیح بدون علامت که تعداد گروههای ضبطکنندهٔ POSIX را در قاعدهٔ منطبقشده ذخیره میکند. تنها با گزینهٔ -P --posix-captures استفاده میشود.
- yypmatch
- آرایهای از l-valueها که برای نگهداری مقادیر برچسب متناظر با پرانتزهای ضبطکننده در قاعدهٔ منطبقشده به کار میروند. طول آرایه باید حداقل yynmatch * 2 باشد (معمولاً YYMAXNMATCH * 2 انتخاب مناسبی است). تنها با گزینهٔ -P --posix-captures استفاده میشود.
- yystable
- منسوخشده.
- yystate
- یک l-value که با گزینهٔ --loop-switch برای ذخیرهٔ وضعیت فعلی DFA استفاده میشود.
- yytarget
- جدول پرشی که شامل مقصدهای پرش (نشانیهای برچسب) برای همهٔ گذارها از یک وضعیت است. این جدول مختص هر وضعیت (محلی) است. تولید جدولهای yytarget با گزینهٔ --computed-gotos فعال میشود.
گزینهها (Options)
برخی از گزینهها دارای پیکربندیهای متناظر هستند، برخی دیگر سراسری بوده و پس از شروع خواندن فایل ورودی توسط re2c، امکان تغییر آنها وجود ندارد. گزینههای اشکالزدایی عموماً نیازمند ساخت re2c در پیکربندی اشکالزدایی هستند. گزینههای داخلی برای آزمایش الگوریتمهای بهکاررفته در re2c مفید هستند.
- -? --help -h
- نمایش پیام راهنما.
- --api <simple | record | generic>
- مشخص کردن API مورد استفاده توسط کد تولیدشده برای ارتباط با کد تعریفشده توسط کاربر. گزینهٔ simple باید در موارد ساده که نیازی به پر کردن مجدد بافر و ذخیرهٔ وضعیت تحلیلگر لغوی (lexer) نیست استفاده شود. گزینهٔ record باید هنگامی استفاده شود که وضعیت تحلیلگر لغوی باید در یک رکورد (struct، class و غیره) ذخیره گردد. گزینهٔ generic باید در موارد پیچیده که دو API دیگر بهاندازهٔ کافی انعطافپذیر نیستند به کار رود.
- --bit-vectors -b
- بهینهسازی پرشهای شرطی با استفاده از ماسکهای بیتی (bit masks). این گزینه مستلزم --nested-ifs است.
- --captures, --leftmost-captures
- فعالسازی استخراج تطابقهای فرعی با گروههای گیرانداز حریصانه از چپترین حالت (leftmost greedy). نتیجه در یک آرایهٔ yybmatch با ظرفیت 2 * YYMAXNMATCH جمعآوری شده و yynmatch روی تعداد گروههای قاعدهٔ منطبق تنظیم میشود.
- --captvars, --leftmost-captvars
- فعالسازی استخراج تطابقهای فرعی با گروههای گیرانداز حریصانه از چپترین حالت. نتیجه در متغیرهای yytl<k> و yytr<k> برای k-امین گروه گیرانداز ذخیره میشود.
- --case-insensitive
- غیرحساس به بزرگی و کوچکی حروف در نظر گرفتن رشتههای محصور در نقلقول تکی و دوتایی.
- --case-inverted
- معکوس کردن مفهوم رشتههای محصور در نقلقول تکی و دوتایی: رشتههای درون نقلقول تکی، حساس به بزرگی و کوچکی حروف و رشتههای درون نقلقول دوتایی، غیرحساس به بزرگی و کوچکی حروف در نظر گرفته میشوند.
- --case-ranges
- ادغام caseهای متوالی در دستورات switch به یک بازه با قالب low ... high. این نحو یک افزونهٔ زبان C/C++ است که توسط کامپایلرهایی مانند GCC، Clang و Tcc پشتیبانی میشود. مزیت اصلی نسبت به استفاده از caseهای تکی، تولید کد کوچکتر و زمان تولید سریعتر است، هرچند برای برخی از کامپایلرها مانند Tcc منجر به اندازهٔ دودویی (binary) کوچکتر نیز میشود.
- --computed-gotos -g
- بهینهسازی پرشهای شرطی با استفاده از افزونهٔ غیراستاندارد "computed goto" (که باید توسط کامپایلر پشتیبانی شود). re2zig تنها در موارد پیچیده با شاخههای شرطی بسیار، جدولهای پرش را تولید میکند. آستانهٔ پیچیدگی را میتوان با پیکربندی cgoto:threshold تعیین کرد. آفستهای نسبی را میتوان با پیکربندی cgoto:relative فعال نمود. این گزینه مستلزم --bit-vectors است.
- --computed-gotos-relative
- مشابه --computed-gotos، اما در عوض آفستهای نسبی برای جدولهای پرش تولید میکند (که باید توسط کامپایلر پشتیبانی شود). این گزینه مستلزم --computed-gotos است.
- --conditions --start-conditions -c
- فعالسازی پشتیبانی از "conditions" مشابه Flex: چندین تحلیلگر لغوی مرتبط درون یک بلوک. این گزینه جایگزینی برای مشخص کردن دستی بلوکهای مختلف re2zig است که با goto یا فراخوانیهای تابع به هم متصل شدهاند.
- --depfile FILE
- نوشتن اطلاعات وابستگی در FILE در قالب یک قاعدهٔ Makefile بهشکل <output-file> : <input-file> [include-file ...]. این امکان را فراهم میکند تا وابستگیهای ساخت در صورت وجود بلوکها/دستورالعملهای include ردگیری شوند، بهگونهای که با بهروزرسانی فایلهای include، بازتولید فایل خروجی آغاز گردد. این گزینه به گزینهٔ --output وابسته است.
- --ebcdic --ecb -e
- تولید یک تحلیلگر لغوی که ورودی را با کدگذاری EBCDIC میخواند. re2zig فرض میکند که محدودهٔ نویسه 0 -- 0xFF و اندازهٔ هر نویسه ۱ بایت است.
- --empty-class <match-empty | match-none | error>
- تعیین شیوهٔ برخورد re2zig با ردههای نویسهای تهی. با match-empty (پیشفرض)، ردهٔ تهی با ورودی خالی تطابق مییابد (که غیرمنطقی است، اما با نسخههای قبلی سازگار است). با match-none ردهٔ تهی همواره در تطبیق ناموفق است. با error ردهٔ تهی خطای کامپایل ایجاد میکند.
- --encoding-policy <fail | substitute | ignore>
- تعیین شیوهٔ برخورد re2zig با جانشینهای یونیکد (surrogates). با fail با مواجهه با یک surrogate، پردازش re2zig با خطا متوقف میشود. با substitute برنامهٔ re2zig بیصدا جانشینها را با کد پوینت خطای 0xFFFD جایگزین میکند. با ignore (پیشفرض) re2zig با جانشینها مانند کد پوینتهای معمولی رفتار میکند. استاندارد یونیکد بیان میکند که جانشینهای مستقل نامعتبر هستند، اما کتابخانهها و برنامههای دنیای واقعی رفتارهای متفاوتی دارند.
- --flex-syntax -F
- پشتیبانی جزئی از نحو Flex: در این حالت، تعاریف نامگذاریشده نیازی به علامت مساوی و سمیکولن پایانی ندارند، و هنگام استفاده باید در آکولاد محصور شوند. نامهای فاقد آکولاد بهصورت رشتههای درون نقلقول دوتایی در نظر گرفته میشوند.
- --goto-label
- استفاده از مدل کد "goto/label": کدگذاری DFA در قالب بلوکهای کد برچسبدار که با انتقالهای goto میان بلوکها به یکدیگر متصل میشوند. این ویژگی تنها برای زبانهایی پشتیبانی میشود که دارای دستور goto هستند.
- --header --type-header -t HEADER
- تولید یک فایل HEADER. محتوای فایل را میتوان با استفاده از بلوکهای ویژهٔ header:on و header:off مشخص کرد. اگر از conditionها استفاده شود، یک enum شرط بهطور خودکار به انتهای هدر تولیدشده افزوده میشود (مگر اینکه یک بلوک صریح conditions وجود داشته باشد).
- -I PATH
- افزودن PATH به فهرست مکانهایی که هنگام جستجو برای فایلهای include استفاده میشوند. این گزینه در ترکیب با بلوک یا دستورالعمل include مفید است. re2zig فایل FILE را در دایرکتوری فایل والد و در مکانهای include مشخصشده با گزینهٔ -I جستجو میکند.
- --input <default | custom>
- نام مستعار منسوخشده برای --api. گزینهٔ default معادل simple است (در واقع برای بیشتر backendها پیشفرض است، اما نه برای همه). گزینهٔ custom معادل generic است.
- --input-encoding <ascii | utf8>
- مشخص کردن نحوهٔ تجزیهٔ عبارتهای باقاعده توسط re2zig. با ascii (پیشفرض) re2zig ورودی را با کدگذاری ASCII مدیریت میکند: هر دنبالهای از واحدهای کد (code units)، دنبالهای از نویسههای تکبایتی مستقل در نظر گرفته میشود. با utf8 re2zig ورودی را با کدگذاری UTF8 مدیریت کرده و نویسههای چندبایتی را تشخیص میدهد.
- --invert-captures
- معکوس کردن مفهوم گروههای گیرانداز (capturing) و غیرگیرانداز (non-capturing). بهطور پیشفرض (...) گیرانداز و (! ...) غیرگیرانداز است. با این گزینه (! ...) گیرانداز و (...) غیرگیرانداز خواهد بود.
- --lang <none | c | d | go | haskell | java | js | ocaml | python | rust | swift | v | zig>
- مشخص کردن زبان مقصد. زبانهای پشتیبانیشده عبارتند از C، D، Go، Haskell، Java، JS، OCaml، Python، Rust، Swift، V، Zig (زبانهای بیشتری را میتوان از طریق فایلهای نحو تعریفشده توسط کاربر اضافه کرد، گزینهٔ --syntax را ببینید). گزینهٔ none پیکربندیهای نحوی پیشفرض را غیرفعال میکند، بهطوری که زبان مقصد تعریفنشده باقی میماند.
- --location-format <gnu | msvc>
- مشخص کردن قالب مکان در پیامها. با gnu مکانها بهصورت 'filename:line:column: ...' چاپ میشوند. با msvc مکانها بهصورت 'filename(line,column) ...' چاپ میشوند. پیشفرض gnu است.
- --loop-switch
- استفاده از مدل کد "loop/switch": کدگذاری DFA در قالب یک حلقه بر روی یک دستور switch، که در آن هر وضعیت مجزا یک case از switch است. وضعیت در متغیر yystate ذخیره میشود. انتقال بین وضعیتها، مقدار yystate را به برچسب case وضعیت مقصد بهروزرسانی کرده و اجرای کد را به ابتدای حلقه ادامه میدهد.
- --nested-ifs -s
- استفاده از دستورهای تودرتوی if بهجای دستورهای switch در پرشهای شرطی. این کار در کامپایلرهای غیربهینهساز معمولاً به تولید کد کارآمدتری منجر میشود.
- --no-debug-info -i
- عدم درج دستورالعملهای خط (line directives) در خروجی. این گزینه هنگامی که کد تولیدشده در یک سیستم کنترل نسخه ذخیره میشود میتواند مفید باشد (برای جلوگیری از تفاوتهای حجیم خودکار ناشی از تغییرات کوچک).
- --no-generation-date
- عدم درج تاریخ در فایل تولیدشده.
- --no-version
- عدم درج نسخه در فایل تولیدشده.
- --no-unsafe
- عدم تولید بستهبند unsafe روی YYPEEK (این گزینه مختص Rust است). به دلایل کارایی، YYPEEK باید از بررسی مرزها (bounds-checking) خودداری کند، چرا که تحلیلگر لغوی از پیش بررسیهای پایان ورودی را به شیوهای کارآمدتر انجام میدهد. کاربر میتواند یک تعریف امن از YYPEEK ارائه دهد، یا تعریفی که تنها در ساختهای release بهصورت ناامن باشد؛ در این حالت، گزینهٔ --no-unsafe به جلوگیری از هشدارهای مربوط به بلوکهای زائد unsafe کمک میکند.
- --output -o OUTPUT
- مشخص کردن فایل خروجی OUTPUT.
- --posix-captures, -P
- فعالسازی استخراج تطابقهای فرعی با گروههای گیرانداز به سبک POSIX. نتیجه در آرایهٔ yybmatch با ظرفیت 2 * YYMAXNMATCH جمعآوری شده و yynmatch روی تعداد گروههای قاعدهٔ منطبق تنظیم میشود.
- --posix-captvars
- فعالسازی استخراج تطابقهای فرعی با گروههای گیرانداز به سبک POSIX. نتیجه در متغیرهای yytl<k> و yytr<k> برای k-امین گروه گیرانداز ذخیره میشود.
- --recursive-functions
- استفاده از مدل کد مبتنی بر توابع همبازگشتی (co-recursive)، که در آن هر وضعیت DFA یک تابع مجزاست که میتواند سایر توابع وضعیت یا خودش را فراخوانی کند.
- --reusable -r
- از نسخهٔ 2.2 منسوخ شده است (بلوکهای با قابلیت استفادهٔ مجدد اکنون بهطور پیشفرض مجاز هستند).
- --skeleton -S
- نادیده گرفتن کد رابط تعریفشده توسط کاربر و تولید یک برنامهٔ خودکفای "اسکلت" (skeleton). علاوه بر این، فایلهای ورودی حاوی رشتههای مشتقشده از گرامر باقاعده و نتایج فشردهشدهٔ تطبیق را تولید میکند که برای تأیید رفتار "اسکلت" روی تمام ورودیها به کار میروند. این گزینه برای یافتن باگها در بهینهسازیها و تولید کد مفید است. این گزینه تنها برای C پشتیبانی میشود..TP --storable-state -f تولید یک lexer که میتواند وضعیت درونی خود را ذخیره کند. این قابلیت در lexerهای مبتنی بر مدل push که هنگام نبود ورودی کافی توسط یک برنامهٔ بیرونی متوقف شده و سپس با فراهم شدن ورودی بیشتر از سر گرفته میشوند، مفید است. در این حالت، کاربران باید علاوه بر این دستورهای اولیهٔ YYGETSTATE و YYSETSTATE را تعریف کنند، و متغیرهای yych، yyaccept و state باید بخشی از وضعیت ذخیرهشدهٔ lexer باشند.
- --syntax FILE
- بارگذاری پیکربندیها از FILE مشخصشده و اعمال آنها بر روی فایل نحو (syntax) پیشفرض. توجه داشته باشید که FILE میتواند تنها تعداد کمی پیکربندی را تعریف کند (در صورتی که برای اصلاح فایل نحو پیشفرض استفاده شود)، یا یک backend زبان کاملاً جدید را تعریف کند (در مورد اخیر توصیه میشود از گزینهٔ --lang none استفاده شود).
- فعالسازی استخراج زیرتطابق با برچسبها.
- --ucs2 --wide-chars -w
- تولید یک lexer که ورودی با کدگذاری UCS2 را میخواند. re2zig فرض میکند که بازهٔ نویسهها 0 -- 0xFFFF و اندازهٔ نویسه ۲ بایت است. این گزینه مستلزم --nested-ifs است.
- --utf8 --utf-8 -8
- تولید یک lexer که ورودی را با کدگذاری UTF-8 میخواند. re2zig فرض میکند که بازهٔ نویسهها 0 -- 0x10FFFF و اندازهٔ نویسه ۱ بایت است.
- --utf16 --utf-16 -x
- تولید یک lexer که ورودی با کدگذاری UTF16 را میخواند. re2zig فرض میکند که بازهٔ نویسهها 0 -- 0x10FFFF و اندازهٔ نویسه ۲ بایت است. این گزینه مستلزم --nested-ifs است.
- --utf32 --unicode -u
- تولید یک lexer که ورودی با کدگذاری UTF32 را میخواند. re2zig فرض میکند که بازهٔ نویسهها 0 -- 0x10FFFF و اندازهٔ نویسه ۴ بایت است. این گزینه مستلزم --nested-ifs است.
- --verbose
- نمایش پیامی کوتاه در صورت موفقیت.
- --vernum -V
- نمایش اطلاعات نسخه در قالب MMmmpp (اصلی، فرعی، وصله).
- --version -v
- نمایش اطلاعات نسخه.
- --single-pass -1
- منسوخشده. کاری انجام نمیدهد (اکنون تکگذره پیشفرض است).
- --debug-output -d
- گنجاندن فراخوانیهای YYDEBUG در کد تولیدشده. این مورد برای ردیابی اجرای lexer مفید است.
- --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
- تولید گراف lexer در قالب .dot به جای خروجی عادی. خروجی را میتوان با کمک Graphviz به یک تصویر تبدیل کرد (مثلاً دستوری مانند dot -Tpng -odfa.png dfa.dot).
- --dfa-minimization <moore | table>
- گزینهٔ داخلی: الگوریتم کمینهسازی DFA مورد استفاده توسط re2zig. گزینهٔ moore الگوریتم مور (Moore) است (که پیشفرض میباشد). گزینهٔ table الگوریتم "پر کردن جدول" است. هر دو الگوریتم باید صرفنظر از بازنامگذاری وضعیتها، DFA یکسانی تولید کنند؛ پر کردن جدول سادهتر و بسیار کندتر است و به عنوان پیادهسازی مرجع عمل میکند.
- --eager-skip
- گزینهٔ داخلی: وادار کردن lexer تولیدشده به جلو بردن مشتاقانهٔ موقعیت ورودی -- بلافاصله پس از خواندن نماد ورودی. این تغییر، رفتار پیشفرض را که در آن موقعیت ورودی به صورت تنبلانه -- پس از گذار به وضعیت بعدی، به جلو برده میشود، عوض میکند.
- --no-lookahead
- گزینهٔ داخلی، منسوخشده. در گذشته برای فعالسازی الگوریتم TDFA(0) به کار میرفت. برخلاف TDFA(1)، الگوریتم TDFA(0) از پیشنگری یکنمادی استفاده نمیکند. این الگوریتم عملیات ثبات را به جای گذارهای خروجی، روی گذارهای ورودی اعمال میکند. ارزیابیها نشان دادند که کارایی الگوریتم TDFA(0) کمتر از TDFA(1) است.
- --no-optimize-tags
- گزینهٔ داخلی: جلوگیری از بهینهسازی متغیرهای برچسب (مفید برای اشکالزدایی).
- --posix-closure <gor1 | gtop>
- گزینهٔ داخلی: تعیین الگوریتم کوتاهترین مسیر مورد استفاده برای ساخت بستار اپسیلون (epsilon-closure) با معناشناسی ابهامزدایی 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>
- گزینهٔ داخلی: مشخص میکند که آیا بهینهسازی برچسبهای ثابت (fixed-tag) باید روی همهٔ برچسبها (all)، هیچیک از آنها (none)، یا فقط برچسبهای موجود در الحاق سطح بالا (toplevel) اعمال شود. پیشفرض all است. برچسبهای "ثابت" برچسبهایی هستند که در فاصلهای ثابت نسبت به یک برچسب دیگر (که "پایه" نامیده میشود) قرار دارند. در چنین مواردی فقط برچسب پایه نیاز به ردیابی دارد، و مقدار برچسب ثابت را میتوان از مقدار برچسب پایه بهعلاوهٔ یک آفست ایستا محاسبه کرد. برای برچسبهایی که تحت انتخاب جایگزین یا تکرار هستند، همچنین لازم است بررسی شود که آیا برچسب پایه دارای مقدار عدم تطابق (no-match) هست یا خیر (در این حالت، با صرفنظر از آفست، برچسب ثابت نیز باید روی no-match تنظیم شود). برای برچسبها در الحاق سطح بالا نیازی به این بررسی نیست، زیرا آنها همیشه مطابقت دارند.
هشدارها (Warnings)
هشدارها را میتوان بهصورت جداگانه فعال، غیرفعال یا به خطا تبدیل کرد.
- -W
- تمام هشدارها را فعال میکند.
- -Werror
- هشدارها را به خطا تبدیل میکند. توجه داشته باشید که این گزینه بهتنهایی هیچ هشداری را فعال نمیکند؛ تنها بر هشدارهایی تأثیر میگذارد که تا کنون فعال شدهاند یا بعداً فعال خواهند شد.
- -W<warning>
- هشدار warning را فعال میکند.
- -Wno-<warning>
- هشدار warning را غیرفعال میکند.
- -Werror-<warning>
- هشدار warning را فعال کرده و با آن مانند یک خطا رفتار میکند (این گزینه متضمن -W<warning> است).
- -Wno-error-<warning>
- با این warning بهخصوص مانند یک خطا رفتار نمیکند. این کار خودِ هشدار را غیرفعال نمیسازد.
- -Wcondition-order
- اگر برنامهٔ تولیدشده فرضیات ضمنی دربارهٔ شمارهگذاری شرطها در نظر بگیرد، هشدار میدهد. کاربر باید از گزینهٔ --header یا بلوک conditions برای تولید نگاشت نام شرطها به اعداد و سپس استفاده از نامهای شرط تولیدشدهٔ خودکار استفاده کند.
- -Wempty-character-class
- اگر یک عبارت باقاعده حاوی یک کلاس نویسهٔ خالی باشد، هشدار میدهد. تلاش برای تطبیق یک کلاس نویسهٔ خالی معنایی ندارد: همیشه باید با شکست مواجه شود. با این حال، به دلایل سازگاری با گذشته، re2zig کلاسهای نویسهٔ خالی را مجاز میداند و با آنها مانند رشتههای خالی رفتار میکند. برای تغییر این رفتار پیشفرض از گزینهٔ --empty-class استفاده کنید.
- -Wmatch-empty-string
- اگر یک قاعده تهیپذیر باشد (با یک رشتهٔ خالی تطبیق یابد)، هشدار میدهد. اگر تحلیلگر لغوی در یک حلقه اجرا شود و تطبیق رشتهٔ خالی ناخواسته باشد، ممکن است تحلیلگر بهطور غیرمنتظرهای در یک حلقهٔ بیپایان متوقف شود.
- -Wswapped-range
- اگر کران پایین یک بازه بزرگتر از کران بالای آن باشد، هشدار میدهد. رفتار پیشفرض این است که بدون اعلام پیام مرزهای بازه جابهجا شوند.
- -Wundefined-control-flow
- اگر برخی از رشتههای ورودی باعث جریان کنترل تعریفنشده در تحلیلگر لغوی شوند، هشدار میدهد (الگوهای دارای اشکال گزارش میشوند). این یک خطای خطرناک و رایج است. میتوان آن را بهسادگی با افزودن قاعدهٔ پیشفرض * برطرف کرد که کمترین اولویت را دارد، با هر واحد کد تطبیق مییابد، و همیشه یک واحد کد را مصرف میکند.
- -Wunreachable-rules
- دربارهٔ قواعدی هشدار میدهد که زیر سایهٔ قواعد دیگر قرار گرفتهاند و هرگز مطابقت نخواهند یافت.
- -Wdeprecated-eof_rule
- دربارهٔ قواعد مستقل پایان ورودی $ هشدار میدهد که بر اثر تغییرات آینده از کار خواهند افتاد و نیاز به اصلاح دارند. در حال حاضر، این قواعد هنگام تداخل با قواعد دیگر اولویت دارند، اما پس از معرفی نماد تعمیمیافتهٔ پایان ورودی $، ترتیب اولویت تغییر خواهد کرد و این قواعد زیر سایهٔ قواعد دیگر قرار خواهند گرفت.
- -Wuseless-escape
- اگر نویسهای فرار (escape) داده شود در حالی که نیازی نبوده، هشدار میدهد. بهطور پیشفرض، re2zig چنین مواردی را نادیده میگیرد، اما این موضوع ممکن است نشاندهندهٔ یک غلط املایی یا خطا در توالی گریز (escape sequence) باشد.
- -Wnondeterministic-tags
- اگر یک برچسب دارای درجهٔ نامعین بودن n باشد، در جایی که n بزرگتر از ۱ است، هشدار میدهد.
- -Wsentinel-in-midrule
- اگر نماد دیدهبان (sentinel) در میانهٔ یک قاعده ظاهر شود هشدار میدهد --- این امر ممکن است باعث خواندن فراتر از انتهای بافر، خرابی (crash) یا فساد حافظه در تحلیلگر لغوی تولیدشده شود. این هشدار تنها در صورتی کاربرد دارد که روش دیدهبان برای بررسی پایان ورودی استفاده شده باشد. در صورت استفاده از پیکربندی re2c:sentinel، این هشدار به خطا تبدیل میشود.
- -Wundefined-syntax-config
- اگر فایل نحو مشخصشده با گزینهٔ --syntax فاقد تعاریف برخی از پیکربندیها باشد، هشدار میدهد. این امر به نگهداری فایلهای نحو سفارشی کمک میکند: اگر نسخهٔ جدیدی پیکربندیهایی را اضافه کند، فایل نحو قدیمی هشداری صادر کرده و کاربر مطلع میشود. اگر برخی از پیکربندیها بدون استفاده هستند و نیازی به تعریف ندارند، باید بهصراحت روی <undefined> تنظیم شوند.
فایلهای نحو (Syntax files)
پشتیبانی از زبانهای مختلف در re2c بر پایهٔ ایدهٔ فایلهای نحو استوار است. یک فایل نحو، فایلی پیکربندی است که نحو زبان مقصد را تعریف میکند -- نه کل زبان، بلکه بخش کوچکی از آن که توسط کد تولیدشده استفاده میشود. فایلهای نحو انعطافپذیری بالایی به re2c میبخشند، اما نباید به عنوان جایگزینی برای پیکربندیهای re2c: استفاده شوند: هدف آنها تعریف نحو زبان مقصد است، نه سفارشیسازی یک تحلیلگر لغوی خاص. تمام زبانهای پشتیبانیشده دارای فایلهای نحو پیشفرضی هستند که بخشی از توزیع به شمار میروند (زیردایرکتوری include/syntax را ببینید)؛ آنها همچنین در فایل باینری re2zig تعبیه شدهاند. کاربران میتوانند یک فایل نحو سفارشی ارائه دهند که چند پیکربندی را برای یکی از زبانهای پشتیبانیشده بازنویسی میکند، یا میتوانند همهٔ پیکربندیها را مجدداً تعریف کنند (در این حالت باید از گزینهٔ --lang none استفاده شود). فایلهای نحو شامل پیکربندیهایی در چهار نوع مختلف هستند: فهرست ویژگیها، پیکربندیهای زبان، پیکربندیهای درونجایی و الگوهای کد.
فهرستهای ویژگی (Feature lists)
- supported_apis
- فهرستی از APIهای پشتیبانیشده با مقادیر ممکنِ simple، record و generic.
- supported_api_styles
- فهرستی از شیوههای API پشتیبانیشده با مقادیر ممکنِ functions و free-form.
- supported_code_models
- فهرستی از مدلهای کد پشتیبانیشده با مقادیر ممکنِ goto-label، loop-switch و recursive-functions.
- supported_targets
- فهرستی از هدفهای تولید کد با مقادیر ممکنِ code، dot و skeleton.
- supported_features
- فهرستی از ویژگیهای پشتیبانیشده با مقادیر ممکنِ nested-ifs، bitmaps، computed-gotos، case-ranges، monadic، unsafe، tags، captures و captvars.
پیکربندیهای زبان (Language configurations)
- semicolons
- غیرصفر در صورتی که زبان پس از دستورها از نقطهویرگول (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 ']'
یک الگوی کد دنبالهای از لیترالهای رشتهای، متغیرها، مؤلفههای اختیاری و فهرستها، یا ارجاعی به یک الگوی کد دیگر، یا مقدار ویژهٔ <undefined> است. متغیرها جانگهدارندههایی هستند که در مرحلهٔ تولید کد جایگزین میشوند. متغیرهای فهرست خاص هستند: هنگام بسط الگوهای فهرست، re2zig عبارتهای سمت راست دونقطه را چند بار تکرار میکند، و هر بار رخدادهای متغیر فهرست را با مقداری مخصوص به همان تکرار جایگزین میسازد. فهرستها دارای کرانهای اختیاری هستند (مقادیر منفی از انتها شمرده میشوند، برای نمونه -1 به معنای آخرین مؤلفه است). نامهای شرطی با یک نقطه شروع میشوند. هم شرطها و هم متغیرها میتوانند محلی (مخصوص همان الگوی کد) یا سراسری (مجاز در تمام الگوهای کد) باشند. هنگامی که re2zig فایل نحو را میخواند، بررسی میکند که هر الگوی کد تنها از متغیرها و شرطهایی استفاده کند که در آن مجاز شناخته شدهاند.
برای نمونه، الگوی کد زیر ساختار 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
}
در ادامه فهرستی از تمام الگوهای کد پشتیبانیشده در re2zig به همراه متغیرها و شرطهای محلی آنها آورده شده است. توجه داشته باشید که یک تعریف مشخص میتواند از متغیرها و شرطهای محلی استفاده کند، اما الزامی به این کار ندارد. تمام الگوهای کد بدون استفاده باید روی <undefined> تنظیم شوند.
- code:var_local
- اعلان یا تعریف یک متغیر محلی. متغیرهای پشتیبانیشده: type (نوع متغیر)، name (نام آن) و init (مقدار اولیه، در صورت وجود). شرطها: .init (درست، اگر مقداردهی اولیه وجود داشته باشد).
- 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
- تعریف یک شمارشگر یا enum (ممکن است با ساختار زبانی ویژهای برای شمارشها تعریف شود، یا صرفاً به صورت چند مقدار ثابت مستقل). متغیرهای پشتیبانیشده عبارتند از: type (نوع شمارشی تعریفشده توسط کاربر یا نوع ثابتها)، elem (متغیر فهرستی که به نام هر عضو بسط مییابد) و init (مقداردهندهٔ اولیه برای هر عضو). شرطها: .init (درست، اگر مقداردهی اولیه وجود داشته باشد).
- code:enum_elem
- عنصر شمارشی (عضوی از نوع شمارشی تعریفشده توسط کاربر یا نام یک مقدار ثابت، بسته به نحوهٔ تعریف code:enum). متغیرهای پشتیبانیشده عبارتند از: name (نام عنصر) و type (نوع آن).
- code:assign
- دستور انتساب. متغیرهای پشتیبانیشده عبارتند از: lhs (سمت چپ) و rhs (سمت راست).
- code:type_int
- نوع عدد صحیح علامتدار (signed).
- code:type_uint
- نوع عدد صحیح بدون علامت (unsigned).
- code:type_yybm
- نوع عناصر در جدول yybm.
- code:type_yytarget
- نوع عناصر در جدول yytarget.
- code:type_yyctable
- نوع عناصر در جدول yyctable.
- code:cmp_eq
- عملگر "برابر با" (equals).
- code:cmp_ne
- عملگر "نابرابر با" (not equals).
- code:cmp_lt
- عملگر "کوچکتر از" (less than).
- code:cmp_gt
- عملگر "بزرگتر از" (greater than).
- code:cmp_le
- عملگر "کوچکتر یا مساوی" (less or equal).
- code:cmp_ge
- عملگر "بزرگتر یا مساوی" (greater or equal).
- code:if_then_else
- دستور if-then-else با یک یا چند شاخه. متغیرهای پشتیبانیشده: branch (متغیری از نوع فهرست که خودش هیچ کدی تولید نمیکند، اما عبارت فهرست را به تعداد شاخهها بسط میدهد)، cond (شرط شاخهٔ جاری) و stmt (متغیری از نوع فهرست که به تمام دستورات شاخهٔ جاری بسط مییابد). شرطها: .cond (درست، اگر شاخهٔ جاری دارای شرط باشد)، .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
- گروهی از حالتهای 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 goto). متغیرهای پشتیبانیشده: 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 (درست، اگر تابع از نوع غیرِ void باشد).
- code:fndef
- مشابه code:fndecl، اما برای تعریف تابع به کار میرود؛ بنابراین دارای یک متغیر فهرستی اضافی stmt است که به تمام دستورات موجود در بدنهٔ تابع بسط مییابد.
- code:fncall
- دستور فراخوانی تابع. متغیرهای پشتیبانیشده: name (نام تابع)، retval (مقدار چپ یا l-value که مقدار بازگشتی در آن ذخیره میشود، در صورت وجود) و arg (متغیری از نوع فهرست که به تمام آرگومانهای تابع بسط مییابد). شرطها: .args (درست، اگر تابع دارای آرگومان باشد) و .retval (درست، اگر ذخیرهسازی مقدار بازگشتی لازم باشد).
- code:tailcall
- دستور فراخوانی انتهایی (tail call). متغیرهای پشتیبانیشده: name (نام تابع) و arg (متغیری از نوع فهرست که به تمام آرگومانهای تابع بسط مییابد). شرطها: .args (درست، اگر تابع دارای آرگومان باشد) و .retval (درست، اگر تابع از نوع غیرِ void باشد).
- code:recursive_functions
- بدنهٔ برنامه با مدل کد --recursive-functions. متغیرهای پشتیبانیشده: fn (متغیری از نوع فهرست که خودش کدی تولید نمیکند، اما عبارت فهرست را به تعداد توابع بسط میدهد)، fndecl (اعلان تابع جاری) و fndef (تعریف تابع جاری).
- code:fingerprint
- اثر انگشت (fingerprint) در بالای فایل خروجی تولیدشده. متغیرهای پشتیبانیشده: ver (نسخهٔ re2zig مورد استفاده برای تولید این مورد) و date (تاریخ تولید).
- code:line_info
- قالب دستورات خط (line directives) (اگر روی <undefined> تنظیم شده باشد، هیچ دستوری تولید نمیشود). متغیرهای پشتیبانیشده: line (شمارهٔ خط) و file (نام فایل).
- code:abort
- دستوری که اجرای برنامه را متوقف (abort) میکند.
- code:yydebug
- دستور YYDEBUG، احتمالاً سفارشیشده برای رابطهای برنامهنویسی کاربردی (API) مختلف. متغیرهای پشتیبانیشده: YYDEBUG، yyrecord، yych (نگاشت به پیکربندیهای متناظر در re2c:)، state (شمارهٔ وضعیت DFA)..TP code:yypeek دستور YYPEEK، احتمالاً برای APIهای گوناگون سفارشیشده است. متغیرهای پشتیبانیشده: YYPEEK، YYCTYPE، YYINPUT، YYCURSOR، yyrecord، yych (به پیکربندیهای متناظر re2c: نگاشت میشوند). شرطها: .cast (اگر re2c:yych:conversion روی مقداری غیر از صفر تنظیم شده باشد، درست است).
- code:yyskip
- دستور YYSKIP، احتمالاً برای APIهای گوناگون سفارشیشده است. متغیرهای پشتیبانیشده: YYSKIP، YYCURSOR، yyrecord (به پیکربندیهای متناظر re2c: نگاشت میشوند).
- code:yybackup
- دستور YYBACKUP، احتمالاً برای APIهای گوناگون سفارشیشده است. متغیرهای پشتیبانیشده: YYBACKUP، YYCURSOR، YYMARKER، yyrecord (به پیکربندیهای متناظر re2c: نگاشت میشوند).
- code:yybackupctx
- دستور YYBACKUPCTX، احتمالاً برای APIهای گوناگون سفارشیشده است. متغیرهای پشتیبانیشده: YYBACKUPCTX، YYCURSOR، YYCTXMARKER، yyrecord (به پیکربندیهای متناظر re2c: نگاشت میشوند).
- code:yyskip_yypeek
- دستور ترکیبی code:yyskip و code:yypeek (پیشفرض code:yyskip و به دنبال آن code:yypeek است).
- code:yypeek_yyskip
- دستور ترکیبی code:yypeek و code:yyskip (پیشفرض code:yypeek و به دنبال آن code:yyskip است).
- code:yyskip_yybackup
- دستور ترکیبی code:yyskip و code:yybackup (پیشفرض code:yyskip و به دنبال آن code:yybackup است).
- code:yybackup_yyskip
- دستور ترکیبی code:yybackup و code:yyskip (پیشفرض code:yybackup و به دنبال آن code:yyskip است).
- code:yybackup_yypeek
- دستور ترکیبی code:yybackup و code:yypeek (پیشفرض code:yybackup و به دنبال آن code:yypeek است).
- code:yyskip_yybackup_yypeek
- دستور ترکیبی code:yyskip، code:yybackup و code:yypeek (پیشفرض ``code:yyskip`` و به دنبال آن code:yybackup و سپس code:yypeek است).
- code:yybackup_yypeek_yyskip
- دستور ترکیبی code:yybackup، code:yypeek و code:yyskip (پیشفرض ``code:yybackup`` و به دنبال آن code:yypeek و سپس code:yyskip است).
- code:yyrestore
- دستور YYRESTORE، احتمالاً برای APIهای گوناگون سفارشیشده است. متغیرهای پشتیبانیشده: YYRESTORE، YYCURSOR، YYMARKER، yyrecord (به پیکربندیهای متناظر re2c: نگاشت میشوند).
- code:yyrestorectx
- دستور YYRESTORECTX، احتمالاً برای APIهای گوناگون سفارشیشده است. متغیرهای پشتیبانیشده: YYRESTORECTX، YYCURSOR، YYCTXMARKER، yyrecord (به پیکربندیهای متناظر re2c: نگاشت میشوند).
- code:yyrestoretag
- دستور YYRESTORETAG، احتمالاً برای APIهای گوناگون سفارشیشده است. متغیرهای پشتیبانیشده: YYRESTORETAG، YYCURSOR، yyrecord (به پیکربندیهای متناظر re2c: نگاشت میشوند)، tag (نام متغیر برچسب استفادهشده برای بازیابی موقعیت).
- code:yyshift
- دستور YYSHIFT، احتمالاً برای APIهای گوناگون سفارشیشده است. متغیرهای پشتیبانیشده: YYSHIFT، YYCURSOR، yyrecord (به پیکربندیهای متناظر re2c: نگاشت میشوند)، offset (تعداد واحدهای کد برای جابهجایی موقعیت فعلی).
- code:yyshiftstag
- دستور YYSHIFTSTAG، احتمالاً برای APIهای گوناگون سفارشیشده است. متغیرهای پشتیبانیشده: YYSHIFTSTAG، yyrecord، negative (به پیکربندیهای متناظر re2c: نگاشت میشوند)، tag (متغیر برچسبی که باید جابهجا شود)، offset (تعداد واحدهای کد برای جابهجایی). شرطها: .nested (اگر این یک برچسب تو در تو باشد درست است -- در این حالت ممکن است مقدار آن برابر با re2c:tags:negative باشد که نباید جابهجا شود).
- code:yyshiftmtag
- دستور YYSHIFTMTAG، احتمالاً برای APIهای گوناگون سفارشیشده است. متغیرهای پشتیبانیشده: YYSHIFTMTAG (به پیکربندی متناظر re2c: نگاشت میشود)، tag (متغیر برچسبی که باید جابهجا شود)، offset (تعداد واحدهای کد برای جابهجایی).
- code:yystagp
- دستور YYSTAGP، احتمالاً برای APIهای گوناگون سفارشیشده است. متغیرهای پشتیبانیشده: YYSTAGP، YYCURSOR، yyrecord (به پیکربندیهای متناظر re2c: نگاشت میشوند)، tag (متغیر برچسبی که باید بهروزرسانی شود).
- code:yymtagp
- دستور YYMTAGP، احتمالاً برای APIهای گوناگون سفارشیشده است. متغیرهای پشتیبانیشده: YYMTAGP (به پیکربندی متناظر re2c: نگاشت میشود)، tag (متغیر برچسبی که باید بهروزرسانی شود).
- code:yystagn
- دستور YYSTAGN، احتمالاً برای APIهای گوناگون سفارشیشده است. متغیرهای پشتیبانیشده: YYSTAGN، negative، yyrecord (به پیکربندیهای متناظر re2c: نگاشت میشوند)، tag (متغیر برچسبی که باید بهروزرسانی شود).
- code:yymtagn
- دستور YYMTAGN، احتمالاً برای APIهای گوناگون سفارشیشده است. متغیرهای پشتیبانیشده: YYMTAGN (به پیکربندی متناظر re2c: نگاشت میشود)، tag (متغیر برچسبی که باید بهروزرسانی شود).
- code:yycopystag
- دستور YYCOPYSTAG، احتمالاً برای APIهای گوناگون سفارشیشده است. متغیرهای پشتیبانیشده: YYCOPYSTAG، yyrecord (به پیکربندیهای متناظر re2c: نگاشت میشوند)، lhs، rhs (متغیرهای برچسب سمت چپ و راست در عملیات کپی).
- code:yycopymtag
- دستور YYCOPYMTAG، احتمالاً برای APIهای گوناگون سفارشیشده است. متغیرهای پشتیبانیشده: YYCOPYMTAG، yyrecord (به پیکربندیهای متناظر re2c: نگاشت میشوند)، lhs، rhs (متغیرهای برچسب سمت چپ و راست در عملیات کپی).
- code:yygetaccept
- دستور YYGETACCEPT، احتمالاً برای APIهای گوناگون سفارشیشده است. متغیرهای پشتیبانیشده: YYGETACCEPT، yyrecord (به پیکربندیهای متناظر re2c: نگاشت میشوند)، var (به پیکربندی re2c:yyaccept نگاشت میشود).
- code:yysetaccept
- دستور YYSETACCEPT، احتمالاً برای APIهای گوناگون سفارشیشده است. متغیرهای پشتیبانیشده: YYSETACCEPT، yyrecord (به پیکربندیهای متناظر re2c: نگاشت میشوند)، var (به پیکربندی re2c:yyaccept نگاشت میشود) و val (مقدار عددی قاعده پذیرفتهشده).
- code:yygetcond
- دستور YYGETCOND، احتمالاً برای APIهای گوناگون سفارشیشده است. متغیرهای پشتیبانیشده: YYGETCOND، yyrecord (به پیکربندیهای متناظر re2c: نگاشت میشوند)، var (به پیکربندی re2c:yycond نگاشت میشود).
- code:yysetcond
- دستور YYSETCOND، احتمالاً برای APIهای گوناگون سفارشیشده است. متغیرهای پشتیبانیشده: YYSETCOND، yyrecord (به پیکربندیهای متناظر re2c: نگاشت میشوند)، var (به پیکربندی re2c:yycond نگاشت میشود) و val (شناسه عددی شرط).
- code:yygetstate
- دستور YYGETSTATE، احتمالاً برای APIهای گوناگون سفارشیشده است. متغیرهای پشتیبانیشده: YYGETSTATE، yyrecord (به پیکربندیهای متناظر re2c: نگاشت میشوند)، var (به پیکربندی re2c:yystate نگاشت میشود).
- code:yysetstate
- دستور YYSETSTATE، احتمالاً برای APIهای گوناگون سفارشیشده است. متغیرهای پشتیبانیشده: YYSETSTATE، yyrecord (به پیکربندیهای متناظر re2c: نگاشت میشوند)، var (به پیکربندی re2c:yystate نگاشت میشود) و val (شماره وضعیت).
- code:yylessthan
- دستور YYLESSTHAN، احتمالاً برای APIهای گوناگون سفارشیشده است. متغیرهای پشتیبانیشده: YYLESSTHAN، YYCURSOR، YYLIMIT، yyrecord (به پیکربندیهای متناظر re2c: نگاشت میشوند)، need (تعداد واحدهای کد برای بررسی در برابر آن). شرط: .many (اگر need بیش از یک باشد درست است).
- code:yyend
- عبارت YYEND، احتمالاً برای APIهای گوناگون سفارشیشده است. متغیرهای پشتیبانیشده: YYEND، YYCURSOR، YYLIMIT.
- code:yybm_filter
- شرطی که برای فیلتر کردن مقادیر yych که توسط جدول yybm پوشش داده نشدهاند استفاده میشود (همراه با گزینه --bitmaps به کار میرود). متغیر پشتیبانیشده: yych (به پیکربندی re2c:yych نگاشت میشود).
- code:yybm_match
- قالب بررسی جدول yybm (تولیدشده با گزینه --bitmaps). متغیرهای پشتیبانیشده: yybm، yych (به پیکربندیهای متناظر re2c: نگاشت میشوند)، offset (آفست در جدول yybm که باید به yych اضافه شود) و mask (ماسک بیتی که باید روی مدخل جدول اعمال شود تا مقدار بولی مورد نیاز برای بررسی به دست آید).
- code:yytarget_filter
- شرطی که برای فیلتر کردن مقادیر yych که توسط جدول yytarget پوشش داده نشدهاند استفاده میشود (همراه با گزینه --computed-gotos به کار میرود). متغیر پشتیبانیشده: yych (به پیکربندی re2c:yych نگاشت میشود).
در اینجا فهرستی از تمام متغیرهای سراسری مجاز در فایلهای نحو (syntax files) آمده است:
- nl
- یک خط جدید (newline).
- indent
- متغیری که هیچ کدی تولید نمیکند، اما اثر جانبی افزایش سطح تورفتگی را دارد..TP dedent متغیری که هیچ کدی تولید نمیکند، اما دارای اثر جانبیِ کاهش سطح دندانهگذاری است.
- topindent
- رشته دندانهگذاری برای دستور فعلی. سطح دندانهگذاری توسط تولیدکننده کد ردیابی شده و بهطور خودکار بهروزرسانی میشود.
در اینجا فهرستی از تمام شرطهای سراسریِ مجاز در فایلهای نحو (syntax) آورده شده است:
- .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 آزاد (free-form) استفاده شود (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
- درست است اگر اثرانگشت (fingerprint) تولیدشده باید حاوی تاریخ تولید باشد.
- .loop_label
- درست است اگر حلقههای تولیدشده توسط re2zig باید دارای برچسب باشند (re2c:label:yyloop روی یک رشته غیرخالی تنظیم شده باشد).
- .monadic
- درست است اگر کد تولیدشده باید بهصورت مونادیک باشد (re2c:monadic = 1). این مورد تنها برای زبانهای تابعی محض مرتبط است.
- .start_conditions
- درست است اگر شرایط شروع (start conditions) فعال باشند (--start-conditions).
- .storable_state
- درست است اگر وضعیت قابل ذخیرهسازی فعال باشد (--storable-state).
- .unsafe
- درست است اگر re2zig برای تولید کد سریعتر باید از بلوکهای "unsafe" استفاده کند (--unsafe، re2c:unsafe = 1). این مورد تنها برای زبانهایی کاربرد دارد که دارای ویژگی "unsafe" هستند.
- .version
- درست است اگر اثرانگشت (fingerprint) تولیدشده باید حاوی نسخه re2zig باشد.
- .yyfill.enable
- درست است اگر YYFILL فعال باشد (re2c:yyfill:enable = 1).
- .yyfn.throw
- درست است اگر پیکربندی re2c:yyfn:throw روی یک رشته غیرخالی تعریف شده باشد.
مدیریت پایان ورودی (HANDLING THE END OF INPUT)
یکی از چالشهای اصلی برای تحلیلگر لغوی (lexer)، دانستن زمان توقف است. چندین شرط برای پایان کار وجود دارد:
- تحلیلگر لغوی ممکن است با یک قاعده (شامل قاعده پیشفرض *) تطبیق یابد و به یک وضعیت نهایی برسد
- تحلیلگر لغوی ممکن است در تطبیق با هر قاعدهای شکست بخورد و به وضعیت پیشفرض برسد
- تحلیلگر لغوی ممکن است به پایان ورودی برسد
دو شرط اول کار تحلیلگر لغوی را بهطور «طبیعی» خاتمه میدهند: تحلیلگر به وضعیتی بدون گذارهای خروجی میرسد و تطبیق بهطور خودکار متوقف میشود. اما شرط سوم، یعنی پایان ورودی، متفاوت است: این وضعیت ممکن است در هر حالتی رخ دهد و تحلیلگر باید بتواند آن را مدیریت کند. بررسی پایان ورودی، جریان عادی کار تحلیلگر لغوی را قطع کرده و شاخههای شرطی به برنامه تولیدشده اضافه میکند؛ از این رو لازم است تعداد این بررسیها به حداقل برسد. re2zig از چند روش مختلف برای مدیریت پایان ورودی پشتیبانی میکند. انتخاب روش به پیچیدگی عبارات باقاعده، نیاز به بافرسازی، ملاحظات کارایی و عوامل دیگر بستگی دارد. در ادامه فهرستی از این روشها آورده شده است:
- نگهبان (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 پوشش داده شود. این کار تعداد بررسیها را کاهش میدهد، اما مشکلی در مورد لکسمهای کوتاه در پایان ورودی به وجود میآید، زیرا این بررسی به نویسههای کافی برای پوشش طولانیترین لکسم نیاز دارد. این مشکل را میتوان با لایهگذاری (padding) ورودی با چند نویسه ساختگی که پسوند لکسم معتبری تشکیل نمیدهند (به طوری که تحلیلگر نتواند با آنها تطبیق یابد) برطرف کرد. طول این پدینگ باید YYMAXFILL باشد که با یک بلوک max تولید میشود. اگر ورودی کافی نباشد، تحلیلگر تابع YYFILL را فراخوانی میکند که باید حداقل تعداد نویسه مورد نیاز را تأمین کند یا بازگشتی نداشته باشد. این روش در صورتی استفاده میشود که YYFILL فعال باشد و re2c:eof برابر -1 باشد (این پیکربندی پیشفرض است).
- بررسیهای سفارشی (Custom checks). رابط عمومی (Generic API) امکان بازنویسی عملیات پایه مانند خواندن یک نویسه را فراهم میسازد، که گنجاندن بررسیهای پایان ورودی را به عنوان بخشی از خود این عملیات امکانپذیر میکند. این روش خطاپذیر است و باید با احتیاط استفاده شود. برای استفاده از روش سفارشی، Generic API را با --api custom یا re2c:api = custom; فعال کرده و بررسیهای پیشفرض مرز را با re2c:yyfill:enable = 0; یا re2c:yyfill:check = 0; غیرفعال نمایید.
زیربخشهای بعدی شامل مثالی از هر روش هستند.
نگهبان (Sentinel)
این مثال از یک کاراکتر نگهبان (sentinel) برای مدیریت پایان ورودی استفاده میکند. برنامه کلمات جداشده با فاصله را در یک رشتهٔ خاتمهیافته با null شمارش میکند. نگهبان null است: این آخرین کاراکتر هر رشتهٔ ورودی است و طبق هیچیک از قواعد در میانهٔ یک جزء واژگانی (lexeme) مجاز نیست (بهویژه در بازههای کاراکتری که نادیدهگرفتن آن آسان است، گنجانده نشده است). اگر یک null در میانهٔ رشته رخ دهد، یک خطای نحوی است و تحلیلگر لغوی با قاعدهٔ پیشفرض * مطابقت مییابد، اما فراتر از انتهای ورودی را نمیخواند و کرش نمیکند (برای بررسی این موضوع از هشدار -Wsentinel-in-midrule و پیکربندی re2c:sentinel استفاده کنید). پیکربندی re2c:yyfill:enable = 0; از تولید بررسیهای مرزی و فراخوانیهای YYFILL جلوگیری میکند.
// re2zig $INPUT -o $OUTPUT
const std = @import("std");
// Expects a null-terminated string.
fn lex(yyinput: [:0]const u8) i32 {
var yycursor: u32 = 0;
var count: i32 = 0;
loop: while (true) {
%{
re2c:yyfill:enable = 0;
* { return -1; }
[\x00] { return count; }
[a-z]+ { count += 1; continue :loop; }
[ ]+ { continue :loop; }
%}
}
}
test {
try std.testing.expectEqual(lex(""), 0);
try std.testing.expectEqual(lex("one two three"), 3);
try std.testing.expectEqual(lex("f0ur"), -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 میشود.
// re2zig $INPUT -o $OUTPUT
const std = @import("std");
// Expects a null-terminated string.
fn lex(yyinput: [:0]const u8) i32 {
var yycursor: usize = 0;
var yymarker: usize = 0;
const yylimit: usize = yyinput.len; // points at the terminating null
var count: i32 = 0;
loop: while (true) {
%{
re2c:yyfill:enable = 0;
re2c:eof = 0;
str = ['] ([^'\\] | [\\][^])* ['];
* { return -1; }
$ { return count; }
str { count += 1; continue :loop; }
[ ]+ { continue :loop; }
%}
}
}
test {
try std.testing.expectEqual(lex(""), 0);
try std.testing.expectEqual(lex("'qu\x00tes' 'are' 'fine: \\'' "), 3);
try std.testing.expectEqual(lex("'unterminated\\'"), -1);
}
بررسیهای مرزی با پدینگ (Bounds checks with padding)
این مثال از بررسیهای مرزی همراه با پدینگ برای مدیریت پایان ورودی استفاده میکند (این روش بهطور پیشفرض فعال است). برنامه رشتههای محصور در تککوتیشن که با فاصله جدا شدهاند را شمارش میکند. پدینگی شامل YYMAXFILL کاراکتر null به انتهای ورودی افزوده شده است، که مقدار YYMAXFILL بهطور خودکار توسط یک بلوک max تولید میشود. استفاده از null برای پدینگ الزامی نیست --- هر کاراکتری را میتوان استفاده کرد تا زمانی که یک پسوند معتبر برای جزء واژگانی (lexeme) تشکیل ندهند (در این مثال، پدینگ نباید شامل تککوتیشن باشد، زیرا ممکن است به عنوان پسوند یک رشتهٔ محصور در تککوتیشن اشتباه گرفته شود). یک قاعدهٔ "stop" وجود دارد که با اولین کاراکتر پدینگ (null) مطابقت یافته و تحلیلگر لغوی را پایان میدهد (توجه داشته باشید که بررسی میکند آیا null در ابتدای پدینگ قرار دارد، در غیر این صورت یک خطای نحوی محسوب میشود). بررسیهای مرزی تنها در برخی وضعیتها تولید میشوند که توسط مؤلفههای قویاً همبند (strongly connected components) اتوماتای زیرین تعیین میگردند. بررسیها به شکل (YYLIMIT - YYCURSOR) < n یا با API عمومی به صورت YYLESSTHAN(n) هستند، که در آن n حداقل تعداد کاراکترهای مورد نیاز برای ادامهٔ کار تحلیلگر لغوی است (همچنین بدین معناست که بررسی مرزی بعدی حداکثر تا n کاراکتر دیگر رخ خواهد داد). اگر شرط بررسی درست باشد، تحلیلگر لغوی به انتهای ورودی رسیده است و YYFILL(n) را فراخوانی میکند که باید یا حداقل n کاراکتر ورودی فراهم کند یا بازنگردد. در این مثال YYFILL همیشه با شکست مواجه میشود و تحلیلگر لغوی را با یک خطا متوقف میکند (که مشکلی ایجاد نمیکند، زیرا کل ورودی در یک بافر جای میگیرد). برای مشاهدهٔ مثالی که بافر ورودی را با YYFILL مجدداً پر میکند، به بخش YYFILL with padding مراجعه کنید.
// re2zig $INPUT -o $OUTPUT
const std = @import("std");
%{max %}
fn lex(str: []const u8) !i32 {
// Create a copy of the input string padded with yymaxfill zeroes at the end.
var yyinput = try std.testing.allocator.alloc(u8, str.len + yymaxfill);
defer std.testing.allocator.free(yyinput);
std.mem.copyForwards(u8, yyinput[0..], str);
std.mem.copyForwards(u8, yyinput[str.len..], &[_]u8{0} ** yymaxfill); // zero padding
var yycursor: usize = 0;
const yylimit: usize = yyinput.len;
var count: i32 = 0;
loop: while (true) {
%{
re2c:YYFILL = "return -1;";
str = ['] ([^'\\] | [\\][^])* ['];
[\x00] {
// Check that it is the sentinel, not some unexpected null.
return if (yycursor - 1 == str.len) count else -1;
}
str { count += 1; continue :loop; }
[ ]+ { continue :loop; }
* { return -1; }
%}
}
}
test {
try std.testing.expectEqual(lex(""), 0);
try std.testing.expectEqual(lex("'qu\x00tes' 'are' 'fine: \\'' "), 3);
try std.testing.expectEqual(lex("'unterminated\\'"), -1);
try std.testing.expectEqual(lex("'unexpected \x00 null\\'"), -1);
}
بررسیهای سفارشی (Custom checks)
این مثال از یک روش سفارشی برای مدیریت پایان ورودی بر پایهٔ generic API استفاده میکند. این برنامه رشتههای محصور در علامت نقلقول تکی را که با فاصله از هم جدا شدهاند، شمارش میکند. این مثال همانند مثال sentinel است، با این تفاوت که ورودی با نویسهٔ پوچ (null-terminated) خاتمه نیافته است. برای جبران نبودِ نویسهٔ نگهبان در پایان ورودی، YYPEEK بازتعریف شده است تا پیش از خواندن نویسهٔ بعدی ورودی، بررسی محدوده (bounds check) را انجام دهد. این کار ناکارآمد است زیرا بررسیها بسیار تکرار میشوند. اگر شرط بررسی برقرار نباشد، YYPEEK نویسهٔ واقعی را برمیگرداند، در غیر این صورت یک نویسهٔ نگهبان ساختگی بازمیگرداند.
// re2zig $INPUT -o $OUTPUT
const std = @import("std");
// Expects a string without terminating null.
fn lex(str: []const u8) i32 {
var cur: usize = 0;
var count: i32 = 0;
loop: while (true) {
%{
re2c:api = generic;
re2c:yyfill:enable = 0;
// YYPEEK returns "fake" terminating null if cursor has reached limit.
re2c:YYPEEK = "if (cur >= str.len) 0 else str[cur]";
re2c:YYSKIP = "cur += 1;";
* { return -1; }
[\x00] { return count; }
[a-z]+ { count += 1; continue :loop; }
[ ]+ { continue :loop; }
%}
}
}
test {
try std.testing.expectEqual(lex(""), 0);
try std.testing.expectEqual(lex("one two three"), 3);
try std.testing.expectEqual(lex("f0ur"), -1);
}
پر کردن مجدد بافر (BUFFER REFILLING)
نیاز به بافرسازی زمانی پیش میآید که ورودی نتواند بهیکباره در حافظه نگاشت شود: یا بیش از حد بزرگ است، یا به صورت جریانی (مانند خواندن از یک سوکت) دریافت میشود. روش معمول در این موارد، تخصیص یک بافر حافظه با اندازهٔ ثابت و پردازش ورودی در تکههایی است که در این بافر جا میشوند. هنگامی که تکهٔ فعلی پردازش شد، به بیرون منتقل شده و دادههای جدید وارد میشوند. در عمل این موضوع تا حدی پیچیدهتر است، زیرا وضعیت تحلیلگر لغوی (lexer) تنها از یک موقعیت ورودی تشکیل نشده، بلکه مجموعهای از موقعیتهای مرتبط با یکدیگر است:
- مکاننما (cursor): نویسهٔ بعدی ورودی که باید خوانده شود (YYCURSOR در API اشارهگر C یا YYSKIP/YYPEEK در generic API)
- حد (limit): موقعیت بعد از آخرین نویسهٔ موجود ورودی (YYLIMIT در API اشارهگر C، که بهطور ضمنی توسط YYLESSTHAN در generic API مدیریت میشود)
- نشانگر (marker): موقعیت جدیدترین تطابق، در صورت وجود (YYMARKER در API پیشفرض یا YYBACKUP/YYRESTORE در generic API)
- توکن (token): آغاز لکسم (lexeme) جاری (در API مربوط به re2zig ضمنی است، زیرا برای عملکرد عادی lexer نیازی به آن نیست و میتواند توسط کاربر تعریف و بهروزرسانی شود)
- نشانگر بافتار (context marker): موقعیت بافتار پسآیند (YYCTXMARKER در API اشارهگر C یا YYBACKUPCTX/YYRESTORECTX در generic API)
- متغیرهای برچسب (tag variables): موقعیتهای زیرتطابق (تعریفشده با بلوکهای stags و mtags و توابع اولیهٔ generic API شامل YYSTAGP/YYSTAGN/YYMTAGP/YYMTAGN)
همهٔ اینها در هر حالتی استفاده نمیشوند، اما در صورت استفاده، باید توسط YYFILL بهروزرسانی شوند. تمام موقعیتهای فعال در بخش بین token و cursor قرار دارند، بنابراین هر چیزی بین شروع بافر و token میتواند دور ریخته شود، بخشِ از token تا limit باید به ابتدای بافر منتقل شود، و فضای خالی در انتهای بافر باید با دادههای جدید پر شود. برای جلوگیری از فراخوانیهای مکرر YYFILL، بهتر است تا جای ممکن نویسههای ورودی بیشتری پر شوند (حتی اگر نویسههای کمتری برای ازسرگیری کار lexer کافی باشد). جزئیات پیادهسازی YYFILL بسته به اینکه از کدام روش مدیریت EOF استفاده میشود اندکی متفاوت است: حالت قاعدهٔ EOF تا حدودی سادهتر از حالت بررسی محدوده همراه با لایه پرکننده (padding) است. همچنین توجه داشته باشید که اگر گزینهٔ -f --storable-state استفاده شود، YYFILL معناشناسی متفاوتی خواهد داشت (که در بخش مربوط به وضعیت قابلذخیره شرح داده شده است).
YYFILL با نویسه نگهبان (YYFILL with sentinel)
اگر از قاعدهٔ EOF استفاده شود، YYFILL یک دستور اولیه شبهتابع است که هیچ آرگومانی نمیپذیرد و مقداری را بازمیگرداند که با صفر مقایسه میشود. فراخوانی YYFILL با شرط YYLIMIT <= YYCURSOR در API اشارهگر C و YYLESSTHAN() در generic API برانگیخته میشود. یک مقدار بازگشتی غیرصفر به این معنی است که YYFILL ناموفق بوده است. یک فراخوانی موفق YYFILL باید حداقل یک نویسه را فراهم کرده و موقعیتهای ورودی را بر این اساس تنظیم کند. Limit همیشه باید روی یک موقعیت بعد از آخرین موقعیت ورودی در بافر تنظیم شود، و نویسهٔ موجود در موقعیت limit باید نماد نگهبان مشخصشده توسط پیکربندی re2c:eof باشد. تصاویر زیر مکانهای نسبی موقعیتهای ورودی را در بافر قبل و بعد از فراخوانی YYFILL نشان میدهند (نماد نگهبان با # مشخص شده است، و تصویر دوم حالتی را نشان میدهد که ورودی کافی برای پر کردن کل بافر وجود ندارد).
<-- shift -->
>-A------------B---------C-------------D#-----------E->
buffer token marker limit,
cursor
>-A------------B---------C-------------D------------E#->
buffer, marker cursor limit
token
<-- shift -->
>-A------------B---------C-------------D#--E (EOF)
buffer token marker limit,
cursor
>-A------------B---------C-------------D---E#........
buffer, marker cursor limit
token
در اینجا مثالی از برنامهای آورده شده است که فایل ورودی input.txt را در تکههای ۴۰۹۶ بایتی میخواند و از قاعدهٔ EOF استفاده میکند.
// re2zig $INPUT -o $OUTPUT
const std = @import("std");
const bufsize = 4095;
const State = struct {
file: *std.Io.Reader,
yyinput: [bufsize + 1]u8,
yycursor: usize,
yymarker: usize,
yylimit: usize,
token: usize,
eof: bool
};
fn fill(st: *State) i32 {
if (st.eof) { return -1; } // unexpected EOF
// Error: lexeme too long. In real life can reallocate a larger buffer.
if (st.token < 1) { return -2; }
// Shift buffer contents (discard everything up to the current token).
std.mem.copyBackwards(
u8, st.yyinput[0..st.yylimit - st.token], st.yyinput[st.token..st.yylimit]);
st.yycursor -= st.token;
st.yymarker = @subWithOverflow(st.yymarker, st.token)[0];
st.yylimit -= st.token;
st.token = 0;
// Fill free space at the end of buffer with new data from file.
st.yylimit += st.file.readSliceShort(st.yyinput[st.yylimit..bufsize]) catch 0;
st.yyinput[st.yylimit] = 0; // append sentinel symbol
// If read less than expected, this is the end of input.
st.eof = st.yylimit < bufsize;
return 0;
}
fn lex(yyrecord: *State) i32 {
var count: i32 = 0;
loop: while (true) {
yyrecord.token = yyrecord.yycursor;
%{
re2c:api = record;
re2c:eof = 0;
re2c:YYFILL = "fill(yyrecord) == 0";
str = ['] ([^'\\] | [\\][^])* ['];
* { return -1; }
$ { return count; }
str { count += 1; continue :loop; }
[ ]+ { continue :loop; }
%}
}
}
test {
const fname = "input";
const content = "'qu\x00tes' 'are' 'fine: \\'' " ** bufsize;
const count = 3 * bufsize; // number of quoted strings written to file
// Prepare input file: a few times the size of the buffer, containing
// strings with zeroes and escaped quotes.
var fw = try std.fs.cwd().createFile(fname, .{});
try fw.writeAll(content);
fw.close();
// Prepare lexer state: all offsets are at the end of buffer.
// Use unbuffered reader - lexer does its own buffering.
const zerobuf: [0]u8 = undefined;
var fr = try std.fs.cwd().openFile(fname, .{.mode = .read_only});
var reader = fr.reader(&zerobuf);
var st = State{
.file = &reader.interface,
.yyinput = undefined,
.yycursor = bufsize,
.yymarker = bufsize,
.yylimit = bufsize,
.token = bufsize,
.eof = false,
};
// Sentinel at `yylimit` offset is set to zero, which triggers YYFILL.
st.yyinput[st.yylimit] = 0;
// Run the lexer.
try std.testing.expectEqual(lex(&st), count);
// Cleanup: remove input file.
fr.close();
try std.fs.cwd().deleteFile(fname);
}
YYFILL همراه با لایهگذاری (padding)
در حالت پیشفرض (زمانی که از قاعده EOF استفاده نمیشود)، YYFILL یک سازوکار پایهایِ (primitive) تابعمانند است که یک تکآرگومان را میپذیرد و هیچ مقداری را برنمیگرداند. فراخوانی YYFILL توسط شرط (YYLIMIT - YYCURSOR) < n در API اشارهگر C و YYLESSTHAN(n) در API عمومی (generic) فعال میشود. آرگومان ارسالشده به YYFILL حداقل تعداد نویسههایی است که باید فراهم شوند. اگر در انجام این کار ناموفق باشد، YYFILL نباید کنترل را به lexer بازگرداند (به همین دلیل بهتر است به صورت یک ماکرو پیادهسازی شود که در صورت شکست، از تابع فراخواننده بازمیگردد). در صورت فراخوانی موفقیتآمیز YYFILL، موقعیت حد (limit) باید یا روی یکی پس از آخرین موقعیت ورودی در بافر تنظیم شود، یا روی انتهای لایهگذاری YYMAXFILL (در حالتی که YYFILL حداقل n نویسه را با موفقیت خوانده باشد، اما این مقدار برای پر کردن کل بافر کافی نباشد). تصاویر زیر موقعیتهای نسبی نقاط ورودی در بافر را قبل و بعد از فراخوانی YYFILL نشان میدهند (لایهگذاری YYMAXFILL در تصویر دوم با نمادهای # مشخص شده است).
<-- shift --> <-- need -->
>-A------------B---------C-----D-------E---F--------G->
buffer token marker cursor limit
>-A------------B---------C-----D-------E---F--------G->
buffer, marker cursor limit
token
<-- shift --> <-- need -->
>-A------------B---------C-----D-------E-F (EOF)
buffer token marker cursor limit
>-A------------B---------C-----D-------E-F###############
buffer, marker cursor limit
token <- YYMAXFILL ->
در اینجا نمونهای از برنامهای آمده است که فایل ورودی input.txt را در تکههای ۴۰۹۶ بایتی خوانده و از بررسی محدودهها (bounds-checking) به همراه لایهگذاری استفاده میکند.
// re2zig $INPUT -o $OUTPUT
const std = @import("std");
%{max %}
const bufsize = 4096;
const State = struct {
file: *std.Io.Reader,
yyinput: [bufsize + yymaxfill]u8,
yycursor: usize,
yymarker: usize,
yylimit: usize,
token: usize,
eof: bool
};
fn fill(st: *State, need: usize) i32 {
if (st.eof) { return -1; } // unexpected EOF
// Error: lexeme too long. In real life can reallocate a larger buffer.
if (st.token < need) { return -2; }
// Shift buffer contents (discard everything up to the current token).
std.mem.copyBackwards(
u8, st.yyinput[0..st.yylimit - st.token], st.yyinput[st.token..st.yylimit]);
st.yycursor -= st.token;
st.yymarker = @subWithOverflow(st.yymarker, st.token)[0];
st.yylimit -= st.token;
st.token = 0;
// Fill free space at the end of buffer with new data from file.
st.yylimit += st.file.readSliceShort(st.yyinput[st.yylimit..bufsize]) catch 0;
// If read less than expected, this is the end of input.
if (st.yylimit < bufsize) {
st.eof = true;
@memset(st.yyinput[st.yylimit..st.yylimit + yymaxfill], 0);
st.yylimit += yymaxfill;
}
return 0;
}
fn lex(yyrecord: *State) i32 {
var count: i32 = 0;
loop: while (true) {
yyrecord.token = yyrecord.yycursor;
%{
re2c:api = record;
re2c:YYFILL = "{ if (fill(yyrecord, @@) != 0) return -2; }";
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 :loop; }
[ ]+ { continue :loop; }
* { return -1; }
%}
}
}
test {
const fname = "input";
const content = "'qu\x00tes' 'are' 'fine: \\'' " ** bufsize;
const count = 3 * bufsize; // number of quoted strings written to file
// Prepare input file: a few times the size of the buffer, containing
// strings with zeroes and escaped quotes.
var fw = try std.fs.cwd().createFile(fname, .{});
try fw.writeAll(content);
fw.close();
// Prepare lexer state: all offsets are at the end of buffer.
// This immediately triggers YYFILL, as the YYLESSTHAN condition is true.
// Use unbuffered reader - lexer does its own buffering.
const zerobuf: [0]u8 = undefined;
var fr = try std.fs.cwd().openFile(fname, .{.mode = .read_only});
var reader = fr.reader(&zerobuf);
var st = State{
.file = &reader.interface,
.yyinput = undefined,
.yycursor = bufsize,
.yymarker = bufsize,
.yylimit = bufsize,
.token = bufsize,
.eof = false,
};
@memset(st.yyinput[st.yylimit..st.yylimit + yymaxfill], 0); // zero-padding at the end
// Run the lexer.
try std.testing.expectEqual(lex(&st), count);
// Cleanup: remove input file.
fr.close();
try std.fs.cwd().deleteFile(fname);
}
ویژگیها (FEATURES)
بلوکهای چندگانه (Multiple blocks)
گاهی اوقات وجود چندین تحلیلگر لغوی (lexer) مرتبط با یکدیگر ضروری است (برای مثال، اگر یک ماشین حالت سطحبالا وجود داشته باشد که بین حالتهای مختلف تحلیلگر لغوی جابهجا میشود). این قابلیت میتواند با استفاده از چندین بلوک متصل re2zig پیادهسازی شود. گزینهٔ دیگر استفاده از شرایط شروع (start conditions) است.
پیادهسازی ارتباط بین بلوکها به زبان مقصد بستگی دارد. در زبانهایی که دارای دستور goto هستند (مانند C/C++ و Go) میتوان تمام بلوکها را در یک تابع قرار داد، به طوری که پیشوند هر کدام یک برچسب (label) باشد. انتقال از یک بلوک به بلوک دیگر یک goto ساده است. در زبانهایی که فاقد goto هستند (مانند Rust)، لازم است از یک حلقه به همراه یک switch روی متغیر حالت استفاده شود، مشابه حلقه/switch مربوط به yystate که توسط re2zig تولید میشود؛ یا این که هر بلوک درون یک تابع قرار داده شده و از فراخوانی توابع استفاده گردد.
مثال زیر از چندین بلوک برای تجزیه اعداد دودویی (binary)، هشتهشتی (octal)، دهدهی (decimal) و شانزدهشانزدهی (hexadecimal) استفاده میکند. هر مبنا بلوک مخصوص به خود را دارد. بلوک اولیه مبنا را تعیین کرده و به بلوکهای دیگر هدایت میکند. پیکربندیهای مشترک در یک بلوک جداگانه در ابتدای برنامه تعریف شدهاند؛ این پیکربندیها توسط بلوکهای دیگر به ارث برده میشوند.
// re2zig $INPUT -o $OUTPUT
const std = @import("std");
// Store u32 number in u64 during parsing to simplify overflow handling.
const State = struct {
yyinput: [:0]const u8,
yycursor: usize,
yymarker: usize,
num: u64,
};
// Common re2c definitions shared between all functions.
%{
re2c:api = record;
re2c:yyrecord = st;
re2c:yyfill:enable = 0;
%}
const ERROR: u64 = @as(u64, std.math.maxInt(u32)) + 1; // overflow
// Add digit with the given base, checking for overflow.
fn add(st: *State, offs: u8, base: u64) void {
const digit = st.yyinput[st.yycursor - 1] - offs;
st.num = @min(st.num * base + digit, ERROR);
}
// Convert u64 to optional u32 (null meaning overflow or parse error).
fn maybeU32(num: u64) ?u32 {
return if (num < ERROR) @intCast(num) else null;
}
fn parse_u32(s: [:0]const u8) ?u32 {
var st = State {.yyinput = s, .yycursor = 0, .yymarker = 0, .num = 0};
%{
'0b' / [01] { return parse_bin(&st); }
"0" { return parse_oct(&st); }
"" / [1-9] { return parse_dec(&st); }
'0x' / [0-9a-fA-F] { return parse_hex(&st); }
* { return null; }
%}
}
fn parse_bin(st: *State) ?u32 {
bin: while (true) {%{
[01] { add(st, 48, 2); continue :bin; }
* { return maybeU32(st.num); }
%}}
}
fn parse_oct(st: *State) ?u32 {
oct: while (true) {%{
[0-7] { add(st, 48, 8); continue :oct; }
* { return maybeU32(st.num); }
%}}
}
fn parse_dec(st: *State) ?u32 {
dec: while (true) {%{
[0-9] { add(st, 48, 10); continue :dec; }
* { return maybeU32(st.num); }
%}}
}
fn parse_hex(st: *State) ?u32 {
hex: while (true) {%{
[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 maybeU32(st.num); }
%}}
}
test {
try std.testing.expectEqual(parse_u32(""), null);
try std.testing.expectEqual(parse_u32("1234567890"), 1234567890);
try std.testing.expectEqual(parse_u32("0b1101"), 13);
try std.testing.expectEqual(parse_u32("0x7Fe"), 2046);
try std.testing.expectEqual(parse_u32("0644"), 420);
try std.testing.expectEqual(parse_u32("9999999999"), null);
}
شرایط شروع (Start conditions)
شرایط شروع با گزینهٔ --start-conditions فعال میشوند. آنها روشی برای کدگذاری چندین اتوماتای مرتبط با یکدیگر در یک بلوک re2zig یکسان ارائه میدهند.
هر شرط متناظر با یک اتوماتای منفرد است و یک نام یکتا دارد که توسط کاربر مشخص میشود و یک شمارهٔ داخلی یکتا که توسط re2zig تعریف میگردد. این شمارهها برای جابهجایی میان شرایط استفاده میشوند: کد تولیدشده از سازوکارهای اولیهٔ YYGETCOND و YYSETCOND برای دریافت شرط کنونی یا تنظیم آن روی شمارهٔ دادهشده استفاده میکند. برای تولید شناسههای عددی شرایط، از بلوک conditions، گزینهٔ --header یا پیکربندی re2c:header استفاده کنید. پیکربندی re2c:cond:enumprefix پیشوند شناسههای تولیدشده را مشخص میکند.
در حالت شرط، هر قاعده باید پیشوندی از فهرستی از نامهای شرایط جداشده با ویرگول در علامتهای قلابدار (< >)، یا نویسهٔ عمومی <*> برای نشان دادن همهٔ شرایط داشته باشد. ساختار دستوری قواعد به شرح زیر گسترش مییابد:
- < condition-list > regular-expression code
- قاعدهای که در هر شرط موجود در condition-list ادغام میشود. این قاعده با regular-expression مطابقت پیدا کرده و code مرتبط را اجرا میکند.
- < condition-list > regular-expression => condition code
- قاعدهای که در هر شرط موجود در condition-list ادغام میشود. این قاعده با regular-expression مطابقت پیدا کرده، شرط کنونی را به condition تغییر میدهد و code مرتبط را اجرا میکند.
- < condition-list > regular-expression :=> condition
- قاعدهای که در هر شرط موجود در condition-list ادغام میشود. این قاعده با regular-expression مطابقت پیدا کرده و فوراً به condition منتقل میشود (هیچ عمل معنایی وجود ندارد).
- < condition-list > !action code
- قاعدهای که code را به مکان تعریفشده توسط action در هر شرطِ موجود در condition-list متصل میکند (برای انواع مختلف کنشها، بخش actions را ببینید).
- <! condition-list > code
- قاعدهای که code را به ابتدای کنشهای معنایی تمامی قواعد برای هر شرط موجود در condition-list اضافه میکند. این ساختار نحوی منسوخ شده است و در عوض باید از کنش !pre_rule استفاده شود (دقیقاً همان کار را انجام میدهد).
- < > code
- قاعدهای که یک شرط ورودی ویژه با شمارهٔ صفر و نام "0" ایجاد میکند که پیش از پرش به شرایط دیگر، code را اجرا مینماید. این نحو منسوخ شده است و در عوض باید از کنش !entry استفاده شود (کنترل دقیقتری فراهم میکند، زیرا کد میتواند به ازای هر شرط مشخص شود و میتوان مستقیماً بدون گذر از توزیع شرط، به ابتدای شرط پرش کرد).
- < > => condition code
- مشابه قاعدهٔ قبلی، با این تفاوت که condition بعدی را تنظیم میکند.
- < > :=> condition
- مشابه قاعدهٔ قبلی، با این تفاوت که کد مرتبطی ندارد و فوراً به condition پرش میکند.
کدی که re2zig برای شرایط تولید میکند بستگی به این دارد که re2zig از رویکرد goto/label استفاده میکند یا رویکرد loop/switch برای کدگذاری اتوماتا.
در زبانهایی که دارای دستور goto هستند (مانند C/C++ و Go)، شرایط بهطور طبیعی به صورت بلوکهایی از کد با پیشوند برچسبهایی به شکل yyc_<cond> پیادهسازی میشوند، که در آن cond نام شرط است (پیشوند برچسب را میتوان با re2c:cond:prefix تغییر داد). جابهجاییها میان شرایط با استفاده از goto و برچسبهای شرط پیادهسازی میشوند. پیش از تمام شرایط، re2zig یک switch اولیه روی YYGETSTATE تولید میکند که به وضعیت شروع شرط کنونی پرش میکند. قواعد میانبر :=> از switch اولیه عبور کرده و مستقیماً به شرط مشخصشده پرش میکنند (میتوان از re2c:cond:goto برای تغییر رفتار پیشفرض استفاده کرد). قواعد دارای کنشهای معنایی بهطور خودکار به شرط بعدی پرش نمیکنند؛ این کار باید توسط کد کنش تعریفشده توسط کاربر انجام شود.
در زبانهایی که دستور goto را ندارند (مانند Rust)، ابزار re2zig از متغیر yystate برای ذخیره شمارههای شرایط استفاده مجدد میکند. هر شرط یک شناسهٔ عددی برابر با شمارهٔ وضعیت شروع خود دریافت میکند، و جابهجایی بین شرایط هیچ تفاوتی با switch میان وضعیتهای DFA در یک شرط واحد ندارد. نیازی به یک switch اولیهٔ جداگانه برای شرایط وجود ندارد. (از آنجا که رویکرد یکسانی برای پیادهسازی وضعیتهای قابل ذخیره استفاده میشود، در صورتی که هر دو قابلیت وضعیتهای قابل ذخیره و شرایط استفاده شوند، YYGETCOND/YYSETCOND زائد خواهند بود).
برنامهٔ زیر از شرایط شروع برای تجزیه اعداد دودویی، هشتهشتی، دهدهی و شانزدهشانزدهی استفاده میکند. یک بلوک منفرد وجود دارد که در آن هر مبنا شرط مخصوص به خود را دارد و شرط اولیه به همهٔ آنها متصل است. متغیر تعریفشده توسط کاربر با نام cond شمارهٔ شرط کنونی را ذخیره میکند؛ این متغیر با شمارهٔ شرط اولیه که توسط بلوک conditions تولید شده است، مقداردهی اولیه میشود.
// re2zig $INPUT -o $OUTPUT -c
const std = @import("std");
%{conditions %}
const ERROR: u64 = @as(u64, std.math.maxInt(u32)) + 1; // overflow
// Add digit with the given base, checking for overflow.
fn add(num: *u64, str: [:0]const u8, cur: usize, offs: u8, base: u64) void {
num.* = @min(num.* * base + (str[cur - 1] - offs), ERROR);
}
fn parse_u32(yyinput: [:0]const u8) ?u32 {
var yycursor: usize = 0;
var yymarker: usize = 0;
var yycond = yycinit;
var num: u64 = 0; // Store number in u64 to simplify overflow checks.
loop: while(true) {
%{
re2c:yyfill:enable = 0;
<init> '0b' / [01] :=> bin
<init> "0" :=> oct
<init> "" / [1-9] :=> dec
<init> '0x' / [0-9a-fA-F] :=> hex
<init> * { return null; }
<bin> [01] { add(&num, yyinput, yycursor, 48, 2); continue :loop; }
<oct> [0-7] { add(&num, yyinput, yycursor, 48, 8); continue :loop; }
<dec> [0-9] { add(&num, yyinput, yycursor, 48, 10); continue :loop; }
<hex> [0-9] { add(&num, yyinput, yycursor, 48, 16); continue :loop; }
<hex> [a-f] { add(&num, yyinput, yycursor, 87, 16); continue :loop; }
<hex> [A-F] { add(&num, yyinput, yycursor, 55, 16); continue :loop; }
<bin, oct, dec, hex> * {
return if (num < ERROR) @intCast(num) else null;
}
%}}
}
test {
try std.testing.expectEqual(parse_u32(""), null);
try std.testing.expectEqual(parse_u32("1234567890"), 1234567890);
try std.testing.expectEqual(parse_u32("0b1101"), 13);
try std.testing.expectEqual(parse_u32("0x7Fe"), 2046);
try std.testing.expectEqual(parse_u32("0644"), 420);
try std.testing.expectEqual(parse_u32("9999999999"), null);
}
وضعیت ذخیرهپذیر (Storable state)
با گزینهٔ --storable-state، ابزار re2zig تحلیلگر لغویای تولید میکند که میتواند وضعیت فعلی خود را ذخیره کند، به فراخواننده بازگردد، و بعداً عملیات را دقیقاً از همانجایی که متوقف شده بود از سر بگیرد. حالت پیشفرض عملکرد در re2zig یک مدل «کششی» ("pull") است که در آن تحلیلگر لغوی هر زمان که نیاز داشته باشد، ورودی بیشتری را «میکشد» (دریافت میکند). این امر ممکن است در مواردی که ورودی بهصورت تکهتکه در دسترس قرار میگیرد غیرقابل قبول باشد (برای مثال، اگر تحلیلگر لغوی توسط تجزیهکننده (parser) فراخوانی شود، یا اگر برنامهٔ تحلیلگر لغوی از طریق پروتکل سوکت با برنامهٔ دیگری ارتباط برقرار کند که باید پیش از ارسال پیام بعدی، منتظر پاسخ از سوی تحلیلگر لغوی بماند). ویژگی وضعیت ذخیرهپذیر دقیقاً برای چنین مواردی در نظر گرفته شده است: این ویژگی امکان تولید تحلیلگرهای لغوی را فراهم میکند که در یک مدل «رانشی» ("push") کار میکنند. هنگامی که تحلیلگر لغوی به ورودی بیشتری نیاز دارد، وضعیت خود را ذخیره کرده و به فراخواننده بازمیگردد. بعداً، زمانی که ورودی بیشتری در دسترس قرار گرفت، فراخواننده تحلیلگر لغوی را دقیقاً از جایی که متوقف شده بود از سر میگیرد. در مقایسه با مدل «کششی»، چند تغییر ضروری است:
- اولیههای YYSETSTATE() و YYGETSTATE(state) را تعریف کنید.
- متغیرهای yych، yyaccept (در صورت استفاده) و state را بهعنوان بخشی از وضعیت ماندگار (persistent) تحلیلگر لغوی تعریف کنید. متغیر state باید با -1 مقداردهی اولیه شود.
- ماکروی YYFILL باید به جای تلاش برای تأمین ورودی بیشتر، به برنامهٔ بیرونی بازگردد. کد بازگشتی باید نشان دهد که تحلیلگر لغوی به ورودی بیشتری نیاز دارد.
- برنامهٔ بیرونی باید موقعیتهایی را که تحلیلگر لغوی به ورودی بیشتری نیاز دارد تشخیص داده و واکنش مناسب نشان دهد.
- بهصورت اختیاری از بلوک getstate برای تولید سوییچ YYGETSTATE جدا از تحلیلگر لغوی اصلی استفاده کنید. این کار فقط برای زبانهایی که دارای goto هستند کار میکند (نه در حالت --loop-switch).
- از re2c:eof و روش sentinel with bounds checks (نگهبان به همراه بررسی کرانها) برای مدیریت پایان ورودی استفاده کنید. روش مبتنی بر فاصلهگذاری (padding) ممکن است کار نکند زیرا مشخص نیست چه زمانی باید فاصلهگذاری را اضافه کرد: انتهای فعلی ورودی ممکن است انتهای نهایی ورودی نباشد، و اضافه کردن زودهنگام فاصلهگذاری ممکن است یک لکسم (lexeme) حریصانهٔ نیمهخواندهشده را قطع کند. علاوه بر این، به دلیل منطق سطح بالای برنامه، دریافت ورودی بیشتر ممکن است به پردازش لکسم انتهای بافر وابسته باشد (که از پیش به دلیل شرط پایان ورودی مسدود شده است).
در اینجا مثالی از یک تحلیلگر لغوی مدل «رانشی» آورده شده است که خواندن بستهها از یک سوکت را شبیهسازی میکند. تحلیلگر لغوی تا زمانی که با پایان ورودی مواجه شود حلقه میزند و به تابع فراخواننده بازمیگردد. تابع فراخواننده با «ارسال» بستهٔ بعدی، ورودی بیشتری فراهم کرده و تحلیل لغوی را از سر میگیرد. این فرایند زمانی که تمام بستهها ارسال شوند، یا خطایی رخ دهد، متوقف میشود.
// re2zig $INPUT -o $OUTPUT -f
const std = @import("std");
const Status = enum {
end,
ready,
waiting,
bad_packet,
big_packet
};
// Use a small buffer to cover the case when a lexeme doesn't fit.
// In real world use a larger buffer.
const bufsize = 10;
const State = struct {
file: std.fs.File.Reader,
yyinput: [bufsize + 1]u8,
yycursor: usize,
yymarker: usize,
yylimit: usize,
token: usize,
yystate: i32,
received: usize,
};
fn fill(st: *State) Status {
// Error: lexeme too long. In real life can reallocate a larger buffer.
if (st.token < 1) { return Status.big_packet; }
// Shift buffer contents (discard everything up to the current token).
std.mem.copyBackwards(
u8, st.yyinput[0..st.yylimit - st.token], st.yyinput[st.token..st.yylimit]);
st.yycursor -= st.token;
st.yymarker = @subWithOverflow(st.yymarker, st.token)[0];
st.yylimit -= st.token;
st.token = 0;
// Fill free space at the end of buffer with new data from file.
st.yylimit += st.file.interface.readSliceShort(st.yyinput[st.yylimit..bufsize]) catch 0;
st.yyinput[st.yylimit] = 0; // append sentinel symbol
return Status.ready;
}
fn lex(yyrecord: *State) Status {
var yych: u8 = 0;
loop: while (true) {
yyrecord.token = yyrecord.yycursor;
%{
re2c:api = record;
re2c:eof = 0;
re2c:YYFILL = "return Status.waiting;";
packet = [a-z]+[;];
* { return Status.bad_packet; }
$ { return Status.end; }
packet { yyrecord.received += 1; continue :loop; }
%}
}
}
fn run(expect: Status, packets: []const []const u8) !void {
// Create a "pipe" (open the same file for reading and writing).
const fname = "input";
var fw = try std.fs.cwd().createFile(fname, .{});
var fr = try std.fs.cwd().openFile(fname, .{ .mode = .read_only});
// Initialize lexer state: `state` value is -1, all offsets are at the end
// of buffer. Use unbuffered reader - lexer does its own buffering.
const zerobuf: [0]u8 = undefined;
var st = State{
.file = fr.reader(&zerobuf),
.yyinput = undefined,
.yycursor = bufsize,
.yymarker = bufsize,
.yylimit = bufsize,
.token = bufsize,
.yystate = -1,
.received = 0,
};
// Sentinel at `yylimit` offset is set to zero, which triggers YYFILL.
st.yyinput[st.yylimit] = 0;
// Main loop. The buffer contains incomplete data which appears packet by
// packet. When the lexer needs more input it saves its internal state and
// returns to the caller which should provide more input and resume lexing.
var status = Status.ready;
var send: usize = 0;
while (true) {
status = lex(&st);
if (status == Status.end) {
break;
} else if (status == Status.waiting) {
if (send < packets.len) {
std.log.debug("sending packet {}", .{send});
try fw.writeAll(packets[send]);
send += 1;
}
status = fill(&st);
std.log.debug("filled buffer [{s}], status {}", .{st.yyinput, status});
if (status != Status.ready) {
break;
}
} else if (status == Status.bad_packet) {
break;
}
}
// Check results.
try std.testing.expectEqual(status, expect);
if (status == Status.end) { try std.testing.expectEqual(st.received, send); }
// Cleanup: remove input file.
fw.close();
fr.close();
try std.fs.cwd().deleteFile(fname);
}
test {
try run(Status.end, &[_][]const u8{});
try run(Status.end, &[_][]const u8{"zero;", "one;", "two;", "three;", "four;"});
try run(Status.bad_packet, &[_][]const u8{"??;"});
try run(Status.big_packet, &[_][]const u8{"looooooooooooong;"});
}
بلوکهای قابل استفاده مجدد (Reusable blocks)
بلوکهای قابل استفاده مجدد با ساختار /*!rules:re2c[:<name>] ... */ یا %{rules[:<name>] ... %} میتوانند به هر تعداد بار مجدداً استفاده شوند و با سایر بلوکهای re2zig ترکیب گردند. بخش <name> اختیاری است. یک بلوک قوانین (rules block) میتواند در یک بلوک یا دستور use به کار گرفته شود. کد مربوط به یک بلوک قوانین، در هر نقطهٔ استفاده تولید میشود.
بلوکهای use با /*!use:re2c[:<name>] ... */ یا %{use[:<name>] ... %} تعریف میشوند. شناسهٔ <name> اختیاری است: اگر مشخص نشده باشد، بلوک قوانین مرتبط، نزدیکترین بلوک قبلی خواهد بود (خواه نامگذاریشده یا بدون نام). یک بلوک use میتواند تعاریف نامگذاریشده، پیکربندیها و قوانین اختصاصی خود را اضافه کند. یک کاربرد مهم برای بلوکهای use، تحلیلگر لغوی (lexer) با پشتیبانی از چندین کدگذاری ورودی است: همان بلوک قوانین چندین بار با پیکربندیهای مختص هر کدگذاری مجدداً استفاده میشود (مثال زیر را ببینید).
دستور درونبلوکیِ !use:<name>; میتواند از داخل یک بلوک re2zig استفاده شود. این دستور، بلوک ارجاعدادهشدهٔ <name> را با بلوک فعلی ادغام میکند. اگر برخی از قوانین و پیکربندیهای ادغامشده با موارد تعریفشدهٔ قبلی همپوشانی داشته باشند، تداخلها به روش معمول حل میشوند: نخستین قاعده اولویت دارد، و آخرین پیکربندی بر موارد پیشین ارجحیت مییابد (آنها را رونویسی میکند). یک استثنا قوانین ویژهٔ *، $ و (در حالت شرطی) <!> هستند که برای آنها تعریف محلیِ بلوک بر هرگونه تعریف به ارثرسیده برتری دارد. دستور use امکان ترکیب بلوکهای مختلف re2zig را با یکدیگر در یک بلوک واحد فراهم میسازد (مثال زیر را ببینید).
بلوکهای نامگذاریشده و دستور درونبلوکی use در نگارش 2.2 از re2zig اضافه شدند. از آن نگارش به بعد، استفاده از بلوکهای قابل استفاده مجدد بهطور پیشفرض مجاز است (نیازی به گزینهٔ خاصی نیست). پیش از نگارش 2.2، حالت استفادهٔ مجدد با گزینهٔ -r --reusable فعال میشد. پیش از نگارش 1.2، بلوکهای قابل استفاده مجدد نمیتوانستند با بلوکهای معمولی ترکیب شوند.
مثالی از دستور !use
// re2zig $INPUT -o $OUTPUT
// This example shows how to combine reusable re2c blocks: two blocks
// ('colors' and 'fish') are merged into one. The 'salmon' rule occurs
// in both blocks; the 'fish' block takes priority because it is used
// earlier. Default rule * occurs in all three blocks; the local (not
// inherited) definition takes priority.
const std = @import("std");
const Ans = enum {color, fish, dunno};
%{rules:colors
* { @panic("ah"); }
"red" | "salmon" | "magenta" { return Ans.color; }
%}
%{rules:fish
* { @panic("oh"); }
"haddock" | "salmon" | "eel" { return Ans.fish; }
%}
fn lex(yyinput: [:0]const u8) Ans {
var yycursor: usize = 0;
var yymarker: usize = 0;
%{
re2c:yyfill:enable = 0;
!use:fish;
!use:colors;
* { return Ans.dunno; } // overrides inherited '*' rules
%}
}
test {
try std.testing.expectEqual(lex("salmon"), Ans.fish);
try std.testing.expectEqual(lex("what?"), Ans.dunno);
}
مثالی از بلوک /*!use:re2c ... */
// re2zig $INPUT -o $OUTPUT --input-encoding utf8
// This example supports multiple input encodings: UTF-8 and UTF-32.
// Both lexers are generated from the same rules block, and the use
// blocks add only encoding-specific configurations.
const std = @import("std");
%{rules
re2c:yyfill:enable = 0;
"∀x ∃y" { return yycursor; }
* { return null; }
%}
fn lex_utf8(yyinput: []const u8) ?usize {
var yycursor: usize = 0;
var yymarker: usize = 0;
%{use
re2c:encoding:utf8 = 1;
re2c:YYCTYPE = u8; // the default
%}
}
fn lex_utf32(yyinput: []const u32) ?usize {
var yycursor: usize = 0;
var yymarker: usize = 0;
%{use
re2c:encoding:utf32 = 1;
re2c:YYCTYPE = u32;
%}
}
test {
const s8 = [_]u8{0xe2, 0x88, 0x80, 0x78, 0x20, 0xe2, 0x88, 0x83, 0x79};
try std.testing.expectEqual(lex_utf8(&s8), s8.len);
const s32 = [_]u32{0x2200, 0x78, 0x20, 0x2203, 0x79};
try std.testing.expectEqual(lex_utf32(&s32), s32.len);
}
استخراج زیرتطابق (Submatch extraction)
ابزار re2zig دو گزینه برای استخراج زیرتطابق (submatch extraction) دارد.
- Tags
- گزینه نخست استفاده از برچسبها (tags) مستقل به شکل @stag یا #mtag است، که در آن stag و mtag نامهای دلخواه تعریفشده توسط کاربر هستند. برچسبها با گزینه -T --tags یا پیکربندی re2c:tags = 1 فعال میشوند. از نظر معنایی، برچسبها نشانگرهای موقعیت هستند: میتوان آنها را در هر کجای یک عبارت باقاعده درج کرد، و به موقعیت متناظر (یا موقعیتهای متعدد) در رشته ورودی متصل میشوند. S-tagها به آخرین موقعیت تطبیقیافته متصل میشوند، و m-tagها به فهرستی از موقعیتها متصل میشوند (میتوان از آنها در زیرعبارتهای تکرار استفاده کرد، جایی که یک موقعیت واحد در عبارت باقاعده با چندین موقعیت در رشته ورودی متناظر است). تمام برچسبها باید توسط کاربر، بهصورت دستی یا با کمک بلوکهای svars و mvars تعریف شوند. اگر بیش از یک راه برای تطبیق برچسبها با ورودی وجود داشته باشد، ابهام با استفاده از راهبرد رفع ابهام حریصانهترین از چپ (leftmost greedy) حل میشود.
- Captures
- گزینه دوم استفاده از گروههای تسخیر (capturing groups) است. آنها با گزینه --captures یا پیکربندی re2c:captures = 1 فعال میشوند. دو حالت برای سیاستهای مختلف رفع ابهام وجود دارد؛ --leftmost-captures (پیشفرض) برای سیاست حریصانهترین از چپ، و --posix-captures برای سیاست طولانیترین تطابق بر پایه POSIX (POSIX longest-match) است. در این حالت تمام زیرعبارتهای درون پرانتز، گروههای تسخیر در نظر گرفته میشوند و میتوان از علامت تعجب برای مشخص کردن گروههای غیرتسخیری استفاده کرد: (! ... ). با گزینه --invert-captures یا پیکربندی re2c:invert-captures = 1، معنای علامت تعجب معکوس میشود. تعداد گروهها برای قاعده تطبیقیافته در یک متغیر yynmatch ذخیره میشود (کل عبارت باقاعده، گروه شماره صفر است)، و نتایج زیرتطابق در آرایه yypmatch ذخیره میشوند. هر دو متغیر yynmatch و yypmatch باید توسط کاربر تعریف شوند، و اندازه yypmatch باید حداقل [yynmatch * 2] باشد. از بلوک maxnmatch برای تعریف YYMAXNMATCH استفاده کنید؛ ثابتی که برابر با بیشترین مقدار yynmatch در میان تمام قواعد است.
- Captvars
- روش دیگر برای استفاده از گروههای تسخیر، گزینه --captvars یا پیکربندی re2c:captvars = 1 است. تنها تفاوت با --captures در نحوه ذخیره نتایج زیرتطابق توسط کد تولیدشده است: به جای yynmatch و yypmatch، ابزار re2zig متغیرهای yytl<k> و yytr<k> را برای k-اُمین گروه تسخیر تولید میکند (کاربر باید این متغیرها را با استفاده از یک بلوک svars اعلان کند). تسخیر با متغیرها از دو سیاست رفع ابهام پشتیبانی میکند: --leftmost-captvars یا re2c:leftmost-captvars = 1 برای سیاست حریصانهترین از چپ (پیشفرض) و --posix-captvars یا re2c:posix-captvars برای سیاست طولانیترین تطابق بر پایه POSIX.
در پشت صحنه، تمام این گزینهها به برچسبها و Tagged Deterministic Finite Automata with Lookahead (اتوماتونهای متناهی معین برچسبدار با نگاه به جلو یا TDFA) ترجمه میشوند. ایده بنیادین TDFA به حداقل رساندن سربار ناشی از استخراج زیرتطابق است. در حالت نهایی، اگر هیچ برچسب یا تسخیری در یک عبارت باقاعده وجود نداشته باشد، TDFA دقیقاً همانند یک DFA معمولی خواهد بود. اگر تعداد برچسبها متعادل باشد، سربار به سختی قابل توجه است. اتوماتون TDFA تولیدشده از تعدادی متغیرهای برچسب (tag variables) استفاده میکند که مستقیماً به برچسبها نگاشت نمیشوند: ممکن است یک متغیر منفرد برای برچسبهای متفاوتی استفاده شود، و یک برچسب ممکن است برای نگهداری تمام مقادیر احتمالی خود به چندین متغیر نیاز داشته باشد. سرانجام ابهام رفع میشود، و تنها یک متغیر نهایی به ازای هر برچسب باقی میماند. متغیرهای برچسب باید با استفاده از بلوکهای stags یا mtags تعریف شوند. اگر وضعیت تحلیلگر لغوی (lexer state) ذخیره شود، متغیرهای برچسب نیز باید بخشی از آن باشند. آنها همچنین باید توسط YYFILL بهروزرسانی شوند.
برچسبهای S-tag از عملیاتهای زیر پشتیبانی میکنند:
- ذخیره موقعیت ورودی در یک s-tag: عبارت t = YYCURSOR با API اشارهگر C یا عملیات تعریفشده توسط کاربر YYSTAGP(t) با API عمومی (generic API)
- ذخیره مقدار پیشفرض در یک s-tag: عبارت t = NULL با API اشارهگر C یا عملیات تعریفشده توسط کاربر YYSTAGN(t) با API عمومی
- کپی کردن یک s-tag در دیگری: t1 = t2
برچسبهای M-tag از عملیاتهای زیر پشتیبانی میکنند:
- الحاق موقعیت ورودی به یک m-tag: عملیات تعریفشده توسط کاربر YYMTAGP(t) با هر دو API پیشفرض و عمومی
- الحاق مقدار پیشفرض به یک m-tag: عملیات تعریفشده توسط کاربر YYMTAGN(t) با هر دو API پیشفرض و عمومی
- کپی کردن یک m-tag در دیگری: t1 = t2
برچسبهای S-tag را میتوان بهصورت مقادیر اسکالر (اشارهگرها یا آفستها) پیادهسازی کرد. برچسبهای M-tag به نمایشی پیچیدهتر نیاز دارند، چرا که باید دنبالهای از مقادیر برچسب را ذخیره کنند. سادهلوحانهترین و ناکارآمدترین بازنمایی از یک m-tag، یک لیست (آرایه، بردار) از مقادیر برچسب است؛ نمایشی کارآمدتر، ذخیره تمام m-tagها در یک درخت پیشوندی (prefix-tree) است که بهصورت آرایهای از گرههای (v, p) نمایش داده میشود، که در آن v مقدار برچسب و p اشارهگری به گره والد است.
در ادامه یک مثال ساده از بهکارگیری s-tagها برای تجزیه نگارشهای معنایی (semantic versions) متشکل از سه مؤلفه عددی آورده شده است: major، minor، patch (مورد آخر اختیاری است). برای مشاهده مثالی پیچیدهتر که از YYFILL استفاده میکند، بخشهای زیر را ببینید.
// re2zig $INPUT -o $OUTPUT
const std = @import("std");
const SemVer = struct {
major: u32,
minor: u32,
patch: u32,
};
const none = std.math.maxInt(usize);
fn s2n(str: []const u8) u32 { // convert a pre-parsed string to a number
var n: u32 = 0;
for (str) |c| { n = n * 10 + (c - 48); }
return n;
}
fn parse(yyinput: [:0]const u8) ?SemVer {
var yycursor: usize = 0;
var yymarker: usize = 0;
// Final tag variables available in semantic action.
%{svars format = "var @@: usize = none;"; %}
// Intermediate tag variables used by the lexer (must be autogenerated).
%{stags format = "var @@: usize = none;"; %}
%{
re2c:yyfill:enable = 0;
re2c:tags = 1;
num = [0-9]+;
@t1 num @t2 "." @t3 num @t4 ("." @t5 num)? [\x00] {
return SemVer {
.major = s2n(yyinput[t1..t2]),
.minor = s2n(yyinput[t3..t4]),
.patch = if (t5 == none) 0 else s2n(yyinput[t5..yycursor - 1]),
};
}
* { return null; }
%}
}
test {
try std.testing.expectEqual(parse("23.34"), SemVer{.major = 23, .minor = 34, .patch = 0});
try std.testing.expectEqual(parse("1.2.99999"), SemVer{.major = 1, .minor = 2, .patch = 99999});
try std.testing.expectEqual(parse("1.a"), null);
}
در اینجا مثالی پیچیدهتر از استفاده از s-tagها با YYFILL برای تجزیه فایلی با نگارشهای معنایی جداشده با خط جدید آمده است. متغیرهای برچسب بخشی از وضعیت تحلیلگر لغوی هستند، و همانند دیگر موقعیتهای ورودی در YYFILL تنظیم میشوند. توجه داشته باشید که این امر برای s-tagها ضروری است زیرا مقادیر آنها پس از تغییر مکان محتویات بافر باطل میشوند. این کار ممکن است در یک پیادهسازی سفارشی که در آن متغیرهای برچسب به جای بافر، آفستهای نسبت به ابتدای رشته ورودی را ذخیره میکنند ضروری نباشد، که میتواند در مورد m-tagها صادق باشد.
// re2zig $INPUT -o $OUTPUT
const std = @import("std");
const bufsize = 4095;
const none = std.math.maxInt(usize);
const err = error.SyntaxError;
const SemVer = struct {
major: u32,
minor: u32,
patch: u32,
};
fn s2n(str: []const u8) u32 { // convert a pre-parsed string to a number
var n: u32 = 0;
for (str) |c| { n = n * 10 + (c - 48); }
return n;
}
const State = struct {
file: *std.Io.Reader,
yyinput: [bufsize + 1]u8,
yycursor: usize,
yymarker: usize,
yylimit: 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 format = "@@: usize,\n"; %}
eof: bool
};
fn fill(st: *State) i32 {
if (st.eof) { return -1; } // unexpected EOF
// Error: lexeme too long. In real life can reallocate a larger buffer.
if (st.token < 1) { return -2; }
// Shift buffer contents (discard everything up to the current token).
std.mem.copyBackwards(
u8, st.yyinput[0..st.yylimit - st.token], st.yyinput[st.token..st.yylimit]);
st.yycursor -= st.token;
st.yymarker = @subWithOverflow(st.yymarker, st.token)[0];
st.yylimit -= 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 format = "if (st.@@ != none) st.@@ = @subWithOverflow(st.@@, st.token)[0];\n"; %}
st.token = 0;
// Fill free space at the end of buffer with new data from file.
st.yylimit += st.file.readSliceShort(st.yyinput[st.yylimit..bufsize]) catch 0;
st.yyinput[st.yylimit] = 0; // append sentinel symbol
// If read less than expected, this is the end of input.
st.eof = st.yylimit < bufsize;
return 0;
}
fn parse(st: *State) !std.ArrayList(SemVer) {
var vers = try std.ArrayList(SemVer).initCapacity(std.testing.allocator, 0);
// Final tag variables available in semantic action.
%{svars format = "var @@: usize = 0;\n"; %}
loop: while (true) {
st.token = st.yycursor;
%{
re2c:api = record;
re2c:eof = 0;
re2c:tags = 1;
re2c:yyrecord = st;
re2c:YYFILL = "fill(st) == 0";
num = [0-9]+;
num @t1 "." @t2 num @t3 ("." @t4 num)? [\n] {
try vers.append(std.testing.allocator, SemVer {
.major = s2n(st.yyinput[st.token..t1]),
.minor = s2n(st.yyinput[t2..t3]),
.patch = if (t4 == none) 0 else s2n(st.yyinput[t4..st.yycursor - 1]),
});
continue :loop;
}
$ { return vers; }
* { return error.SyntaxError; }
%}
}
}
test {
const fname = "input";
const content = "1.22.333\n" ** bufsize;
// Prepare input file: a few times the size of the buffer, containing
// strings with zeroes and escaped quotes.
var fw = try std.fs.cwd().createFile(fname, .{});
try fw.writeAll(content);
fw.close();
// Prepare lexer state: all offsets are at the end of buffer.
// Use unbuffered reader - lexer does its own buffering.
const zerobuf: [0]u8 = undefined;
var fr = try std.fs.cwd().openFile(fname, .{.mode = .read_only});
var reader = fr.reader(&zerobuf);
var st = State{
.file = &reader.interface,
.yyinput = undefined,
.yycursor = bufsize,
.yymarker = bufsize,
.yylimit = bufsize,
.token = bufsize,
%{stags format = ".@@ = none,\n"; %}
.eof = false,
};
// Sentinel at `yylimit` offset is set to zero, which triggers YYFILL.
st.yyinput[st.yylimit] = 0;
// Manually construct expected result.
var expect = [_]SemVer{SemVer{.major = 1, .minor = 22, .patch = 333}} ** bufsize;
// Run the lexer.
var result = try parse(&st);
try std.testing.expectEqualDeep(&expect, result.items);
// Cleanup: free memory and remove input file.
result.deinit(std.testing.allocator);
fr.close();
try std.fs.cwd().deleteFile(fname);
}
در اینجا مثالی از استفاده از گروههای تسخیر (capturing groups) برای تجزیه نگارشهای معنایی آورده شده است.
// re2zig $INPUT -o $OUTPUT
const std = @import("std");
const none = std.math.maxInt(usize);
const SemVer = struct {
major: u32,
minor: u32,
patch: u32,
};
fn s2n(str: []const u8) u32 { // convert pre-parsed string to a number
var n: u32 = 0;
for (str) |c| { n = n * 10 + (c - 48); }
return n;
}
fn parse(yyinput: [:0]const u8) ?SemVer {
var yycursor: usize = 0;
var yymarker: usize = 0;
// Final tag variables available in semantic action.
%{svars format = "var @@: usize = none;"; %}
// Intermediate tag variables used by the lexer (must be autogenerated).
%{stags format = "var @@: usize = none;"; %}
%{
re2c:yyfill:enable = 0;
re2c:captvars = 1;
num = [0-9]+;
(num) "." (num) ("." num)? [\x00] {
return SemVer {
.major = s2n(yyinput[yytl1..yytr1]),
.minor = s2n(yyinput[yytl2..yytr2]),
.patch = if (yytl3 == none) 0 else s2n(yyinput[yytl3 + 1..yytr3])
};
}
* { return null; }
%}
}
test {
try std.testing.expectEqual(parse("23.34"), SemVer{.major = 23, .minor = 34, .patch = 0});
try std.testing.expectEqual(parse("1.2.99999"), SemVer{.major = 1, .minor = 2, .patch = 99999});
try std.testing.expectEqual(parse("1.a"), null);
}
در اینجا مثالی از بهکارگیری m-tagها برای تجزیه یک نگارش با تعداد متغیری از مؤلفهها آورده شده است. متغیرهای برچسب در یک ترای (trie) ذخیره میشوند.
// re2zig $INPUT -o $OUTPUT
const std = @import("std");
const none = std.math.maxInt(usize);
const mtag_root = none - 1;
const err = error.SyntaxError;
// 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).
const MtagElem = struct {
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: *std.ArrayList(MtagElem), mtag: usize, value: usize) !usize {
try trie.append(std.testing.allocator, MtagElem{.elem = value, .pred = mtag});
return trie.items.len - 1;
}
// Recursively unwind tag histories and collect version components.
fn unwind(trie: *std.ArrayList(MtagElem),
x: usize,
y: usize,
str: []const u8,
ver: *std.ArrayList(u32)) !void {
// Reached the root of the m-tag tree, stop recursion.
if (x == mtag_root and y == mtag_root) return;
// Unwind history further.
try unwind(trie, trie.items[x].pred, trie.items[y].pred, str, ver);
// Get tag values. Tag histories must have equal length.
std.debug.assert(x != mtag_root and y != mtag_root);
const ex = trie.items[x].elem;
const ey = trie.items[y].elem;
if (ex != none and ey != none) {
// Both tags are valid string indices, extract component.
try ver.append(std.testing.allocator, s2n(str[ex..ey]));
} else {
// Both tags are none (this corresponds to zero repetitions).
std.debug.assert(ex == none and ey == none);
}
}
fn s2n(str: []const u8) u32 { // convert a pre-parsed string to a number
var n: u32 = 0;
for (str) |c| { n = n * 10 + (c - 48); }
return n;
}
fn parse(yyinput: [:0]const u8) !std.ArrayList(u32) {
var yycursor: usize = 0;
var yymarker: usize = 0;
var mt = try std.ArrayList(MtagElem).initCapacity(std.testing.allocator, 0);
defer mt.deinit(std.testing.allocator);
// Final tag variables available in semantic action.
%{svars format = "var @@: usize = none;"; %}
%{mvars format = "var @@: usize = mtag_root;"; %}
// Intermediate tag variables used by the lexer (must be autogenerated).
%{stags format = "var @@: usize = none;"; %}
%{mtags format = "var @@: usize = mtag_root;"; %}
%{
re2c:YYMTAGP = "@@ = add_mtag(&mt, @@, yycursor) catch none;";
re2c:YYMTAGN = "@@ = add_mtag(&mt, @@, none) catch none;";
re2c:yyfill:enable = 0;
re2c:tags = 1;
num = [0-9]+;
@t1 num @t2 ("." #t3 num #t4)* [\x00] {
var ver = try std.ArrayList(u32).initCapacity(std.testing.allocator, 0);
try ver.append(std.testing.allocator, s2n(yyinput[t1..t2]));
try unwind(&mt, t3, t4, yyinput, &ver);
return ver;
}
* { return error.SyntaxError; }
%}
}
test {
var result = try parse("1");
var expect = [_]u32{1};
try std.testing.expectEqualDeep(&expect, result.items);
result.deinit(std.testing.allocator);
}
test {
var result = try parse("1.2.3.4.5.6.7");
var expect = [_]u32{1, 2, 3, 4, 5, 6, 7};
try std.testing.expectEqualDeep(&expect, result.items);
result.deinit(std.testing.allocator);
}
test {
const result = parse("1.2.") catch null;
try std.testing.expectEqualDeep(result, null);
}
پشتیبانی از کدگذاری (Encoding support)
درک تفاوت بین 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ها تعیین میشوند. هنگامی که re2zig عبارات باقاعده را به اتوماتا کامپایل میکند، 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 تعاریف re2zig را برای بلوکها، دستهها و ویژگیهای توصیفشده در استاندارد یونیکد فراهم میکنند.
گزینهٔ --input-encoding کدگذاری فایل مبدأ را تعیین میکند، که میتواند برای فعال کردن لیترالهای یونیکد در عبارات باقاعده استفاده شود. برای مثال --input-encoding utf8 به re2zig میگوید که فایل مبدأ با فرمت UTF8 است (این گزینه با --utf8 که کدگذاری متن ورودی را تنظیم میکند متفاوت است). گزینهٔ --encoding-policy نحوهٔ مدیریت جانشینهای یونیکد (code pointها در محدودهٔ [0xD800-0xDFFF]) را توسط re2zig مشخص میکند.
در ادامه نمونهای از یک تحلیلگر لغوی برای شناسههای یونیکد با کدگذاری UTF8 آمده است.
// re2zig $INPUT -o $OUTPUT --utf8
const std = @import("std");
%{include "unicode_categories.re" %}
fn lex(yyinput: [:0]const u8) bool {
var yycursor: u32 = 0;
var yymarker: u32 = 0;
%{
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; }
%}
}
test {
try std.testing.expect(lex("_Ыдентификатор"));
}
فایلهای ضمیمه (Include files)
ابزار re2zig به کاربر امکان میدهد فایلهای دیگر را با استفاده از بلوکی به شکل /*!include:re2c FILE */ یا %{include FILE %}، یا دستورالعمل درونبلوکی !include FILE ; ضمیمه کند، که در آن FILE مسیر فایل مورد نظر برای ضمیمه شدن است. re2zig فایلهای ضمیمه را در پوشهٔ فایل دربرگیرنده و در مسیرهای ضمیمه که میتوان با گزینهٔ -I تعیین کرد، جستجو میکند. بلوکها/دستورالعملهای ضمیمه در re2zig دقیقاً مشابه #include در C/C++ عمل میکنند: محتویات FILE عیناً در محل بلوک/دستورالعمل جایگذاری میشوند. فایلهای ضمیمه ممکن است خود شامل فایلهای ضمیمهٔ دیگری باشند. از گزینهٔ --depfile برای ردیابی وابستگیهای ساخت فایل خروجی به فایلهای ضمیمه استفاده کنید. ابزار re2zig برخی فایلهای ضمیمهٔ از پیش تعریفشده را ارائه میدهد که در زیرپوشهٔ include/ پروژه یافت میشوند. این فایلها شامل تعاریفی هستند که ممکن است برای پروژههای دیگر نیز مفید باشند (مانند دستهبندیهای یونیکد) و ساختاری شبیه به یک کتابخانهٔ استاندارد برای re2zig تشکیل میدهند. در ادامه مثالی از نحوهٔ استفاده از فایلهای ضمیمه آورده شده است.
فایل ضمیمهٔ ۱ (definitions.zig)
const Num = enum { integer, floating, nan };
%{
number = [1-9][0-9]*;
%}
فایل include ۲ (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.floating; }
فایل ورودی
// re2zig $INPUT -o $OUTPUT
const std = @import("std");
%{include "definitions.zig" %}
fn lex(yyinput: [:0]const u8) Num {
var yycursor: u32 = 0;
var yymarker: u32 = 0;
%{
re2c:yyfill:enable = 0;
* { return Num.nan; }
number { return Num.integer; }
!include "extra_rules.re.inc";
%}
}
test {
try std.testing.expectEqual(lex("123"), Num.integer);
try std.testing.expectEqual(lex("123.4567"), Num.floating);
}
فایلهای سرآیند
ابزار re2zig این امکان را فراهم میسازد که با استفاده از گزینهٔ --header یا پیکربندی re2c:header و جفتبلوکهایی به شکل /*!header:re2c:on*/ و /*!header:re2c:off*/، یا %{header:on%} و %{header:off%}، از فایل ورودی .re یک فایل سرآیند تولید شود. بلوک نخست آغاز فایل سرآیند و بلوک دوم پایان آن را مشخص میکند. تمام موارد مابین این بلوکها توسط re2zig پردازش شده و کد تولیدشده در فایلی که با گزینهٔ --header یا پیکربندی re2c:header مشخص شده (یا در صورت عدم استفاده از گزینه یا پیکربندی، در stdout) نوشته میشود. فایل سرآیند تولیدشدهٔ خودکار ممکن است در مواردی نیاز باشد که از re2zig برای تولید تعاریفی استفاده میشود که باید از سایر واحدهای ترجمه (translation units) قابل مشاهده باشند.
در ادامه نمونهای از تولید یک فایل سرآیند آورده شده است که شامل تعریف وضعیت لکسر به همراه متغیرهای برچسب (tag variables) است (تعداد متغیرها به گرامر باقاعده بستگی داشته و برای برنامهنویس نامشخص است).
فایل ورودی
// re2zig $INPUT -o $OUTPUT --header lexer/state.zig
const std = @import("std");
const state = @import("lexer/state.zig"); // the module is generated by re2c
%{header:on %}
pub const State = struct {
yyinput: [:0]const u8,
yycursor: usize,
%{stags format = "@@: usize,"; %}
};
%{header:off %}
fn lex(yyrecord: *state.State) usize {
var t: usize = 0;
%{
re2c:header = "lexer/state.zig";
re2c:api = record;
re2c:yyfill:enable = 0;
re2c:tags = 1;
[a]* @t [b]* { return t; }
%}
}
test {
var st = state.State {
.yyinput = "ab",
.yycursor = 0,
%{stags format = ".@@ = 0,"; %}
};
try std.testing.expectEqual(lex(&st), 1);
}
فایل سرآیند
// Generated by re2c
pub const State = struct {
yyinput: [:0]const u8,
yycursor: usize,
yyt1: usize,
};
برنامههای اسکلتی (Skeleton programs)
با گزینهٔ -S, --skeleton، ابزار re2zig تمام کدهای غیر re2zig را نادیده گرفته و یک برنامهٔ C خودکفا تولید میکند که میتواند در ادامه کامپایل و اجرا شود. این برنامه از کد تحلیلگر لغوی (lexer) و دادههای ورودی تشکیل شده است. برای هر DFA ساختهشده (بلوک یا شرط)، re2zig یک lexer مستقل و دو فایل تولید میکند: یک فایل .input با رشتههای مشتقشده از DFA، و یک فایل .keys با نتایج تطابق مورد انتظار. برنامه هر lexer را بر روی فایل .input مربوطه اجرا کرده و نتایج را با انتظارات مقایسه میکند. برنامههای اسکلتی به چند دلیل بسیار مفید هستند:
- آنها میتوانند درستی بهینهسازیهای مختلف re2zig را بررسی کنند (دادهها در ابتدای فرآیند، پیش از اعمال هرگونه تغییر و تبدیل در DFA تولید میشوند).
- تولید مجموعهای از دادههای ورودی با پوشش مناسب میتواند هم برای آزمون و هم برای بنچمارک مفید باشد.
- تولید برنامههای اجرایی خودکفا به کاربر امکان میدهد نمونههای آزمون کمینه (minimized test cases) دریافت کند (کد اصلی ممکن است بزرگ بوده یا وابستگیهای زیادی داشته باشد).
دشواریِ تولید دادههای ورودی در این است که بهجز در سادهترین حالتها، تعداد رشتههای ورودیِ ممکن بیش از حد زیاد است (حتی اگر طول رشته محدود باشد). re2zig این مشکل را با تولید رشتههای کافی برای پوشش تقریباً تمام انتقالهای DFA حل میکند. این ابزار از الگوریتم زیر استفاده میکند: ابتدا یک اسکلت از DFA میسازد. برای کدگذاریهایی با اندازهٔ واحد کد ۱ بایتی (مانند ASCII، UTF-8 و EBCDIC)، این اسکلت صرفاً یک کپی دقیق از DFA اصلی است. برای کدگذاریهایی با واحدهای کد چندبایتی، اسکلت یک کپی از DFA با حذف برخی از انتقالها است: بدین ترتیب که re2zig حداکثر ۲۵۶ واحد کد را برای هر بازهٔ پیوسته و مجزا که متناظر با یک انتقال DFA است در نظر میگیرد. مقادیر انتخابشده بهطور یکنواخت توزیع شدهاند و شامل مرزهای بازه نیز میشوند. re2zig بهجای تلاش برای پوشش تمام مسیرهای ممکن در اسکلت (که کاری ناممکن است)، مسیرهای کافی برای پوشش همهٔ انتقالهای اسکلت را تولید میکند و بدین ترتیب پرشهای شرطی مربوطه را در lexer تحریک مینماید. پیادهسازی این الگوریتم به حدود 1Gb انتقال محدود شده و مقدار ثابتی از حافظه را مصرف میکند (re2zig دادهها را بهمحض تولید در فایل مینویسد).
مصورسازی و اشکالزدایی (Visualization and debug)
با گزینهٔ -D, --emit-dot، ابزار re2zig کدی تولید نمیکند؛ در عوض، 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)
ابزار re2zig در اصل توسط 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.