fragments.py 16 KB

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