diff options
Diffstat (limited to 'kconfiglib.py')
| -rw-r--r-- | kconfiglib.py | 217 |
1 files changed, 98 insertions, 119 deletions
diff --git a/kconfiglib.py b/kconfiglib.py index 2dd9b46..d0c46c6 100644 --- a/kconfiglib.py +++ b/kconfiglib.py @@ -265,16 +265,16 @@ class Config(object): "top_node", "y", - # These are used during parsing - "_line", - "_lines", + # Parsing-related + "_parsing_kconfigs", + "_reuse_line", + "_file", "_filename", "_linenr", - "_file_len", "_filestack", + "_line", "_tokens", "_tokens_i", - "_tokens_len", "_has_tokens", ) @@ -351,14 +351,16 @@ class Config(object): sym = self.const_syms[nmy] sym.rev_dep = sym.weak_rev_dep = sym.direct_dep = self.n - self.modules = self._lookup_sym("MODULES", False) + self._parsing_kconfigs = True + + self.modules = self._lookup_sym("MODULES") self.defconfig_list = None # Predefined symbol. DEFCONFIG_LIST uses this. - uname_sym = self._lookup_const_sym("UNAME_RELEASE", False) + uname_sym = self._lookup_const_sym("UNAME_RELEASE") uname_sym._type = STRING uname_sym.defaults.append( - (self._lookup_const_sym(platform.uname()[2], False), + (self._lookup_const_sym(platform.uname()[2]), self.y)) # env_var doubles as the SYMBOL_AUTO flag from the C implementation, so # just set it to something. The naming breaks a bit here, but it's @@ -378,8 +380,8 @@ class Config(object): # Parse the Kconfig files + self._reuse_line = False self._has_tokens = False - self._tokens_i = 0 self._filestack = [] @@ -388,10 +390,11 @@ class Config(object): self.top_node, # parent self.y, # visible_if_deps self.top_node) # prev_node - self.top_node.list = self.top_node.next self.top_node.next = None + self._parsing_kconfigs = False + # Do various post-processing of the menu tree, e.g. to finalize # choices, flatten ifs, and implicitly create menus _finalize_tree(self.top_node) @@ -546,9 +549,13 @@ class Config(object): the C tools). m is rewritten to 'm && MODULES'. """ # TODO: explain - self._line = s self._filename = None - self._tokenize(True) + + self._line = "if " + s + self._tokenize() + self._line = s + del self._tokens[0] + return eval_expr(self._parse_expr(True)) def unset_values(self): @@ -655,55 +662,43 @@ class Config(object): # Kconfig parsing # - def _tokenize(self, for_eval_string): + def _tokenize(self): """ Parses Config._line, putting the tokens in Config._tokens. Registers any new symbols encountered (via _lookup_sym()). Tries to be reasonably speedy by processing chunks of text via regexes and string operations where possible. This is a hotspot during parsing. - - for_eval_string: - True when parsing an expression for a call to Config.eval_string(), - in which case we should not treat the first token specially nor - register new symbols. """ s = self._line # Tricky implementation detail: While parsing a token, 'token' refers - # to the previous token. See _NOT_REF for why this is needed. + # to the previous token. See _STRING_LEX for why this is needed. - if for_eval_string: - self._tokens = [] - token = None - # The current index in the string being tokenized - i = 0 + # See comment at _initial_token_re_match definition + initial_token_match = _initial_token_re_match(s) + if not initial_token_match: + self._tokens = (None,) + self._tokens_i = -1 + return - else: - # See comment at _initial_token_re_match definition - initial_token_match = _initial_token_re_match(s) - if not initial_token_match: - self._tokens_len = 0 - return - - keyword = _get_keyword(initial_token_match.group(1)) - - if keyword == _T_HELP: - # Avoid junk after "help", e.g. "---", being registered as a - # symbol - self._tokens = (_T_HELP,) - self._tokens_len = 1 # TODO: why is this needed? - self._tokens_i = 0 - return - - if keyword is None: - self._parse_error("expected keyword as first token") - - token = keyword - self._tokens = [keyword] - # The current index in the string being tokenized - i = initial_token_match.end() + keyword = _get_keyword(initial_token_match.group(1)) + + if keyword == _T_HELP: + # Avoid junk after "help", e.g. "---", being registered as a + # symbol + self._tokens = (_T_HELP, None) + self._tokens_i = -1 + return + + if keyword is None: + self._parse_error("expected keyword as first token") + + token = keyword + self._tokens = [keyword] + # The current index in the string being tokenized + i = initial_token_match.end() # Main tokenization loop (for tokens past the first one) while i < len(s): @@ -732,9 +727,9 @@ class Config(object): # ...except we translate n, m, and y into the # corresponding constant symbols, like the C # implementation - token = self._lookup_const_sym(name, for_eval_string) + token = self._lookup_const_sym(name) else: - token = self._lookup_sym(name, for_eval_string) + token = self._lookup_sym(name) else: # It's a case of missing quotes. For example, the @@ -802,15 +797,12 @@ class Config(object): i += 1 # This is the only place where we don't survive with a - # single token of lookback, hence the kludge: - # 'option env="FOO"' does not refer to a constant symbol - # named "FOO". - token = \ - val \ - if token in _STRING_LEX or \ - (not for_eval_string and \ - self._tokens[0] == _T_OPTION) else \ - self._lookup_const_sym(val, for_eval_string) + # single token of lookback: 'option env="FOO"' does not + # refer to a constant symbol named "FOO". + token = val \ + if token in _STRING_LEX or \ + self._tokens[0] == _T_OPTION else \ + self._lookup_const_sym(val) elif c == "&": # Invalid characters are ignored @@ -873,23 +865,20 @@ class Config(object): self._tokens.append(token) - self._tokens_i = 0 - self._tokens_len = len(self._tokens) + # TODO: say something about the None-termination + + self._tokens.append(None) + self._tokens_i = -1 def _next_token(self): - if self._tokens_i >= self._tokens_len: - return None - token = self._tokens[self._tokens_i] self._tokens_i += 1 - return token + return self._tokens[self._tokens_i] def _peek_token(self): - return None if self._tokens_i >= self._tokens_len \ - else self._tokens[self._tokens_i] + return self._tokens[self._tokens_i + 1] def _check_token(self, token): - if self._tokens_i < self._tokens_len and \ - self._tokens[self._tokens_i] == token: + if self._tokens[self._tokens_i + 1] == token: self._tokens_i += 1 return True return False @@ -897,7 +886,7 @@ class Config(object): def _enter_file_empty_stack(self, filename): try: # TODO: comment - f = self._open(filename) + self._file = self._open(filename) except IOError as e: # Extend the error message a bit in this case raise IOError( @@ -910,40 +899,33 @@ class Config(object): self._filename = filename self._linenr = 0 - with f: - self._lines = f.readlines() - self._file_len = len(self._lines) def _enter_file(self, filename): - self._filestack.append((self._filename, self._linenr, self._lines, - self._file_len)) + self._filestack.append((self._file, self._filename, self._linenr)) self._enter_file_empty_stack(filename) def _leave_file(self): - self._filename, self._linenr, self._lines, self._file_len = \ - self._filestack.pop() + self._file.close() + self._file, self._filename, self._linenr = self._filestack.pop() def _next_line(self): - if self._linenr >= self._file_len: - return False + # This provides a single line of "unget" if _reuse_line is set to True + if not self._reuse_line: + self._line = self._file.readline() + self._linenr += 1 - line = self._lines[self._linenr] - self._linenr += 1 + self._reuse_line = False - while line.endswith("\\\n"): - line = line[:-2] + self._lines[self._linenr] + while self._line.endswith("\\\n"): + # TODO: Can hang if the file ends with a backslash + self._line = self._line[:-2] + self._file.readline() self._linenr += 1 - self._line = line - return True + return self._line def _next_line_no_join(self): - if self._linenr >= self._file_len: - return None - - self._line = self._lines[self._linenr] + self._line = self._file.readline() self._linenr += 1 - return self._line def _parse_block(self, end_token, parent, visible_if_deps, prev_node): @@ -964,10 +946,6 @@ class Config(object): 'visible if' dependencies from enclosing menus. Propagated to Symbol and Choice prompts. - prev_line: - A "cached" (line, tokens) tuple from having parsed a line earlier - that we realized belonged to a different construct. - prev_node: The previous menu node. New nodes will be added after this one (by modifying its 'next' pointer). @@ -995,7 +973,7 @@ class Config(object): prev_node.next = None return prev_node - self._tokenize(False) + self._tokenize() self._has_tokens = False @@ -1187,7 +1165,7 @@ class Config(object): if not self._next_line(): break - self._tokenize(False) + self._tokenize() t0 = self._next_token() if t0 is None: @@ -1205,10 +1183,10 @@ class Config(object): while 1: line = self._next_line_no_join() - if line is None or not line.isspace(): + if not line or not line.isspace(): break - if line is None: + if not line: node.help = "" break @@ -1217,7 +1195,7 @@ class Config(object): # If the first non-empty lines has zero indent, there is no # help text node.help = "" - self._linenr -= 1 # Unget the line + self._reuse_line = True # "Unget" the line break # The help text goes on till the first non-empty line with less @@ -1226,16 +1204,16 @@ class Config(object): help_lines = [_deindent(line, indent).rstrip()] while 1: line = self._next_line_no_join() - if line is None or \ + if not line or \ (not line.isspace() and _indentation(line) < indent): node.help = "\n".join(help_lines).rstrip() + "\n" break help_lines.append(_deindent(line, indent).rstrip()) - if line is None: + if not line: break - self._linenr -= 1 # Unget the line + self._reuse_line = True # "Unget" the line elif t0 == _T_SELECT: if not isinstance(node.item, Symbol): @@ -1251,6 +1229,7 @@ class Config(object): elif t0 in (_T_BOOL, _T_TRISTATE, _T_INT, _T_HEX, _T_STRING): node.item._type = _TOKEN_TO_TYPE[t0] + if self._peek_token() is not None: prompt = (self._next_token(), self._parse_cond()) @@ -1259,6 +1238,7 @@ class Config(object): elif t0 in (_T_DEF_BOOL, _T_DEF_TRISTATE): node.item._type = _TOKEN_TO_TYPE[t0] + defaults.append((self._parse_expr(False), self._parse_cond())) elif t0 == _T_PROMPT: @@ -1292,8 +1272,7 @@ class Config(object): self._filename, self._linenr) else: defaults.append( - (self._lookup_const_sym(os.environ[env_var], - False), + (self._lookup_const_sym(os.environ[env_var]), self.y)) elif self._check_token(_T_DEFCONFIG_LIST): @@ -1347,7 +1326,7 @@ class Config(object): node.item.is_optional = True else: - self._tokens_i = 0 + self._tokens_i = -1 self._has_tokens = True break @@ -1506,7 +1485,7 @@ class Config(object): # Symbol lookup # - def _lookup_sym(self, name, for_eval_string): + def _lookup_sym(self, name): """ Fetches the symbol 'name' from the symbol table, creating and registering it if it does not exist. TODO If 'for_eval_string' is True, @@ -1522,14 +1501,14 @@ class Config(object): sym.is_constant = False sym.rev_dep = sym.weak_rev_dep = sym.direct_dep = self.n - if for_eval_string: - self._warn("no symbol {} in configuration".format(name)) - else: + if self._parsing_kconfigs: self.syms[name] = sym + else: + self._warn("no symbol {} in configuration".format(name)) return sym - def _lookup_const_sym(self, name, for_eval_string): + def _lookup_const_sym(self, name): """ TODO: say something """ @@ -1542,7 +1521,7 @@ class Config(object): sym.is_constant = True sym.rev_dep = sym.weak_rev_dep = sym.direct_dep = self.n - if not for_eval_string: + if self._parsing_kconfigs: self.const_syms[name] = sym return sym @@ -1556,14 +1535,6 @@ class Config(object): Returns a list containing all .config strings for the configuration. """ - config_strings = [] - add_fn = config_strings.append - - node = self.top_node.list - if node is None: - # Empty configuration - return config_strings - # Symbol._already_written is set to True when a symbol config string is # fetched, so that symbols defined in multiple locations only get one # .config entry. We reset it prior to writing out a new .config. It @@ -1577,6 +1548,14 @@ class Config(object): for sym in self.defined_syms: sym._already_written = False + node = self.top_node.list + if node is None: + # Empty configuration + return [] + + config_strings = [] + add_fn = config_strings.append + while 1: if isinstance(node.item, Symbol): sym = node.item |
