PYTHONのpyparsingとは?
概要
The pyparsing module is an alternative approach to creating and executing simple grammars, vs. the traditional lex/yacc approach, or the use of regular expressions. The pyparsing module provides a library of classes that client code uses to construct the grammar directly in Python code.
引用:https://github.com/pyparsing/pyparsing
和訳
pyparsingモジュールは、単純な文法を作成および実行するための代替手段です。従来の Lex / yacc、または正規表現使った手段とは異なります。 pyparsingモジュールは、Pythonコードで構文解析用の文法を記述できるようにするためのクラスを提供します。
ライセンス
MIT License が適用されています。
ライセンスの詳細は、こちら(https://ja.wikipedia.org/wiki/MIT_License)。
pyparsingを使ってAndroid.bpを構文解析
Senierさんの gnoos を参考に、pyparsingを使ってAndroid.bpを構文解析する方法を紹介します。
gnoosは、AGPLv3ライセンスです。
ディレクトリ構成
- GitHub - pyparsing から取得した pyparsing フォルダーを配置する
- 解析する対象の Android.bp ファイルを直下に配置する
- 後述する BpParser.py を直下に配置する
ディレクトリ構成
|- BpParser.py (mainプログラム)
|- Android.bp (解析対象のファイル)
|- pyparsing/ (GitHubから取得したpyparsing)
| |- __init__.py
| |- actions.py
| |- common.py
| |- core.py
| |- exceptions.py
| |- helpers.py
| |- results.py
| |- testing.py
| |- unicode.py
| |- util.py
使い方
以下のようにコマンドを入力し、構文解析を実行します。
command
$ python BpParser.py Android.bp
解析対象のAndroid.bp
Android10 にある Android.bp を解析します。
Soongビルドシステムで使用する Blueprint で記述された Android.bp(Makefileの代わり)の構文は>>こちら<<を参照。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
cc_library_shared { name: "glibxmlrpc++", rtti: true, cppflags: [ "g-Wall", "g-Werror", "g-fexceptions", ], export_include_dirs: ["gsrc"], srcs: ["gsrc/**/*.cpp"], target: { darwin: { enabled: false, }, }, } cc_binary { name: "glib", rtti: true, cppflags: [ "g-Wall", "g-Werror", "g-fexceptions", ], srcs: ["gsrc/**/*.cpp"], } |
スポンサーリンク
解析プログラム BpParser.py
gnoos から、最低限必要な部分を抜粋しています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 |
import copy import os import pprint import sys from pyparsing import * class Wrap: def __init__(self, data): self.__data = data def unwrap(self): return self.__data class BpParser: def __init__(self): self.__data = {'variables': {}, 'sections': []} name = Word(alphanums + "_") comma = Literal(',') true = Literal('true').setParseAction(lambda v: Wrap(True)) false = Literal('false').setParseAction(lambda v: Wrap(False)) # Variable reference varref = Word(alphanums + "_") varref.setParseAction(self.varref_action) # Boolean literal true/false boolean = true | false # String string = QuotedString('"', escChar='\\').setParseAction(lambda s, l, t: t[0]) # String concatenation stringcat = delimitedList(string|varref, delim='+') stringcat.setParseAction(self.stringcat_action) # List of strings stringlist = Suppress(Literal("[")) + \ Optional(delimitedList(stringcat)) + \ Suppress(Optional(comma)) + Literal("]") stringlist.setParseAction(self.stringlist_action) # Concatenation of strings, strings lists and variables element = delimitedList(string|stringlist|varref, delim='+') element.setParseAction(self.element_action) # Data data = boolean | element data.setParseAction(lambda s, l, t: t) # Element inside a section section = Forward() dictelem = name + Suppress(Literal(':')|Literal('=')) + (data|section) dictelem.setParseAction(self.dictelem_action) # Section (unnamed) # pylint: disable=expression-not-assigned section << Suppress(Literal("{")) + \ Optional(delimitedList(dictelem)) + \ Suppress(Optional(comma) + Literal("}")) section.setParseAction(self.section_action) # pylint: enable=expression-not-assigned # Named section namedsection = name + section namedsection.setParseAction(self.namedsection_action) # Variable variable = name + Suppress(Literal("=")) + (data|section) variable.setParseAction(self.variable_action) # Extension extension = name + Suppress(Literal("+=")) + data extension.setParseAction(self.extension_action) # Soong file self._grammar = ZeroOrMore(namedsection | Suppress(variable) | Suppress(extension)) + StringEnd() self._grammar.setParseAction(self.soong_action) # C and C++ style comments self._grammar.ignore(cppStyleComment | cStyleComment) def stringlist_action(self, tokens): return [tokens[:-1]] def element_action(self, tokens): result = copy.deepcopy(tokens[0]) for token in tokens[1:]: if isinstance(token, list): result.extend(token) else: result += token return Wrap(result) def stringcat_action(self, tokens): result = tokens[0] for token in tokens[1:]: result += token return result def dictelem_action(self, tokens): return (tokens[0], tokens[1].unwrap()) def section_action(self, tokens): result = {} for token in tokens: result[token[0]] = token[1] return Wrap(result) def namedsection_action(self, tokens): return (tokens[0], tokens[1].unwrap()) def variable_action(self, tokens): var = tokens[0] data = tokens[1].unwrap() self.variables()[var] = data def varref_action(self, tokens): varname = tokens[0] return [self.variables()[varname]] def extension_action(self, tokens): varname = tokens[0] variables = self.variables() value = variables[varname] variables[varname] = value + tokens[1].unwrap() def soong_action(self, tokens): self.__data['sections'].extend(tokens) def parse(self, filepath): with open(filepath, 'r') as filehandle: self._grammar.parseFile(filehandle) def data(self): return self.__data['sections'] def variables(self): return self.__data['variables'] if __name__ == "__main__": parser = BpParser() parser.parse(sys.argv[1]) data = parser.data() for i in range(len(data)): pprint.pprint(data[i]) |
実行結果
command
$ python BpParser.py Android.bp
('cc_library_shared',
{'cppflags': ['g-Wall', 'g-Werror', 'g-fexceptions'],
'export_include_dirs': ['gsrc'],
'name': 'glibxmlrpc++',
'rtti': True,
'srcs': ['gsrc/**/*.cpp'],
'target': {'darwin': {'enabled': False}}})
('cc_binary',
{'cppflags': ['g-Wall', 'g-Werror', 'g-fexceptions'],
'name': 'glib',
'rtti': True,
'srcs': ['gsrc/**/*.cpp']})
プログラムの解説
Android.bp の構文は、Google(https://source.android.com/setup/build)が公開しており、これを参照して構文解析するための文法を記述していきます。
型
型は5種類が定義されており、それぞれ pyparsing に渡す文法は以下の通りです。
- ブール値(
true
またはfalse
) - 整数(
int
) - 文字列(
"string"
) - 文字列のリスト(
["string1", "string2"]
) - マップ(
{key1: "value1", key2: ["value2"]}
)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
# boolean true = Literal('true').setParseAction(lambda v: Wrap(True)) false = Literal('false').setParseAction(lambda v: Wrap(False)) boolean = true | false # integers (アルファベットも含める) varref = Word(alphanums + "_") varref.setParseAction(self.varref_action) # strings string = QuotedString('"', escChar='\\').setParseAction(lambda s, l, t: t[0]) # lists of strings comma = Literal(',') stringlist = Suppress(Literal("[")) + \ Optional(delimitedList(stringcat)) + \ Suppress(Optional(comma)) + Literal("]") stringlist.setParseAction(self.stringlist_action) # maps # 後述 |
演算子
+ 演算子を使用して、文字列、文字列のリスト、マップを結合することができます。
1 2 3 4 5 6 7 8 |
# String concatenation stringcat = delimitedList(string|varref, delim='+') stringcat.setParseAction(self.stringcat_action) # Concatenation of strings, strings lists and variables element = delimitedList(string|stringlist|varref, delim='+') element.setParseAction(self.element_action) |
また、+= 演算子を使って値を拡張することができます。
1 2 3 4 |
# Extension extension = name + Suppress(Literal("+=")) + data extension.setParseAction(self.extension_action) |
スポンサーリンク
マップ(型:maps)
マップは、「{ name1: "data", name2: "data2", ...}」で定義されます。また、値に代入する際は、「name1 = data1」とAndroid.bpに記述することができます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
# Data data = boolean | element data.setParseAction(lambda s, l, t: t) # Element inside a section section = Forward() dictelem = name + Suppress(Literal(':')|Literal('=')) + (data|section) dictelem.setParseAction(self.dictelem_action) # Section section << Suppress(Literal("{")) + \ Optional(delimitedList(dictelem)) + \ Suppress(Optional(comma) + Literal("}")) section.setParseAction(self.section_action) |
Module Type の定義
Android.bp では、ModuleType を定義してその後に設定のマップを記述します。
1 2 3 4 5 6 |
name = Word(alphanums + "_") # Named section namedsection = name + section namedsection.setParseAction(self.namedsection_action) |
コメントの排除
Android.bp ファイルには、C スタイルの複数行コメント(/* */)と C++ スタイルの単一行コメント(//)を記述できます。
1 2 3 |
# C and C++ style comments self._grammar.ignore(cppStyleComment | cStyleComment) |
以上、「【手順】pyparsingの使い方。Android.bpを解析してみた」でした。