Scalaは、いわゆる関数型、命令型どっちでもいける全方位型言語である。
一方で、全容を理解するのが難しい言語ではある。(奥行きがある)
しかし、ログ解析者からすると、パーサーの部分だけ理解できればよい、という面もある。
↓のようなINIファイルのパースのみに集中する。。。
6[dataStorage1]
7indexer=Elasticsearch
8url=localhost:9200
9user=admin
10password=qiita
11
12[dataStorage2]
13driver=Splunk
14url=local:8089
15user=admin
16password=qiita
Scalaには、ケースクラスという非常に強力なスペックがある。
ケースクラスとはなにか、とは難しいだが、ログ解析者からすればパターンマッチに派生強化したクラスだと理解する。
Scalaは、ケースクラスによるパターンマッチが非常に強力な上にパーサーコンビネータという機能があり、これを組み合わせるとAST(抽象構文木)を駆使するのが楽にできる。
ASTの何がいいかというと、ログ解析者的には、JSON,mapなどに変換できるところである。
↑に対応するコードをcase classを用いて書く。。
60// String
61case class String_of_AST(string: String) extends AST
62// Property
63case class Property_of_AST(key: String_of_AST, value: String_of_AST) extends AST
64// Section
65case class Section_of_AST(name: String_of_AST, properties: List[Property_of_AST]) extends AST
66// Section(s)
67case class Sections_of_AST(sections: List[Section_of_AST]) extends AST
↑に対応するコードをパーサーコンビネータで書く。。
35class IniParser extends RegexParsers {
36
37 // String
38 def string :Parser[String_of_AST] = """[^\[\]=\s]*""".r^^{
39 case value => String_of_AST(value)
40 }
41 42 // Property
43 def property :Parser[Property_of_AST] = string~"="~string^^{
44 case (key~_~value) => Property_of_AST(key, value)
45 }
46
47 // Section
48 def section :Parser[Section_of_AST] = "["~>string~"]"~rep(property)^^{
49 case (section~_~properties) => Section_of_AST(section, properties)
50 }
51
52 // Section(s)
53 def sections :Parser[Sections_of_AST] = rep(section)^^{
54 case sections => Sections_of_AST(sections)
55 }
パーサーの最終的な目的はなにか?
ログ解析者的には、
意味(Semantics)が同じものを異なる形式(Syntax)にいろいろ変換するためである。
コードを見てみる。。。
1object ParserSample extends App {
2
3 val parser = new IniParser
4
5 val result = parser.parseAll(parser.sections, """
6[db1]
7indexer=Elasticsearch
8url=localhost:9200
9user=admin
10password=qiita
11
12[db2]
13driver=Splunk
14url=local:8089
15user=admin
16password=qiita
17""")
18
19 val sections = result.get
20
21 println(sections)
22 println("")
23
24 val map = sections.sections.map { section =>
25 (section.name.string -> section.properties.map { property =>
26 (property.key.string -> property.value.string)
27 }.toMap)
28 }.toMap
29
30 println(map)
31}
32
33import scala.util.parsing.combinator.RegexParsers
34
35class IniParser extends RegexParsers {
36
37 // String
38 def string :Parser[String_of_AST] = """[^\[\]=\s]*""".r^^{
39 case value => String_of_AST(value)
40 }
41 42 // Property
43 def property :Parser[Property_of_AST] = string~"="~string^^{
44 case (key~_~value) => Property_of_AST(key, value)
45 }
46
47 // Section
48 def section :Parser[Section_of_AST] = "["~>string~"]"~rep(property)^^{
49 case (section~_~properties) => Section_of_AST(section, properties)
50 }
51
52 // Section(s)
53 def sections :Parser[Sections_of_AST] = rep(section)^^{
54 case sections => Sections_of_AST(sections)
55 }
56}
57
58trait AST
59
60// String
61case class String_of_AST(string: String) extends AST
62// Property
63case class Property_of_AST(key: String_of_AST, value: String_of_AST) extends AST
64// Section
65case class Section_of_AST(name: String_of_AST, properties: List[Property_of_AST]) extends AST
66// Section(s)
67case class Sections_of_AST(sections: List[Section_of_AST]) extends AST
実行してみる。。
$ scala Parser.scala
Sections_of_AST(List(Section_of_AST(String_of_AST(db1),List(Property_of_AST(String_of_AST(indexer),String_of_AST(Elasticsearch)), Property_of_AST(String_of_AST(url),String_of_AST(localhost:9200)), Property_of_AST(String_of_AST(user),String_of_AST(admin)), Property_of_AST(String_of_AST(password),String_of_AST(qiita)))), Section_of_AST(String_of_AST(db2),List(Property_of_AST(String_of_AST(driver),String_of_AST(Splunk)), Property_of_AST(String_of_AST(url),String_of_AST(local:8089)), Property_of_AST(String_of_AST(user),String_of_AST(admin)), Property_of_AST(String_of_AST(password),String_of_AST(qiita))))))
Map(db1 -> Map(indexer -> Elasticsearch, url -> localhost:9200, user -> admin, password -> qiita), db2 -> Map(driver -> Splunk, url -> local:8089, user -> admin, password -> qiita))
(`ー´)b