Swift 小技 -- Regexと再帰関数 1

なんだかswiftで超長い文字列をRegexかけたら超めんどくさいことになってしまったのと、それに対するソリューションが打ち出せたのでそのメモ。

 

Regexのネタはsystemstatsのサマリーで、例えばこんな文字列がはき出される

 


Summary
=======
System Version: 15G31
Total Time: 91:23:15

Usage
=====
Wake Time: 67:14:38
User Active: 47:57:26

 ... 中略 ...

Memory Summary
==============
Swap Dev: SSD
Total: 8192.0 MB
Free: 297.3 MB
Wired: 1835.0 MB
Compressor: 1140.6 MB
Compressed: 3275.0 MB

...

Ranked Memory Activity
======================
Time: 2016-07-22 03:46:44 to 2016-07-22 03:56:45 (00:10:01)
Free: 791.1 MB
Wired: 1738.0 MB
Compressor: 1099.2 MB
Compressed: 3421.0 MB

...

で、やりたいことはこれから集計結果をKVSにして、CoreDataに保存。

階層構造を持っているので、[Key:AnyObject]にしてleafならVはDoubleなり独自型なりの具体値、leafでなくなんらか上位概念なら具体値にぶつかるまで[Key:[Key:AnyObject]]とする。

 

今日言及したいのは、その前段としては上記データ解析のための文字列引き抜き。

-Dオプションをsystemstatsにわたすと、JSONっぽいデータ構造になるんで、"} -> }:"とか部分的に何カ所か変換すればJSONシリアライズできるかとも考えたが、それでは結局何度も飛んでくる同じデータを自前でサマってやらないといけないので、今回は演算をケチるためこちらを使うこととした。

 

DataRegulatorのプロトコルをつくって具体的Regulatorは必ずparseを実装する。

それぞれ具体的な処理対象はフォーマットがいちいち違ったりするので、Enumで実装して、興味のある処理対象にぶつかったらenum値が知っている処理内容(ここでRegex使う)を返すというそんな感じの設計。

 

最初に描いたのはこんな感じ。

