۲۳ - ۵اکوسیستم پارسینگ هسکل

هسکل کتابخونه‌های پارسینگ ِ خیلی خوب و زیادی داره. احتمالاً معروف‌ترین‌ها ‏‎parsec‎‏ و ‏‎attoparsec‎‏ هستن، ولی کتابخونه‌های دیگه‌ای مثل ‏‎megaparsec‎‏ هم وجود دارن. ‏‎aeson‎‏ و ‏‎cassava‎‏ از کتابخونه‌هایی اند که برای پارس ِ انواعِ بخصوصی از داده‌ها درست شدن (به ترتیب برای داده‌ها ِ JSON و داده‌ها ِ CSV).

همونطور که دیدین ما برای این فصل ‏‎trifecta‎‏ رو انتخاب کردیم. یکی از دلایل‌ش اینه که ‏‎trifecta‎‏ برخلافِ بعضی کتابخونه‌های دیگه پیغام‌های خطای خیلی ساده و گویایی داره. از طرفِ دیگه به نظر نمیرسه اساسِ ‏‎trifecta‎‏ تغییرات زیادی کنه. طراحی‌ش یه کم غیرمعمول و پیچیده‌ست، ولی بیشتر چیزهایی که پیچیده‌ش می‌کنن، تو این فصل تأثیری به حال شما ندارن. اگه قصد دارین خیلی زیاد پارس کنین، بهتره با ‏‎attoparsec‎‏ آشنا بشین؛ سرعتِ پارس کردنِ این کتابخونه دلیلِ اصلیِ شهرت‌شه. جلوتر در این فصل کمی با ‏‎attoparsec‎‏ و ‏‎aeson‎‏ کار می‌کنیم.

‏‎trifecta‎‏ طوری تکامل پیدا کرده که API ِش* بین دوتا کتابخونه ِ ‏‎parsers‎‏ و ‏‎trifecta‎‏ تقسیم شده. دلیل‌ش اینه که خودِ پکیج ِ ‏‎trifecta‎‏، پیاده‌سازی‌های بخصوصِ پارسر ِ ‏‎trifecta‎‏ و کاربردهای مختصِ ‏‎trifecta‎‏ رو داره، ولی ‏‎parsers‎‏ مجموعه‌ای از تایپکلاس‌هایی‌ه که قابلیت‌های کلیِ پارسرها رو انتزاعی می‌کنن. ماژول ِ ‏‎Text.Trifecta‎‏ چیزهایی که از هر دو پکیج لازم هستن رو در اختیارتون میذاره. اینها رو گفتیم که اگه یه وقت خواستین غارنوردی کنین، بدونین از کجا شروع کنین.

*

API مخففِ application programming interface و به معنای رابط برنامه‌نویسی کاربردی هست. وقتی یه برنامه رو با استفاده از یه کتابخونه می‌نویسیم، یا برنامه‌مون به یه سرویس مثل توئیتر درخواست می‌فرسته (یا بطور کلی، نرم‌افزاری رو با اتکا به یه نرم‌افزارِ دیگه درست می‌کنیم)، از یه سری توابعِ تعریف شده استفاده می‌کنیم. API همون توابعی هستن که برای تعامل با نرم‌افزارِ دوم ازشون استفاده می‌کنیم؛ بدون اینکه نگرانِ کدِ منبع‌ِشون باشیم. هر وقت یه کتابخونه رو روی Hackage نگاه می‌کنین، دارین API ِ اون کتابخونه رو می‌بینین (مگر اینکه روی کدِ منبع‌ِش کلیک کنین).

تایپکلاس‌های ‏‎parsers‎‏

همونطور که بالاتر گفتیم، ‏‎trifecta‎‏ به یه سری تایپکلاس‌های کتابخونه ِ ‏‎parsers‎‏ اتکا داره. این تایپکلاس‌ها کارهای رایج و مشترکی که پارسرها انجام میدن رو انتزاعی می‌کنن. به چندتاشون که تو این فصل استفاده می‌کنیم اشاره می‌کنیم تا یه شِمای کلی ازشون پیدا کنین.

