summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorUlf Magnusson <ulfalizer@gmail.com>2018-09-04 18:05:28 +0200
committerUlf Magnusson <ulfalizer@gmail.com>2018-09-04 20:49:21 +0200
commita092257a49ed7850913cf53e474f4c8dd175c94b (patch)
tree1024946e748ce2801b03553e78eb9616b0d550da
parentef5358e8b8174716470b00691fba9946b7d307ce (diff)
Allow macro expansion within symbol names
The C implementation supports this (though it's undocumented, and unused to far). This can be used e.g. to dynamically instatiate symbols from template files: Kconfig.template: config $(subsys)_LOG bool "Enable logging for $(subsys)" depends on $(subsys)_HAS_LOG ... other stuff dependent on $(subsys) Elsewhere: subsys = FOO source "Kconfig.template" subsys = BAR source "Kconfig.template" Pretty sure this can easily be abused, but it should be supported at least.
-rw-r--r--kconfiglib.py80
-rw-r--r--tests/Kpreprocess6
-rw-r--r--testsuite.py15
3 files changed, 72 insertions, 29 deletions
diff --git a/kconfiglib.py b/kconfiglib.py
index db61650..b6e534c 100644
--- a/kconfiglib.py
+++ b/kconfiglib.py
@@ -1771,9 +1771,6 @@ class Kconfig(object):
if match:
# We have an identifier or keyword
- # Jump past it
- i = match.end()
-
# Check what it is. lookup_sym() will take care of allocating
# new symbols for us the first time we see them. Note that
# 'token' still refers to the previous token.
@@ -1783,11 +1780,20 @@ class Kconfig(object):
if keyword:
# It's a keyword
token = keyword
+ # Jump past it
+ i = match.end()
elif token not in _STRING_LEX:
# It's a non-const symbol, except we translate n, m, and y
# into the corresponding constant symbols, like the C
# implementation
+
+ if "$" in name:
+ # Macro expansion within symbol name
+ name, s, i = self._expand_name(s, i)
+ else:
+ i = match.end()
+
token = self.const_syms[name] \
if name in ("n", "m", "y") else \
self._lookup_sym(name)
@@ -1803,6 +1809,7 @@ class Kconfig(object):
#
# endmenu
token = name
+ i = match.end()
else:
# Neither a keyword nor a non-const symbol (except
@@ -1813,7 +1820,7 @@ class Kconfig(object):
c = s[i]
if c in "\"'":
- s, end_i = self._expand_str(s, i, c)
+ s, end_i = self._expand_str(s, i)
# os.path.expandvars() and the $UNAME_RELEASE replace() is
# a backwards compatibility hack, which should be
@@ -1864,23 +1871,6 @@ class Kconfig(object):
token = _T_CLOSE_PAREN
i += 1
- elif s.startswith("$(", i):
- s, end_i = self._expand_macro(s, i, ())
- val = s[i:end_i]
- # isspace() is False for empty strings
- if not val.strip():
- # Avoid creating a Kconfig symbol with a blank name.
- # It's almost guaranteed to be an error.
- self._parse_error("macro expanded to blank string")
- i = end_i
-
- # Compatibility with what the C implementation does. Might
- # be unexpected that you can reference non-constant symbols
- # this way though...
- token = self.const_syms[val] \
- if val in ("n", "m", "y") else \
- self._lookup_sym(val)
-
elif c == "#":
break
@@ -2083,13 +2073,48 @@ class Kconfig(object):
s, i = self._expand_macro(s, i, args)
return s
- def _expand_str(self, s, i, quote):
+ def _expand_name(self, s, i):
+ # Expands a symbol name starting at index 'i' in 's'.
+ #
+ # Returns the expanded name, the expanded 's' (including the part
+ # before the name), and the index of the next token after the name.
+
+ s, end_i = self._expand_name_iter(s, i)
+ name = s[i:end_i]
+ # isspace() is False for empty strings
+ if not name.strip():
+ # Avoid creating a Kconfig symbol with a blank name. It's almost
+ # guaranteed to be an error.
+ self._parse_error("macro expanded to blank string")
+
+ # Skip trailing whitespace
+ while end_i < len(s) and s[end_i].isspace():
+ end_i += 1
+
+ return name, s, end_i
+
+ def _expand_name_iter(self, s, i):
+ # Expands a symbol name starting at index 'i' in 's'.
+ #
+ # Returns the expanded 's' (including the part before the name), the
+ # index of the first character after the expanded string in 's'.
+
+ while 1:
+ match = _name_special_search(s, i)
+
+ if match.group() == "$(":
+ s, i = self._expand_macro(s, match.start(), ())
+ else:
+ return (s, match.start())
+
+ def _expand_str(self, s, i):
# Expands a quoted string starting at index 'i' in 's'. Handles both
# backslash escapes and macro expansion.
#
# Returns the expanded 's' (including the part before the string) and
# the index of the first character after the expanded string in 's'.
+ quote = s[i]
i += 1 # Skip over initial "/'
while 1:
match = _string_special_search(s, i)
@@ -6063,12 +6088,13 @@ def _re_search(regex):
#
# This regex will also fail to match for empty lines and comment lines.
#
-# '$' is included to detect a variable assignment left-hand side with a $ in it
-# (which might be from a macro expansion).
+# '$' is included to detect preprocessor variable assignments with macro
+# expansions in the left-hand side.
_command_match = _re_match(r"\s*([$A-Za-z0-9_-]+)\s*")
# An identifier/keyword after the first token. Also eats trailing whitespace.
-_id_keyword_match = _re_match(r"([A-Za-z0-9_/.-]+)\s*")
+# '$' is included to detect identifiers containing macro expansions.
+_id_keyword_match = _re_match(r"([$A-Za-z0-9_/.-]+)\s*")
# A fragment in the left-hand side of a preprocessor variable assignment. These
# are the portions between macro expansions ($(foo)). Macros are supported in
@@ -6085,6 +6111,10 @@ _macro_special_search = _re_search(r"\)|,|\$\(")
# Special characters/strings while expanding a string (quotes, '\', and '$(')
_string_special_search = _re_search(r'"|\'|\\|\$\(')
+# Special characters/strings while expanding a symbol name. Also includes
+# end-of-line, in case the macro is the last thing on the line.
+_name_special_search = _re_search(r'[^$A-Za-z0-9_/.-]|\$\(|$')
+
# A valid right-hand side for an assignment to a string symbol in a .config
# file, including escaped characters. Extracts the contents.
_conf_string_match = _re_match(r'"((?:[^\\"]|\\.)*)"')
diff --git a/tests/Kpreprocess b/tests/Kpreprocess
index 2ebf6e6..e30b389 100644
--- a/tests/Kpreprocess
+++ b/tests/Kpreprocess
@@ -67,11 +67,17 @@ special-chars-fn-res = $(fn,$(comma)$(dollar)$(left-paren)foo$(right-paren))
qaz = QAZ
echo = $(1)
+ignore-first = $(2)
config PRINT_ME
string "$(ENV_1)" if ($(echo,FOO) && $(echo,BAR)) || !$(echo,BAZ) || !(($(qaz)))
default "$(echo,"foo")" if "foo $(echo,"bar") baz" = "$(undefined)"
+# Expansion within a symbol token, with deliberate sloppiness
+config PRINT_$(ignore-first, ,ME)_TOO
+ bool "foo"
+ default FOO$(ignore-first, ,BAR)BAZ$(qaz) if $(qaz)&&$(qaz)FOO&&x$(ignore-first, ,xx)
+
# Recursive expansion (throws an exception)
diff --git a/testsuite.py b/testsuite.py
index f7f75dd..eb305e0 100644
--- a/testsuite.py
+++ b/testsuite.py
@@ -2398,6 +2398,13 @@ config PRINT_ME
default "\"foo\"" if "foo \"bar\" baz" = ""
""")
+ verify_str(c.syms["PRINT_ME_TOO"], r"""
+config PRINT_ME_TOO
+ bool
+ prompt "foo"
+ default FOOBARBAZQAZ if QAZ && QAZFOO && xxx
+""")
+
def verify_recursive(name):
try:
c.variables[name].expanded_value
@@ -2427,8 +2434,8 @@ config PRINT_ME
verify_variable("shell-stderr-res", "", "", False)
verify_variable("location-res",
- "Kconfiglib/tests/Kpreprocess:119",
- "Kconfiglib/tests/Kpreprocess:119",
+ "Kconfiglib/tests/Kpreprocess:125",
+ "Kconfiglib/tests/Kpreprocess:125",
False)
verify_variable("warning-res", "", "", False)
@@ -2447,8 +2454,8 @@ config PRINT_ME
# Check that the expected warnings were generated
verify_equal(c.warnings, [
- "Kconfiglib/tests/Kpreprocess:116: warning: 'echo message on stderr >&2' wrote to stderr: message on stderr",
- "Kconfiglib/tests/Kpreprocess:124: warning: a warning"
+ "Kconfiglib/tests/Kpreprocess:122: warning: 'echo message on stderr >&2' wrote to stderr: message on stderr",
+ "Kconfiglib/tests/Kpreprocess:130: warning: a warning"
])