fragments.py 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425
  1. #
  2. # Copyright 2018-2019 Espressif Systems (Shanghai) PTE LTD
  3. #
  4. # Licensed under the Apache License, Version 2.0 (the "License");
  5. # you may not use this file except in compliance with the License.
  6. # You may obtain a copy of the License at
  7. #
  8. # http://www.apache.org/licenses/LICENSE-2.0
  9. #
  10. # Unless required by applicable law or agreed to in writing, software
  11. # distributed under the License is distributed on an "AS IS" BASIS,
  12. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. # See the License for the specific language governing permissions and
  14. # limitations under the License.
  15. #
  16. import os
  17. import re
  18. from sdkconfig import SDKConfig
  19. from pyparsing import OneOrMore
  20. from pyparsing import restOfLine
  21. from pyparsing import alphanums
  22. from pyparsing import Word
  23. from pyparsing import alphas
  24. from pyparsing import ParseFatalException
  25. from pyparsing import Suppress
  26. from pyparsing import Group
  27. from pyparsing import Literal
  28. from pyparsing import ZeroOrMore
  29. from pyparsing import Optional
  30. from pyparsing import originalTextFor
  31. from pyparsing import Forward
  32. from pyparsing import indentedBlock
  33. from collections import namedtuple
  34. import abc
  35. KeyGrammar = namedtuple("KeyGrammar", "grammar min max required")
  36. class FragmentFile():
  37. """
  38. Fragment file internal representation. Parses and stores instances of the fragment definitions
  39. contained within the file.
  40. """
  41. def __init__(self, fragment_file, sdkconfig):
  42. try:
  43. fragment_file = open(fragment_file, "r")
  44. except TypeError:
  45. pass
  46. path = os.path.realpath(fragment_file.name)
  47. indent_stack = [1]
  48. class parse_ctx:
  49. fragment = None # current fragment
  50. key = "" # current key
  51. keys = list() # list of keys parsed
  52. key_grammar = None # current key grammar
  53. @staticmethod
  54. def reset():
  55. parse_ctx.fragment_instance = None
  56. parse_ctx.key = ""
  57. parse_ctx.keys = list()
  58. parse_ctx.key_grammar = None
  59. def fragment_type_parse_action(toks):
  60. parse_ctx.reset()
  61. parse_ctx.fragment = FRAGMENT_TYPES[toks[0]]() # create instance of the fragment
  62. return None
  63. def expand_conditionals(toks, stmts):
  64. try:
  65. stmt = toks["value"]
  66. stmts.append(stmt)
  67. except KeyError:
  68. try:
  69. conditions = toks["conditional"]
  70. for condition in conditions:
  71. try:
  72. _toks = condition[1]
  73. _cond = condition[0]
  74. if sdkconfig.evaluate_expression(_cond):
  75. expand_conditionals(_toks, stmts)
  76. break
  77. except IndexError:
  78. expand_conditionals(condition[0], stmts)
  79. except KeyError:
  80. for tok in toks:
  81. expand_conditionals(tok, stmts)
  82. def key_body_parsed(pstr, loc, toks):
  83. stmts = list()
  84. expand_conditionals(toks, stmts)
  85. if parse_ctx.key_grammar.min and len(stmts) < parse_ctx.key_grammar.min:
  86. raise ParseFatalException(pstr, loc, "fragment requires at least %d values for key '%s'" %
  87. (parse_ctx.key_grammar.min, parse_ctx.key))
  88. if parse_ctx.key_grammar.max and len(stmts) > parse_ctx.key_grammar.max:
  89. raise ParseFatalException(pstr, loc, "fragment requires at most %d values for key '%s'" %
  90. (parse_ctx.key_grammar.max, parse_ctx.key))
  91. try:
  92. parse_ctx.fragment.set_key_value(parse_ctx.key, stmts)
  93. except Exception as e:
  94. raise ParseFatalException(pstr, loc, "unable to add key '%s'; %s" % (parse_ctx.key, e.message))
  95. return None
  96. key = Word(alphanums + "_") + Suppress(":")
  97. key_stmt = Forward()
  98. condition_block = indentedBlock(key_stmt, indent_stack)
  99. key_stmts = OneOrMore(condition_block)
  100. key_body = Suppress(key) + key_stmts
  101. key_body.setParseAction(key_body_parsed)
  102. condition = originalTextFor(SDKConfig.get_expression_grammar()).setResultsName("condition")
  103. if_condition = Group(Suppress("if") + condition + Suppress(":") + condition_block)
  104. elif_condition = Group(Suppress("elif") + condition + Suppress(":") + condition_block)
  105. else_condition = Group(Suppress("else") + Suppress(":") + condition_block)
  106. conditional = (if_condition + Optional(OneOrMore(elif_condition)) + Optional(else_condition)).setResultsName("conditional")
  107. def key_parse_action(pstr, loc, toks):
  108. key = toks[0]
  109. if key in parse_ctx.keys:
  110. raise ParseFatalException(pstr, loc, "duplicate key '%s' value definition" % parse_ctx.key)
  111. parse_ctx.key = key
  112. parse_ctx.keys.append(key)
  113. try:
  114. parse_ctx.key_grammar = parse_ctx.fragment.get_key_grammars()[key]
  115. key_grammar = parse_ctx.key_grammar.grammar
  116. except KeyError:
  117. raise ParseFatalException(pstr, loc, "key '%s' is not supported by fragment" % key)
  118. except Exception as e:
  119. raise ParseFatalException(pstr, loc, "unable to parse key '%s'; %s" % (key, e.message))
  120. key_stmt << (conditional | Group(key_grammar).setResultsName("value"))
  121. return None
  122. def name_parse_action(pstr, loc, toks):
  123. parse_ctx.fragment.name = toks[0]
  124. key.setParseAction(key_parse_action)
  125. ftype = Word(alphas).setParseAction(fragment_type_parse_action)
  126. fid = Suppress(":") + Word(alphanums + "_.").setResultsName("name")
  127. fid.setParseAction(name_parse_action)
  128. header = Suppress("[") + ftype + fid + Suppress("]")
  129. def fragment_parse_action(pstr, loc, toks):
  130. key_grammars = parse_ctx.fragment.get_key_grammars()
  131. required_keys = set([k for (k,v) in key_grammars.items() if v.required])
  132. present_keys = required_keys.intersection(set(parse_ctx.keys))
  133. if present_keys != required_keys:
  134. raise ParseFatalException(pstr, loc, "required keys %s for fragment not found" %
  135. list(required_keys - present_keys))
  136. return parse_ctx.fragment
  137. fragment_stmt = Forward()
  138. fragment_block = indentedBlock(fragment_stmt, indent_stack)
  139. fragment_if_condition = Group(Suppress("if") + condition + Suppress(":") + fragment_block)
  140. fragment_elif_condition = Group(Suppress("elif") + condition + Suppress(":") + fragment_block)
  141. fragment_else_condition = Group(Suppress("else") + Suppress(":") + fragment_block)
  142. fragment_conditional = (fragment_if_condition + Optional(OneOrMore(fragment_elif_condition)) +
  143. Optional(fragment_else_condition)).setResultsName("conditional")
  144. fragment = (header + OneOrMore(indentedBlock(key_body, indent_stack, False))).setResultsName("value")
  145. fragment.setParseAction(fragment_parse_action)
  146. fragment.ignore("#" + restOfLine)
  147. deprecated_mapping = DeprecatedMapping.get_fragment_grammar(sdkconfig, fragment_file.name).setResultsName("value")
  148. fragment_stmt << (Group(deprecated_mapping) | Group(fragment) | Group(fragment_conditional))
  149. def fragment_stmt_parsed(pstr, loc, toks):
  150. stmts = list()
  151. expand_conditionals(toks, stmts)
  152. return stmts
  153. parser = ZeroOrMore(fragment_stmt)
  154. parser.setParseAction(fragment_stmt_parsed)
  155. self.fragments = parser.parseFile(fragment_file, parseAll=True)
  156. for fragment in self.fragments:
  157. fragment.path = path
  158. class Fragment():
  159. __metaclass__ = abc.ABCMeta
  160. """
  161. Encapsulates a fragment as defined in the generator syntax. Sets values common to all fragment and performs processing
  162. such as checking the validity of the fragment name and getting the entry values.
  163. """
  164. IDENTIFIER = Word(alphas + "_", alphanums + "_")
  165. ENTITY = Word(alphanums + ".-_$")
  166. @abc.abstractmethod
  167. def set_key_value(self, key, parse_results):
  168. pass
  169. @abc.abstractmethod
  170. def get_key_grammars(self):
  171. pass
  172. class Sections(Fragment):
  173. grammars = {
  174. "entries": KeyGrammar(Word(alphanums + "+.").setResultsName("section"), 1, None, True)
  175. }
  176. """
  177. Utility function that returns a list of sections given a sections fragment entry,
  178. with the '+' notation and symbol concatenation handled automatically.
  179. """
  180. @staticmethod
  181. def get_section_data_from_entry(sections_entry, symbol=None):
  182. if not symbol:
  183. sections = list()
  184. sections.append(sections_entry.replace("+", ""))
  185. sections.append(sections_entry.replace("+", ".*"))
  186. return sections
  187. else:
  188. if sections_entry.endswith("+"):
  189. section = sections_entry.replace("+", ".*")
  190. expansion = section.replace(".*", "." + symbol)
  191. return (section, expansion)
  192. else:
  193. return (sections_entry, None)
  194. def set_key_value(self, key, parse_results):
  195. if key == "entries":
  196. self.entries = set()
  197. for result in parse_results:
  198. self.entries.add(result["section"])
  199. def get_key_grammars(self):
  200. return self.__class__.grammars
  201. class Scheme(Fragment):
  202. """
  203. Encapsulates a scheme fragment, which defines what target input sections are placed under.
  204. """
  205. grammars = {
  206. "entries": KeyGrammar(Fragment.IDENTIFIER.setResultsName("sections") + Suppress("->") +
  207. Fragment.IDENTIFIER.setResultsName("target"), 1, None, True)
  208. }
  209. def set_key_value(self, key, parse_results):
  210. if key == "entries":
  211. self.entries = set()
  212. for result in parse_results:
  213. self.entries.add((result["sections"], result["target"]))
  214. def get_key_grammars(self):
  215. return self.__class__.grammars
  216. class Mapping(Fragment):
  217. """
  218. Encapsulates a mapping fragment, which defines what targets the input sections of mappable entties are placed under.
  219. """
  220. MAPPING_ALL_OBJECTS = "*"
  221. def __init__(self):
  222. Fragment.__init__(self)
  223. self.entries = set()
  224. self.deprecated = False
  225. def set_key_value(self, key, parse_results):
  226. if key == "archive":
  227. self.archive = parse_results[0]["archive"]
  228. elif key == "entries":
  229. for result in parse_results:
  230. obj = None
  231. symbol = None
  232. scheme = None
  233. try:
  234. obj = result["object"]
  235. except KeyError:
  236. pass
  237. try:
  238. symbol = result["symbol"]
  239. except KeyError:
  240. pass
  241. try:
  242. scheme = result["scheme"]
  243. except KeyError:
  244. pass
  245. self.entries.add((obj, symbol, scheme))
  246. def get_key_grammars(self):
  247. # There are three possible patterns for mapping entries:
  248. # obj:symbol (scheme)
  249. # obj (scheme)
  250. # * (scheme)
  251. obj = Fragment.ENTITY.setResultsName("object")
  252. symbol = Suppress(":") + Fragment.IDENTIFIER.setResultsName("symbol")
  253. scheme = Suppress("(") + Fragment.IDENTIFIER.setResultsName("scheme") + Suppress(")")
  254. pattern1 = obj + symbol + scheme
  255. pattern2 = obj + scheme
  256. pattern3 = Literal(Mapping.MAPPING_ALL_OBJECTS).setResultsName("object") + scheme
  257. entry = pattern1 | pattern2 | pattern3
  258. grammars = {
  259. "archive": KeyGrammar(Fragment.ENTITY.setResultsName("archive"), 1, 1, True),
  260. "entries": KeyGrammar(entry, 0, None, True)
  261. }
  262. return grammars
  263. class DeprecatedMapping():
  264. """
  265. Encapsulates a mapping fragment, which defines what targets the input sections of mappable entties are placed under.
  266. """
  267. # Name of the default condition entry
  268. DEFAULT_CONDITION = "default"
  269. MAPPING_ALL_OBJECTS = "*"
  270. @staticmethod
  271. def get_fragment_grammar(sdkconfig, fragment_file):
  272. # Match header [mapping]
  273. header = Suppress("[") + Suppress("mapping") + Suppress("]")
  274. # There are three possible patterns for mapping entries:
  275. # obj:symbol (scheme)
  276. # obj (scheme)
  277. # * (scheme)
  278. obj = Fragment.ENTITY.setResultsName("object")
  279. symbol = Suppress(":") + Fragment.IDENTIFIER.setResultsName("symbol")
  280. scheme = Suppress("(") + Fragment.IDENTIFIER.setResultsName("scheme") + Suppress(")")
  281. pattern1 = Group(obj + symbol + scheme)
  282. pattern2 = Group(obj + scheme)
  283. pattern3 = Group(Literal(Mapping.MAPPING_ALL_OBJECTS).setResultsName("object") + scheme)
  284. mapping_entry = pattern1 | pattern2 | pattern3
  285. # To simplify parsing, classify groups of condition-mapping entry into two types: normal and default
  286. # A normal grouping is one with a non-default condition. The default grouping is one which contains the
  287. # default condition
  288. mapping_entries = Group(ZeroOrMore(mapping_entry)).setResultsName("mappings")
  289. normal_condition = Suppress(":") + originalTextFor(SDKConfig.get_expression_grammar())
  290. default_condition = Optional(Suppress(":") + Literal(DeprecatedMapping.DEFAULT_CONDITION))
  291. normal_group = Group(normal_condition.setResultsName("condition") + mapping_entries)
  292. default_group = Group(default_condition + mapping_entries).setResultsName("default_group")
  293. normal_groups = Group(ZeroOrMore(normal_group)).setResultsName("normal_groups")
  294. # Any mapping fragment definition can have zero or more normal group and only one default group as a last entry.
  295. archive = Suppress("archive") + Suppress(":") + Fragment.ENTITY.setResultsName("archive")
  296. entries = Suppress("entries") + Suppress(":") + (normal_groups + default_group).setResultsName("entries")
  297. mapping = Group(header + archive + entries)
  298. mapping.ignore("#" + restOfLine)
  299. def parsed_deprecated_mapping(pstr, loc, toks):
  300. fragment = Mapping()
  301. fragment.archive = toks[0].archive
  302. fragment.name = re.sub(r"[^0-9a-zA-Z]+", "_", fragment.archive)
  303. fragment.deprecated = True
  304. fragment.entries = set()
  305. condition_true = False
  306. for entries in toks[0].entries[0]:
  307. condition = next(iter(entries.condition.asList())).strip()
  308. condition_val = sdkconfig.evaluate_expression(condition)
  309. if condition_val:
  310. for entry in entries[1]:
  311. fragment.entries.add((entry.object, None if entry.symbol == '' else entry.symbol, entry.scheme))
  312. condition_true = True
  313. break
  314. if not fragment.entries and not condition_true:
  315. try:
  316. entries = toks[0].entries[1][1]
  317. except IndexError:
  318. entries = toks[0].entries[1][0]
  319. for entry in entries:
  320. fragment.entries.add((entry.object, None if entry.symbol == '' else entry.symbol, entry.scheme))
  321. if not fragment.entries:
  322. fragment.entries.add(("*", None, "default"))
  323. dep_warning = str(ParseFatalException(pstr, loc,
  324. "Warning: Deprecated old-style mapping fragment parsed in file %s." % fragment_file))
  325. print(dep_warning)
  326. return fragment
  327. mapping.setParseAction(parsed_deprecated_mapping)
  328. return mapping
  329. FRAGMENT_TYPES = {
  330. "sections": Sections,
  331. "scheme": Scheme,
  332. "mapping": Mapping
  333. }