دقت کنین که توضیحات زیر مربوط به کُدهایی هستن که کتابخونه ِ ‏‎parsers‎‏ براتون تأمین کرده، لازم نیست بنویسین‌شون!

۱.

تایپکلاسِ ‏‎Alternative‎‏ یه سوپرکلاس برای تایپکلاسِ ‏‎Parsing‎‏ ِه. یه ذره جلوتر از ‏‎Alternative‎‏ بیشتر صحبت می‌کنیم. تایپکلاسِ ‏‎Parsing‎‏ قابلیت‌هایی رو تأمین می‌کنه که برای توصیفِ پارسرها لازم‌اند (مستقل از ورودی‌شون). حداقل توابعِ لازم برای تعریف یه نمونه ِ کامل از این تایپکلاس ‏‎try‎‏، ‏‎(<?>)‎‏، و ‏‎notFollowedBy‎‏ هستن. با ‏‎try‎‏ شروع می‌کنیم:

-- Text.Parser.Combinators
class Alternative m => Parsing m where
  try :: m a -> m a

این تابع یه پارسر می‌گیره که ممکنه ورودی مصرف کنه، و در صورتِ شکست، برمی‌گرده به جایی که شروع کرده بوده، در صورتِ مصرف نکردنِ ورودی هم شکست می‌خوره.

با تابعِ ‏‎notFollowedBy‎‏ (م. به معنای "دنبال نشده توسطِ") از این تایپکلاس میشه کلیدواژه‌ها رو پیدا کرد؛ یه رشته از حروف که می‌خوایم رو بهش میدیم و تعیین می‌کنیم که چه چیزی به دنبال اون حروف نباشه:

notFollowedBy :: Show a => m a -> m ()
-- م. مثال برای پارسری که
-- .رو پیدا می‌کنه let کلیدواژه‌ی
-- > noAlpha = notFollowedBy alphaNum
-- > keywordLet =
--     try $ string "let" <* noAlpha

۲.

تایپکلاسِ ‏‎Parsing‎‏ شاملِ ‏‎unexpected‎‏ (م. به معنای "غیرمنتظره") هم میشه. از این تابع برای صدور خطا روی یه توکِن ِ غیرمنتظره به کار میره (اوایل فصل هم دیدیم). تابعِ ‏‎eof‎‏ فقط در آخرِ ورودی موفق میشه:

eof :: m ()
-- > eof =
--         notFollowedBy anyChar
--     <?> "end of input"

در بخش‌های بعدی این تابع رو بیشتر می‌بینیم.

۳.

این کتابخونه تایپکلاسِ ‏‎CharParsing‎‏ هم با سوپرکلاس ِ ‏‎Parsing‎‏ تعریف می‌کنه که برای پارس ِ حروف مجرد به کار میره.

-- Text.Parser.Char
class Parsing m => CharParsing m where

از این کلاس قبلاً ‏‎char‎‏ رو دیدیم، ولی تابع‌های زیر هم شامل میشه:

-- هر کاراکتری غیر از اونی
-- که بهش داده شده رو پارس
-- می‌کنه. کاراکتر پارس‌شده
-- .رو برمی‌گردونه
notChar :: Char -> m Char

-- پارسر برای هر کاراکتری موفق
-- میشه. کاراکتر پارس‌شده
-- .رو برمی‌گردونه
anyChar :: m Char

-- ،تسلسلی از کاراکترها رو پارس می‌کنه
-- .پارس‌شده رو برمی‌گردونه string
string :: String -> m String

-- تسلسلی از کاراکترها با تایپ
-- ای Text رو پارس می‌کنه، تیکه Text
-- .که پارس شده رو برمی‌گردونه
text :: Text -> m Text