۲۳ - ۱۱تمرین‌های فصل

۱.

طبق تعاریف در این آدرس، یه پارسر برای نسخه‌بندیِ معنایی بنویسین. بعد از اینکه پارسرتون کار کرد، یه نمونه ِ ‏‎Ord‎‏ برای تایپِ ‏‎SemVer‎‏ بنویسین که از مشخصاتی که در سایتِ SemVer آورده شده پیروی می‌کنه.

-- ،به خاطر اولویت/ترتیب‌بندی
-- .نمیشه اعداد رو مثل نوشته‌ها مرتب کرد
data NumberOrString =
    NOSS String
  | NOSI Integer

type Major = Integer
type Minor = Integer
type Patch = Integer
type Release = [NumberOrString]
type Metadata = [NumberOrString]

data SemVer =
  SemVer Major Minor Patch Release Metadata

parseSemVer :: Parser SemVer
parseSemVer = undefined

نتایج مورد نظر:

λ> parseString parseSemVer mempty "2.1.1"
Success (SemVer 2 1 1 [] []

λ> parseString parseSemVer mempty "1.0.0-x.7.z.92"
Success (SemVer 1 0 0
         [NOSS "x", NOSI 7, NOSS "z", NOSI 92] [])

λ> SemVer 2 1 1 [] [] > SemVer 2 1 0 [] []
True

۲.

یه پارسر برای اعداد صحیح ِ بزرگتر از صفر بنویسین. از توابعِ ازپیش‌تعریف‌شده ِ ‏‎digit‎‏ و ‏‎integer‎‏ استفاده نکنین، ولی موردی نداره از بقیه‌ی کتابخونه‌هایی که تا اینجا نشون‌تون دادیم استفاده کنین. ازتون انتظار نمیره یه کتابخونه ِ پارسینگ رو از اول بنویسین.

parseDigit :: Parser Char
parseDigit = undefined

base10Integer :: Parser Integer
base10Integer = undefined 

نتایج مورد نظر:

λ> parseString parseDigit mempty "123"
Success '1'
λ> parseString parseDigit mempty "abc"
Failure (interactive):1:1: error: expected:
  parseDigit
abc<EOF>
^
λ> parseString base10Integer mempty "123abc"
Success 123
λ> parseString parseDigit mempty "abc"
Failure (interactive):1:1: error: expected:
  integer
abc<EOF>
^

راهنمایی: فرض کنین اعداد با مبنای ۱۰ رو پارس می‌کنین. همینطور که اعداد رو از چپ به راست پارس می‌کنین، از حساب برای یه جور "انبار‌کننده" ِ کم هزینه استفاده کنین.

۳.

پارسری که نوشتین رو ارتقا بدین تا برای اعدادِ منفی هم کار کنه. سعی کنین یه پارسر ِ جدید بر مبنای پارسر ِ قبلی‌تون تعریف کنین.

λ> parseString base10Integer mempty "-123abc"
Success (-123)

۴.

برای انواع فرمت‌های تلفن‌های کانادا یه پارسر بنویسین.

-- یا همون کدِ منطقه
type NumberingPlanArea = Int
type Exchange = Int
type LineNumber = Int

data PhoneNumber =
  PhoneNumber NumberingPlanArea
             Exchange LineNumber
deriving (Eq, Show)

parsePhone :: Parser PhoneNumber
parsePhone = undefined

باید چنین رفتاری داشته باشه:

λ> parseString parsePhone mempty "123-456-7890"
Success (Phone Number 123 456 7890)
λ> parseString parsePhone mempty "1234567890"
Success (Phone Number 123 456 7890) 
λ> parseString parsePhone mempty "(123) 456-7890"
Success (Phone Number 123 456 7890) 
λ> parseString parsePhone mempty "1-123-456-7890"
Success (Phone Number 123 456 7890)

برای مقایسه می‌تونین به صفحه‌ی Wikipedia با عنوان National conventions for writing telephone numbers برین. برای تمرین بیشتر، پیشنهاد می‌کنیم برای فرمتِ شماره تلفن‌های کشور خودتون هم پارسر بنویسین.

۵.

برای یه فایلِ گزارشی پارسر بنویسین و زمانِ گذرونده برای هر کار رو جمع بزنین. مضاف بر اون، میانگین زمانی که در هر روز روی یک کار گذرونده شده هم پیدا کنید. این فرمت نوشتاری از کامنت پشتیبانی می‌کنه، که پارسرتون باید اونها رو نادیده بگیره. یه کاراکترِ ‏‎#‎‏ که بعدش تاریخ میاد، شروعِ یک روز رو تعیین می‌کنه.

یه مثال از فرمتِ گزارش:

-- wheee a comment

# 2025-02-05
08:00 Breakfast
09:00 Sanitizing moisture collector
11:00 Exercising in high-grav gym
12:00 Lunch
13:00 Programming
17:00 Commutting home in rover
17:30 R&R
19:00 Dinner
21:00 Shower
21:15 Read
22:00 Sleep

# 2025-02-07 -- تاریخ‌ها الزاماً متسلسل نیستن
08:00 Breakfast
09:00 Bumped head, passed out
13:36 Wake up, headache
13:37 Go to medbay
13:40 Patch self up
13:45 Commute home for rest
14:15 Read
21:00 Dinner
21:15 Read
22:00 Sleep

خودتون باید یه نوع‌داده ِ مناسب برای ارائه‌ی این داده پیدا کنین. برای امتیاز اضافه، با یه نمونه ِ ‏‎Show‎‏ دو طرفه‌ش کنین، یعنی همون چیزی رو چاپ کنه که پارس کردین. بعد هم یه ژنراتور ِ ‏‎Gen‎‏ از ‏‎QuickCheck‎‏ برای این داده بنویسین و ببینین که آیا ‏‎QuickCheck‎‏ می‌تونه پارسرتون رو شکست بده یا نه.

۶.

یه پارسر برای آدرس‌های ‏‎IPv4‎‏ بنویسین.

import Data.Word

data IPAddress =
  IPAddress Word32
  deriving (Eq, Ord, Show)

یه ‏‎Word32‎‏ در واقع یه int ِ سی‌ودو-بیتی و بی‌علامته (‏‎32-bit unsigned int‎‏). کمترین مقداری که این تایپ می‌تونه داشته باشه صفره (یعنی شامل اعداد منفی نمیشه)، اما در عوض دو برابر عددِ مثبت داره. دقت کنین:

Prelude> import Data.Int
Prelude> import Data.Word
Prelude> maxBound :: Int32
2147483647
Prelude> maxBound :: Word32
4294967295
Prelude> div 4294967295 2147483647
2

استفاده از ‏‎Word32‎‏ برای ارائه‌ی آدرس‌های ‏‎IPv4‎‏ راه جمع‌وجور و مناسبی‌ه. تو این تمرین انتظار میره خودتون طرز کار آدرس‌های IP رو یاد بگیرین تا بتونین یه پارسر ِ صحیح بنویسین. می‌تونین از یه موتور جستجو استفاده کنین، یا اگه کتابی با موضوع اینترنت و شبکه دارین از روی اون بخونین.

مثال‌هایی از آدرس‌هایی ‏‎IPv4‎‏ و معادلِ مبنای ۱۰ شون:

172.16.256.1 -> 2886794753
204.120.0.15 -> 3430416399

۷.

مثل تمرین قبل، اما برای ‏‎IPv6‎‏.

import Data.Word

data IPAddress6 =
  IPAddress6 Word64 Word64
  deriving (Eq, Ord, Show)

مثال‌هایی از آدرس‌های ‏‎IPv6‎‏ و معادلِ مبنای ۱۰ شون:

0:0:0:0:0:ffff:ac10:fe01 -> 281473568538113
0:0:0:0:0:ffff:cc78:f    -> 281474112159759

FE80:0000:0000:0000:0000:0202:B3FF:FE1E:8329 ->
  338288524927261089654163772891438416681

2001:DB8::8:800:200C:417A ->
  42540766411282592856906245548098208122

آدرس‌های ‏‎IPv6‎‏ رو میشه کامل یا کوتاه شده، یا مخفف نوشت، و همین یه ذره کار رو سخت می‌کنه. از این پرسش‌وپاسخ می‌تونین بیشتر یاد بگیرین.

مطمئن بشین که پارسرتون نسخه‌ی مخففِ مثال‌های قبلی هم درست پارس می‌کنه:

FE80::0202:B3FF:FE1E:8329
2001:DB8::8:800:200C:417A

۸.

نمونه ِ مشتق‌گرفته‌شده‌ی ‏‎Show‎‏ رو از تایپ‌های ‏‎IPAddress‎‏ و ‏‎IPAddress6‎‏ حذف کنین و برای هر تایپ طوری نمونه ِ ‏‎Show‎‏ بنویسین که با فرمتِ معمولِ نوشتاری ِ همون تایپ چاپ کنه.

۹.

یه تابع برای تبدیل بین ‏‎IPAddress‎‏ و ‏‎IPAddress6‎‏ بنویسین.

۱۰.

برای زبان DOT که Graphviz برای بیانِ گراف در نوشتار ازش استفاده می‌کنه، یه پارسر بنویسین.

پیشنهاد می‌کنیم به نوع‌داده ِ AST در ‏‎Haphviz‎‏ نگاه کنین تا بهتون کمک کنه گراف رو در نوع‌داده ِ هسکل نشون بدین. اگر هم حوصله دارین این تمرین رو بهتر انجام بدین، می‌تونین ‏‎fgl‎‏ رو امتحان کنین.