func parse(data:String) -> [String:AnyObject] {
let exp = Type.MemorySize()
let memQnt = "\(exp.pattern)"

let timeForm = "\\d+:\\d+:\\d+"
let dateForm = "\\d{4}-\\d{2}-\\d{2}\\s\(timeForm)"
switch self {
// case Summary:
// case Usage:
// case CPUSummary: break
// case ThermalSummary: break
// case FanSummary: break
// case IOSummary: break
// case RankedIOActivity: break
// case AirportSummary: break
// case TopFanActivity: break
case MemorySummary:
let args = ["Swap Dev", "Total", "Free", "Wired", "Compressor", "Compressed", "Purgeable", "Internal", "External", "IOAccelResident", "IOAccelWired", "IOAccelDirty",
"IOAccelCached", "IOAccelPurgeable", "Faults", "Purges", "Zero-fills", "Reactivations", "Page-ins", "Page-outs", "Decompressions", "Compressions", "Swap-ins", "Swap-outs"]
let pattern = "\\s*Swap\\sDev:\\t(\\w+)\n\\s*Total:\\t\\s*(\(memQnt))\n\\s*Free:\\t\\s*(\(memQnt))\n\\s*Wired:\\t\\s*(\(memQnt))\n" +
"\\s*Compressor:\\t\\s*(\(memQnt))\n\\s*Compressed:\\t\\s*(\(memQnt))\n\\s*Purgeable:\\t\\s*(\(memQnt))\n\\s*Internal:\\t\\s*(\(memQnt))\n" +
"\\s*External:\\t\\s*(\(memQnt))\n\n\\s*IOAccelResident:\\t\\s*(\(memQnt))\n\\s*IOAccelWired:\\t\\s*(\(memQnt))\n\\s*IOAccelDirty:\\t\\s*(\(memQnt))\n" +
"\\s*IOAccelCached:\\t\\s*(\(memQnt))\n\\s*IOAccelPurgeable:\\t\\s*(\(memQnt))\n\n\\s*Faults:\\t\\s*(\\d+)\n\\s*Purges:\\t\\s*(\\d+)\\s\\(\\s*(\(memQnt))\\/s\\)\n" +
"\\s*Zero-fills:\\t\\s*(\\d+)\\s\\(\\s*(\(memQnt))\\/s\\)\n\\s*Reactivations:\\t\\s*(\\d+)\\s\\(\\s*(\(memQnt))\\/s\\)\n\\s*Page-ins:\\t\\s*(\\d+)\\s\\(\\s*(\(memQnt))\\/s\\)\n" +
"\\s*Page-outs:\\t\\s*(\\d+)\\s\\(\\s*(\(memQnt))\\/s\\)\n\\s*Decompressions:\\t\\s*(\\d+)\\s\\(\\s*(\(memQnt))\\/s\\)\n\\s*Compressions:\\t\\s*(\\d+)\\s\\(\\s*(\(memQnt))\\/s\\)\n" +
"\\s*Swap-ins:\\t\\s*(\\d+)\\s\\(\\s*(\(memQnt))\\/s\\)\n\\s*Swap-outs:\\t\\s*(\\d+)\\s\\(\\s*(\(memQnt))\\/s\\)"

return generateSimpleArgs(args,
                                             data: data,
                                              pattern: pattern)

自戒のためだが、自分で言うのも何だけどバカも休み休みにしろといった内容。。。もはやこのpatternはメンテナンス不能(正確には"したくない")だし、同じことを何度も何度も何度も書きまくり。

 

で、次にこう書いた。

case RankedMemoryActivity:

     typealias RegexType = (labels: [String], types: [Type], regex: String)

            

     let memory:RegexType = (labels:[""], types:[._MemSize], regex:"\\t\\s+(\(memQnt))\n")

     let memAct:RegexType = (labels:["Size", "Speed"], types:[._Int, ._MemSize], regex:"\\t\\s+(\\d+)\\s\\(\\s+(\(memQnt)\\/s)\\)\n")

     // CONTRACT: ()の数とtreeの配列サイズが一致すること subが空の時は1つしかないのでlabelをキー名とする

     // CONTRACT: item0のタプルが最上位キーとなる

     let args:[(label:String, regex:RegexType)] =

                [("Time:", (["Start", "Duration"], [._Date, ._Time], "\\t(\(dateForm))\\sto\\s\(dateForm)\\s\\*1\\)\n")),

                 ("Free:", memory), ("Wired:", memory), ("Compressor:", memory), ("Compressed:", memory),("Purgeable:", memory), ("Internal:", memory),

                 ("External:", memory),

                 ("IOAccelResident:", memory), ("IOAccelWired:", memory),("IOAccelDirty:", memory),("IOAccelCached:", memory), ("IOAccelPurgeable:", memory),

                 ("Faults:", ([""], [._Int], "\\t\\s+(\\d+)")),("Purges:", memAct), ("Zero-fills:", memAct), ("Reactivations:", memAct), ("Page-ins:", memAct), ("Page-outs:", memAct),

                 ("Decompressions:", memAct), ("Compressions:", memAct), ("Swap-ins:", memAct), ("Swap-outs:", memAct)]

            

     let comb = {(a1:String, a2:(label: String, regex: RegexType)) -> String in

                              return a1 + "\\s*\(a2.label)\(a2.regex.regex)"

                        }      

      let pattern = args[13 ... (args.count - 1)].reduce(

                args[8 ... 12].reduce(

                    args[1 ... 7].reduce(

                        comb("", args[0]), combine: comb) + "\n", combine: comb) +

                                   "\n", combine: comb)

      debugPrint(pattern)

      return generateComplexArgs(args,

                                                      data: data,

                                                       pattern: pattern)

 

 ミソは前から順にaccとしているので、reduce順は逆ですよといったところ。

なお糖衣が効いてるだけで、ObjCでもblocksと関数ポインタが解っていれば同じことはできる。

 

元来僕は、書くのに数十分かけた再帰文は読むのにも数十分かかると考える方なので、あまり技巧的なことはしないのだけど、さすがにこれは。。。

systemstatsの書式も、いまいちマナーに決まりが薄いし。

 

型はgenericsを使うという手もありそうだけど、どうもScalaにくらべてswiftは型のハンドリングがいまいちな感想。つまり、genericsに対応遅れ感がどうしても残る。

 

まだ完成している言語とは言えないので、しかたないとして、今抱えてる悩みは、ScalaHaskellのノリでガシガシFP構文書くと、Xcodeソースコードwatcherプロセスがいい感じに死ぬこと。

 

*1:\(timeForm