描述
下面的日期, 有些使用了 M D Y 格式, 有些使用了 Y M D 格式, 還使用了任意分隔符! 請把這些散亂的文本解析成合適的 ISO 8601 (YYYY-MM-DD) 格式化日期。
假設(shè)只有以 4 個數(shù)字開頭的日期使用 Y M D 格式, 其它的使用 M D Y 格式扩灯。
輸入樣本
2/13/15
1-31-10
5 10 2015
2012 3 17
2001-01-01
2008/01/07
輸出樣本
2015-02-13
2010-01-31
2015-05-10
2012-03-17
2001-01-01
2008-01-07
擴(kuò)展挑戰(zhàn) [中級]
使用 2014-12-24 作為相對日期的基準(zhǔn)媚赖。
當(dāng)添加 days(天數(shù)) 時, 要考慮到每月會有不同的天數(shù), 忽略閏年。
當(dāng)添加月和年時, 使用整個 units, 以至于:
one month before october 10 is september 10
one year after 2001-04-02 is 2002-04-02
one month after january 30 is february 28 (not march 1)
Sally's inputs:
tomorrow
2010-dec-7
OCT 23
1 week ago
next Monday
last sunDAY
1 year ago
1 month ago
last week
LAST MONTH
10 October 2010
an year ago
2 years from tomoRRow
1 month from 2016-01-31
4 DAYS FROM today
9 weeks from yesterday
Sally's expected outputs:
2014-12-25
2010-12-01
2014-10-23
2014-12-17
2014-12-29
2014-12-21
2013-12-24
2014-11-24
2014-12-15
2014-11-24
2010-10-10
2013-12-24
2016-12-25
2016-02-28
2014-12-28
2015-02-25
smls 大神給出了完整的 grammar:
my $today = Date.new(2014, 12, 24);
grammar MessyDate {
rule TOP {
| <date> { make $<date>.made } # 跟在 regex 后面的花括號是閉包
| :i <duration> ago { make $today.earlier: |$<duration>.made }
| :i <duration> from <date> { make $<date>.made.later: |$<duration>.made }
}
rule date {
| [ || <month> (<sep>?) <day> [$0 <year>]?
|| <day> (<sep>?) <month> [$0 <year>]?
|| <year> (<sep>?) <month> $0 <day> ]
{ make Date.new: $<year>.made//$today.year, |$<month day>?.made }
| :i today { make $today }
| :i yesterday { make $today - 1 }
| :i tomorrow { make $today + 1 }
| :i last <weekday> { make $today - ($today.day-of-week - $<weekday>.made) % 7 || 7 }
| :i next <weekday> { make $today + ($<weekday>.made - $today.day-of-week) % 7 || 7 }
| :i last <unit> { make $today.earlier: |($<unit>.made => 1) }
| :i next <unit> { make $today.later: |($<unit>.made => 1) }
}
rule duration {
<count> <unit> { make $<unit>.made => $<count>.made }
}
token year {
| <number(4)> { make +$<number> } # <number(4)> 是擴(kuò)展的 <...> 語法, 實(shí)際是方法調(diào)用
| <number(2, 0..49)> { make 2000 + $<number> }
| <number(2, 50..*)> { make 1900 + $<number> }
}
token month {
| <number(1..2, 1..12)> { make +$<number> }
| :i Jan[uary]? { make 1 } # [...] 是非捕獲分組
| :i Feb[ruary]? { make 2 }
| :i Mar[ch]? { make 3 }
| :i Apr[il]? { make 4 }
| :i May { make 5 }
| :i Jun[e]? { make 6 }
| :i Jul[y]? { make 7 }
| :i Aug[ust]? { make 8 }
| :i Sep[tember]? { make 9 }
| :i Oct[ober]? { make 10 }
| :i Nov[ember]? { make 11 }
| :i Dec[ember]? { make 12 }
}
token day { <number(1..2, 1..31)> { make +$<number> } }
token weekday {
| :i Mon[day]? { make 1 }
| :i Tue[sday]? { make 2 }
| :i Wed[nesday]? { make 3 }
| :i Thu[rsday]? { make 4 }
| :i Fri[day]? { make 5 }
| :i Sat[urday]? { make 6 }
| :i Sun[day]? { make 7 }
}
token sep { <[-/.\h]> } # <[...]> 是 Perl 6 中的字符類
token count { (<[0..9]>+) { make +$0 } | an? { make 1 } }
token unit { :i (day|week|month|year) s? { make $0.lc } }
multi token number ($digits) { <[0..9]> ** {$digits} }
multi token number ($digits, $test) { (<[0..9]> ** {$digits}) <?{ +$0 ~~ $test }> }
}
for lines() {
say MessyDate.parse($_).made // "failed to parse '$_'";
}
在 grammar 中, 有兩個 regex 的變體, rule
和 token
珠插。rule 默認(rèn)不會回溯. rule 與 token 的一個重要區(qū)別是, rule
這樣的正則采取了 :sigspace
修飾符惧磺。 rule
實(shí)際上是
regex :ratchet :sigspace { ... }
的簡寫. ratchet 這個單詞的意思是: (防倒轉(zhuǎn)的)棘齒, 意思它是不能回溯的! 而 :sigspace
表明正則中的空白是有意義的, 而 token
實(shí)際上是
regex :ratchet { ... }
的簡寫。 所以在 token 中, 若不是顯式的寫上 \s
捻撑、\h
豺妓、\n
等空白符號, 其它情況下就好像空白隱身了一樣, 雖然你寫了, 但是編譯器卻視而不見。
//
在左側(cè)匹配失敗時會在右側(cè)提供一個默認(rèn)值布讹。
<number(4)>
和 <number(2, 0..49)>
中使用了擴(kuò)展了的 <...>
元語法。 標(biāo)識符(例如左面的 number)后面的第一個字符決定了閉合尖括號之前剩余文本的處理训堆。它的底層語義是函數(shù)或方法調(diào)用, 所以, 如果標(biāo)識符后面的第一個字符是左圓括號, 那么它要么是方法調(diào)用, 要么是函數(shù)調(diào)用:
<number(4)>
等價于 <number=&number(4)>
<number(2, 0..49)>
等價于 <number=&number(2, 0..49)>
multi token number ($digits) { <[0..9]> ** {$digits} }
multi token number ($digits, $test) { (<[0..9]> ** {$digits}) <?{ +$0 ~~ $test }> }
在擴(kuò)展的 <...>
語法中, 一個前置的 ?{
或 !{
標(biāo)示著代碼斷言:
(<[0..9]> ** {$digits}) <?{ +$0 ~~ $test }>
等價于:
(<[0..9]> ** {$digits}) { +$0 ~~ $test or fail } # + 強(qiáng)制后面的$0為數(shù)值上下文, 以匹配 $test 中的數(shù)字
上面的兩句代碼, 具名 regex
, token
, 或 rule
是一個子例程, 所以可以傳遞 參數(shù)給具名 token描验。
這從標(biāo)準(zhǔn)輸入里讀取散亂的日期并把對應(yīng)的 ISO 日期寫到標(biāo)準(zhǔn)輸出。
它能解析任務(wù)描述中的所有日期(包含擴(kuò)展), 還有 - 然而, 在它們中我得到 4 個人不同結(jié)果坑鱼。請弄清它們是否是錯誤的, 并且為什么是錯的:
2010-dec-7 --> 我得到 2010-12-07 而不是 2010-12-01
last week --> 我得到 2014-12-17 而不是 2014-12-15
1 month from 2016-01-31 --> 我得到 2016-02-29 而不是 2016-02-28
9 weeks from yesterday --> 我得到 2015-02-24 而不是 2015-02-25
有人在評論中問他 make/made
是類中的方法嗎膘流?
是的, 它們是 Match 類的方法。
Match objects(注意 object 是復(fù)數(shù))
每個 regex match(并且通過擴(kuò)展, 每個 grammar token match)的結(jié)果被表示為一個 Match 對象鲁沥。
通過這個對象你能訪問各種信息片段:
匹配到的字符串
關(guān)于輸入字符串匹配的開始和結(jié)束位置
每個位置捕獲和具名捕獲的sub-matches
-
與這個匹配有關(guān)的 AST 片段, 如果有的話
AST 片段
在 token/rule
里面調(diào)用 make
, 設(shè)置將會與當(dāng)前匹配關(guān)聯(lián)的 "AST 片段"呼股。然后, 你可以通過在當(dāng)前結(jié)果 Match 對象身上調(diào)用 .made
方法來獲取那個關(guān)聯(lián)數(shù)據(jù)。
這正是自由形式的插槽, 允許你使用 Match 對象存儲任何你想要的東西并在以后檢索它, 盡管顯而易見這意味著像我那樣創(chuàng)建一個 AST画恰。
在 grammar 中創(chuàng)建 "AST"
在我的 grammar 中每個 token/rule
使用 .made
來取得它的 sub-rule 匹配制造的數(shù)據(jù)塊(AST), 把它們組合成一個更大的數(shù)據(jù)塊, 這是為了讓它的父級 rule (parent rule) 能被檢索彭谁。等等。
我在每個 token/rule 里面使用這些語法簡寫來引用 sub-matches 的 Match 對象:
-
$0
是指第一個位置子匹配(由 () 捕獲組引起)的Match對象允扇。 -
$<date>
指的是名字為 "date" 的具名 sub-match 的 Match 對象 (通過<date>
遞歸引用 名為 date 的 token 引起).