Skip to contentSkip to conversation

kconfiglib: update to the latest version

PATCH Superseded Ulf Magnusson 2 participants 4 messages Aug 13, 2015 – Aug 28, 2015

lore
Share & tools
lore
#0 lore raw

PATCH v2 1/1 kconfiglib: update to the latest version

Changes to v1:
- Based on a newer Kconfiglib version. Fixes a problem with line numbers and continuation lines.
- The patch should hopefully apply now. :P

Ulf Magnusson (1):
kconfiglib: update to the latest version

 tools/buildman/kconfiglib.py | 4226 +++++++++++++++++++-----------------------
 1 file changed, 1940 insertions(+), 2286 deletions(-)
Signature · 1 line
2.1.4
#1 lore raw

PATCH v2 1/1 kconfiglib: update to the latest version

Corresponds to ba71a0e (Fix _parse_block() 'parent' documentation re. ifs.) from upstream, just adding the SPDX tag.

Has performance improvements, code cleanup, Python 3 support, and various small fixes, including the following:

- Unset user values when loading a zero-byte .config. (5e54e2c)
- Ignore indented .config assignments. (f8a7510)
- Do not require $srctree to be set for non-kernel projects. (d56e9c1)
- Report correct locations in the presence of continuation lines.

    (0cebc87)
---
 tools/buildman/kconfiglib.py | 4226 +++++++++++++++++++-----------------------
 1 file changed, 1940 insertions(+), 2286 deletions(-)
tools/buildman/kconfiglib.py +1940 -2286
index c662b64..7a47be9 100644
@@ -36,57 +36,61 @@ Kconfig-based configuration systems. Features include the following:
 
 For the Linux kernel, scripts are run using
 
- $ make scriptconfig SCRIPT=<path to script> [SCRIPT_ARG=<arg>]
+ $ make scriptconfig [ARCH=<arch>] SCRIPT=<path to script> [SCRIPT_ARG=<arg>]
 
-Running scripts via the 'scriptconfig' target ensures that required environment
-variables (SRCARCH, ARCH, srctree, KERNELVERSION, etc.) are set up correctly.
-Alternative architectures can be specified like for other 'make *config'
-targets:
+Using the 'scriptconfig' target ensures that required environment variables
+(SRCARCH, ARCH, srctree, KERNELVERSION, etc.) are set up correctly.
 
- $ make scriptconfig ARCH=mips SCRIPT=<path to script> [SCRIPT_ARG=<arg>]
-
-The script will receive the name of the Kconfig file to load in sys.argv[1].
-(As of Linux 3.7.0-rc8 this is always "Kconfig" from the kernel top-level
-directory.) If an argument is provided with SCRIPT_ARG, it will appear in
-sys.argv[2].
+Scripts receive the name of the Kconfig file to load in sys.argv[1]. As of
+Linux 4.1.0-rc5, this is always "Kconfig" from the kernel top-level directory.
+If an argument is provided with SCRIPT_ARG, it appears as sys.argv[2].
 
 To get an interactive Python prompt with Kconfiglib preloaded and a Config
-object 'c' created, use
+object 'c' created, run
 
- $ make iscriptconfig [ARCH=<architecture>]
+ $ make iscriptconfig [ARCH=<arch>]
 
-Kconfiglib requires Python 2. For (i)scriptconfig the command to run the Python
-interpreter can be passed in the environment variable PYTHONCMD (defaults to
-'python'; PyPy works too and is a bit faster).
+Kconfiglib supports both Python 2 and Python 3. For (i)scriptconfig, the Python
+interpreter to use can be passed in PYTHONCMD, which defaults to 'python'. PyPy
+works well too, and might give a nice speedup for long-running jobs.
 
-Look in the examples/ subdirectory for examples, which can be run with e.g.
+The examples/ directory contains short example scripts, which can be run with
+e.g.
 
  $ make scriptconfig SCRIPT=Kconfiglib/examples/print_tree.py
 
 or
 
- $ make scriptconfig SCRIPT=Kconfiglib/examples/help_grep.py SCRIPT_ARG="kernel"
+ $ make scriptconfig SCRIPT=Kconfiglib/examples/help_grep.py SCRIPT_ARG=kernel
 
-Look in testsuite.py for the test suite.
+testsuite.py contains the test suite. See the top of the script for how to run
+it.
 
 Credits: Written by Ulf "Ulfalizer" Magnusson
 
-Send bug reports, suggestions and other feedback to kconfiglib at gmail.com .
-Don't wrestle with internal APIs. Tell me what you need and I might add it in a
-safe way as a client API instead."""
-
-# If you have Psyco installed (32-bit installations, Python <= 2.6 only),
-# setting this to True (right here, not@runtime) might give a nice speedup.
-# (22% faster for parsing arch/x86/Kconfig and 58% faster for evaluating all
-# symbols in it without a .config on my Core Duo.)
-use_psyco = False
+Send bug reports, suggestions and other feedback to ulfalizer a.t Google's
+email service. Don't wrestle with internal APIs. Tell me what you need and I
+might add it in a safe way as a client API instead."""
 
 import os
 import re
-import string
 import sys
 
-class Config():
+# File layout:
+#
+# Public classes
+# Public functions
+# Internal classes
+# Internal functions
+# Internal global constants
+
+# Line length: 79 columns
+
+#
+# Public classes
+#
+
+class Config(object):
 
     """Represents a Kconfig configuration, e.g. for i386 or ARM. This is the
     set of symbols and other items appearing in the configuration together with
@@ -97,46 +101,46 @@ class Config():
     # Public interface
     #
 
-    def __init__(self,
-                 filename = "Kconfig",
-                 base_dir = "$srctree",
-                 print_warnings = True,
-                 print_undef_assign = False):
+    def __init__(self, filename="Kconfig", base_dir=None, print_warnings=True,
+                 print_undef_assign=False):
         """Creates a new Config object, representing a Kconfig configuration.
         Raises Kconfig_Syntax_Error on syntax errors.
 
-        filename (default: "Kconfig") -- The base Kconfig file of the
-                 configuration. For the Linux kernel, this should usually be be
-                 "Kconfig" from the top-level directory, as environment
-                 variables will make sure the right Kconfig is included from
-                 there (usually arch/<architecture>/Kconfig). If you are using
-                 kconfiglib via 'make scriptconfig' the filename of the
-                 correct Kconfig will be in sys.argv[1].
-
-        base_dir (default: "$srctree") -- The base directory relative to which
-                'source' statements within Kconfig files will work. For the
-                Linux kernel this should be the top-level directory of the
-                kernel tree. $-references to environment variables will be
-                expanded.
-
-                The environment variable 'srctree' is set by the Linux makefiles
-                to the top-level kernel directory. A default of "." would not
-                work if an alternative build directory is used.
-
-        print_warnings (default: True) -- Set to True if warnings related to
-                       this configuration should be printed to stderr. This can
-                       be changed later with Config.set_print_warnings(). It is
-                       provided as a constructor argument since warnings might
-                       be generated during parsing.
-
-        print_undef_assign (default: False) -- Set to True if informational
-                           messages related to assignments to undefined symbols
-                           should be printed to stderr for this configuration.
-                           Can be changed later with
-                           Config.set_print_undef_assign()."""
+        filename (default: "Kconfig"): The base Kconfig file of the
+           configuration. For the Linux kernel, you'll probably want "Kconfig"
+           from the top-level directory, as environment variables will make
+           sure the right Kconfig is included from there
+           (arch/<architecture>/Kconfig). If you are using Kconfiglib via 'make
+           scriptconfig', the filename of the base base Kconfig file will be in
+           sys.argv[1].
+
+        base_dir (default: None): The base directory relative to which 'source'
+           statements within Kconfig files will work. For the Linux kernel this
+           should be the top-level directory of the kernel tree. $-references
+           to existing environment variables will be expanded.
+
+           If None (the default), the environment variable 'srctree' will be
+           used if set, and the current directory otherwise. 'srctree' is set
+           by the Linux makefiles to the top-level kernel directory. A default
+           of "." would not work with an alternative build directory.
+
+        print_warnings (default: True): Set to True if warnings related to this
+           configuration should be printed to stderr. This can be changed later
+           with Config.set_print_warnings(). It is provided as a constructor
+           argument since warnings might be generated during parsing.
+
+        print_undef_assign (default: False): Set to True if informational
+           messages related to assignments to undefined symbols should be
+           printed to stderr for this configuration. Can be changed later with
+           Config.set_print_undef_assign()."""
 
         # The set of all symbols, indexed by name (a string)
         self.syms = {}
+        # Python 2/3 compatibility hack. This is the only one needed.
+        if sys.version_info[0] >= 3:
+            self.syms_iter = self.syms.values
+        else:
+            self.syms_iter = self.syms.itervalues
 
         # The set of all defined symbols in the configuration in the order they
         # appear in the Kconfig files. This excludes the special symbols n, m,
@@ -147,14 +151,19 @@ class Config():
         # by name (a string)
         self.named_choices = {}
 
-        def register_special_symbol(type, name, value):
+        # Lists containing all choices, menus and comments in the configuration
+        self.choices = []
+        self.menus = []
+        self.comments = []
+
+        def register_special_symbol(type_, name, val):
             sym = Symbol()
             sym.is_special_ = True
             sym.is_defined_ = True
             sym.config = self
             sym.name = name
-            sym.type = type
-            sym.cached_value = value
+            sym.type = type_
+            sym.cached_val = val
             self.syms[name] = sym
             return sym
 
@@ -163,7 +172,6 @@ class Config():
         self.n = register_special_symbol(TRISTATE, "n", "n")
         self.m = register_special_symbol(TRISTATE, "m", "m")
         self.y = register_special_symbol(TRISTATE, "y", "y")
-
         # DEFCONFIG_LIST uses this
         register_special_symbol(STRING, "UNAME_RELEASE", os.uname()[2])
 
@@ -172,7 +180,7 @@ class Config():
         self.defconfig_sym = None
 
         # See Symbol.get_(src)arch()
-        self.arch    = os.environ.get("ARCH")
+        self.arch = os.environ.get("ARCH")
         self.srcarch = os.environ.get("SRCARCH")
 
         # See Config.__init__(). We need this for get_defconfig_filename().
@@ -181,26 +189,22 @@ class Config():
             self.srctree = "."
 
         self.filename = filename
-        self.base_dir = _strip_trailing_slash(os.path.expandvars(base_dir))
+        if base_dir is None:
+            self.base_dir = self.srctree
+        else:
+            self.base_dir = os.path.expandvars(base_dir)
 
         # The 'mainmenu' text
         self.mainmenu_text = None
 
         # The filename of the most recently loaded .config file
         self.config_filename = None
-
         # The textual header of the most recently loaded .config, uncommented
         self.config_header = None
 
         self.print_warnings = print_warnings
         self.print_undef_assign = print_undef_assign
 
-        # Lists containing all choices, menus and comments in the configuration
-
-        self.choices = []
-        self.menus = []
-        self.comments = []
-
         # For parsing routines that stop when finding a line belonging to a
         # different construct, these holds that line and the tokenized version
         # of that line. The purpose is to avoid having to re-tokenize the line,
@@ -210,11 +214,11 @@ class Config():
         self.end_line_tokens = None
 
         # See the comment in _parse_expr().
-        self.parse_expr_cur_sym_or_choice = None
-        self.parse_expr_line = None
-        self.parse_expr_filename = None
-        self.parse_expr_linenr = None
-        self.parse_expr_transform_m = None
+        self._cur_item = None
+        self._line = None
+        self._filename = None
+        self._linenr = None
+        self._transform_m = None
 
         # Parse the Kconfig files
         self.top_block = self._parse_file(filename, None, None, None)
@@ -222,169 +226,6 @@ class Config():
         # Build Symbol.dep for all symbols
         self._build_dep()
 
-    def load_config(self, filename, replace = True):
-        """Loads symbol values from a file in the familiar .config format.
-           Equivalent to calling Symbol.set_user_value() to set each of the
-           values.
-
-           filename -- The .config file to load. $-references to environment
-                       variables will be expanded. For scripts to work even
-                       when an alternative build directory is used with the
-                       Linux kernel, you need to refer to the top-level kernel
-                       directory with "$srctree".
-
-           replace (default: True) -- True if the configuration should replace
-                   the old configuration; False if it should add to it."""
-
-        def warn_override(filename, linenr, name, old_user_val, new_user_val):
-            self._warn("overriding the value of {0}. "
-                       'Old value: "{1}", new value: "{2}".'
-                        .format(name, old_user_val, new_user_val),
-                       filename,
-                       linenr)
-
-        filename = os.path.expandvars(filename)
-
-        # Put this first so that a missing file doesn't screw up our state
-        line_feeder = _FileFeed(_get_lines(filename), filename)
-
-        self.config_filename = filename
-
-        # Invalidate everything. This is usually faster than finding the
-        # minimal set of symbols that needs to be invalidated, as nearly all
-        # symbols will tend to be affected anyway.
-        if replace:
-            self.unset_user_values()
-        else:
-            self._invalidate_all()
-
-        # Read header
-
-        self.config_header = None
-
-        def is_header_line(line):
-            return line.startswith("#") and \
-                   not unset_re.match(line)
-
-        first_line = line_feeder.get_next()
-
-        if first_line is None:
-            return
-
-        if not is_header_line(first_line):
-            line_feeder.go_back()
-        else:
-            self.config_header = first_line[1:]
-
-            # Read remaining header lines
-            while 1:
-                line = line_feeder.get_next()
-
-                if line is None:
-                    break
-
-                if not is_header_line(line):
-                    line_feeder.go_back()
-                    break
-
-                self.config_header += line[1:]
-
-            # Remove trailing newline
-            if self.config_header.endswith("\n"):
-                self.config_header = self.config_header[:-1]
-
-        # Read assignments
-
-        filename = line_feeder.get_filename()
-
-        while 1:
-            line = line_feeder.get_next()
-            if line is None:
-                return
-
-            linenr = line_feeder.get_linenr()
-
-            line = line.strip()
-
-            set_re_match = set_re.match(line)
-            if set_re_match:
-                name, val = set_re_match.groups()
-                # The unescaping producedure below should be safe since " can
-                # only appear as \" inside the string
-                val = _strip_quotes(val, line, filename, linenr)\
-                      .replace('\\"', '"').replace("\\\\", "\\")
-                if name in self.syms:
-                    sym = self.syms[name]
-
-                    old_user_val = sym.user_val
-                    if old_user_val is not None:
-                        warn_override(filename, linenr, name, old_user_val, val)
-
-                    if sym.is_choice_symbol_:
-                        user_mode = sym.parent.user_mode
-                        if user_mode is not None and user_mode != val:
-                            self._warn("assignment to {0} changes mode of containing "
-                                       'choice from "{1}" to "{2}".'
-                                       .format(name, val, user_mode),
-                                       filename,
-                                       linenr)
-
-                    sym._set_user_value_no_invalidate(val, True)
-
-                else:
-                    self._undef_assign('attempt to assign the value "{0}" to the '
-                                       "undefined symbol {1}."
-                                       .format(val, name),
-                                       filename,
-                                       linenr)
-
-            else:
-                unset_re_match = unset_re.match(line)
-                if unset_re_match:
-                    name = unset_re_match.group(1)
-                    if name in self.syms:
-                        sym = self.syms[name]
-
-                        old_user_val = sym.user_val
-                        if old_user_val is not None:
-                            warn_override(filename, linenr, name, old_user_val, "n")
-
-                        sym._set_user_value_no_invalidate("n", True)
-
-    def write_config(self, filename, header = None):
-        """Writes out symbol values in the familiar .config format.
-
-           filename -- The filename under which to save the configuration.
-
-           header (default: None) -- A textual header that will appear at the
-                  beginning of the file, with each line commented out
-                  automatically. None means no header."""
-
-        # already_written is set when _make_conf() is called on a symbol, so
-        # that symbols defined in multiple locations only get one entry in the
-        # .config. We need to reset it prior to writing out a new .config.
-        for sym in self.syms.itervalues():
-            sym.already_written = False
-
-        with open(filename, "w") as f:
-            # Write header
-            if header is not None:
-                f.write(_comment(header))
-                f.write("\n")
-
-            # Write configuration.
-            # (You'd think passing a list around to all the nodes and appending
-            # to it to avoid copying would be faster, but it's actually a lot
-            # slower with PyPy, and about as fast with Python. Passing the file
-            # around is slower too.)
-            f.write("\n".join(self.top_block._make_conf()))
-            f.write("\n")
-
-    def get_kconfig_filename(self):
-        """Returns the name of the (base) kconfig file this configuration was
-        loaded from."""
-        return self.filename
-
     def get_arch(self):
         """Returns the value the environment variable ARCH had at the time the
         Config instance was created, or None if ARCH was not set. For the
@@ -395,8 +236,8 @@ class Config():
     def get_srcarch(self):
         """Returns the value the environment variable SRCARCH had at the time
         the Config instance was created, or None if SRCARCH was not set. For
-        the kernel, this corresponds to the arch/ subdirectory containing
-        architecture-specific source code."""
+        the kernel, this corresponds to the particular arch/ subdirectory
+        containing architecture-specific code."""
         return self.srcarch
 
     def get_srctree(self):
@@ -406,11 +247,33 @@ class Config():
         in a separate directory."""
         return self.srctree
 
+    def get_base_dir(self):
+        """Returns the base directory relative to which 'source' statements
+        will work, passed as an argument to Config.__init__()."""
+        return self.base_dir
+
+    def get_kconfig_filename(self):
+        """Returns the name of the (base) kconfig file this configuration was
+        loaded from."""
+        return self.filename
+
     def get_config_filename(self):
-        """Returns the name of the most recently loaded configuration file, or
-        None if no configuration has been loaded."""
+        """Returns the filename of the most recently loaded configuration file,
+        or None if no configuration has been loaded."""
         return self.config_filename
 
+    def get_config_header(self):
+        """Returns the (uncommented) textual header of the .config file most
+        recently loaded with load_config(). Returns None if no .config file has
+        been loaded or if the most recently loaded .config file has no header.
+        The header consists of all lines up to but not including the first line
+        that either
+
+        1. Does not start with "#"
+        2. Has the form "# CONFIG_FOO is not set."
+        """
+        return self.config_header
+
     def get_mainmenu_text(self):
         """Returns the text of the 'mainmenu' statement (with $-references to
         symbols replaced by symbol values), or None if the configuration has no
@@ -431,30 +294,26 @@ class Config():
         directory before looking in the current directory; see
         Config.__init__().
 
-        WARNING: A wart here is that scripts/kconfig/Makefile sometimes uses the
-        --defconfig=<defconfig> option when calling the C implementation of e.g.
-        'make defconfig'. This option overrides the 'option defconfig_list'
-        symbol, meaning the result from get_defconfig_filename() might not
-        match what 'make defconfig' would use. That probably ought to be worked
-        around somehow, so that this function always gives the "expected"
-        result."""
-
+        WARNING: A wart here is that scripts/kconfig/Makefile sometimes uses
+        the --defconfig=<defconfig> option when calling the C implementation of
+        e.g. 'make defconfig'. This option overrides the 'option
+        defconfig_list' symbol, meaning the result from
+        get_defconfig_filename() might not match what 'make defconfig' would
+        use. That probably ought to be worked around somehow, so that this
+        function always gives the "expected" result."""
         if self.defconfig_sym is None:
             return None
-
-        for (filename, cond_expr) in self.defconfig_sym.def_exprs:
+        for filename, cond_expr in self.defconfig_sym.def_exprs:
             if self._eval_expr(cond_expr) == "y":
                 filename = self._expand_sym_refs(filename)
-
                 # We first look in $srctree. os.path.join() won't work here as
                 # an absolute path in filename would override $srctree.
-                srctree_filename = os.path.normpath(self.srctree + "/" + filename)
+                srctree_filename = os.path.normpath(self.srctree + "/" +
+                                                    filename)
                 if os.path.exists(srctree_filename):
                     return srctree_filename
-
                 if os.path.exists(filename):
                     return filename
-
         return None
 
     def get_symbol(self, name):
@@ -464,14 +323,12 @@ class Config():
         KeyError if the symbol does not exist."""
         return self.syms.get(name)
 
-    def get_top_level_items(self):
-        """Returns a list containing the items (symbols, menus, choice
-        statements and comments) at the top level of the configuration -- that
-        is, all items that do not appear within a menu or choice. The items
-        appear in the same order as within the configuration."""
-        return self.top_block.get_items()
+    def __getitem__(self, name):
+        """Returns the symbol with name 'name'. Raises KeyError if the symbol
+        does not appear in the configuration."""
+        return self.syms[name]
 
-    def get_symbols(self, all_symbols = True):
+    def get_symbols(self, all_symbols=True):
         """Returns a list of symbols from the configuration. An alternative for
         iterating over all defined symbols (in the order of definition) is
 
@@ -483,13 +340,26 @@ class Config():
         for sym in config.get_symbols(False):
             ...
 
-        all_symbols (default: True) -- If True, all symbols - including special
-                    and undefined symbols - will be included in the result, in
-                    an undefined order. If False, only symbols actually defined
-                    and not merely referred to in the configuration will be
-                    included in the result, and will appear in the order that
-                    they are defined within the Kconfig configuration files."""
-        return self.syms.values() if all_symbols else self.kconfig_syms
+        all_symbols (default: True): If True, all symbols -- including special
+           and undefined symbols -- will be included in the result, in an
+           undefined order. If False, only symbols actually defined and not
+           merely referred to in the configuration will be included in the
+           result, and will appear in the order that they are defined within
+           the Kconfig configuration files."""
+        return list(self.syms.values()) if all_symbols else self.kconfig_syms
+
+    def __iter__(self):
+        """Convenience function for iterating over the set of all defined
+        symbols in the configuration, used like
+
+        for sym in conf:
+            ...
+
+        The iteration happens in the order of definition within the Kconfig
+        configuration files. Symbols only referred to but not defined will not
+        be included, nor will the special symbols n, m, and y. If you want to
+        include such symbols as well, see config.get_symbols()."""
+        return iter(self.kconfig_syms)
 
     def get_choices(self):
         """Returns a list containing all choice statements in the
@@ -506,6 +376,158 @@ class Config():
         order they appear in the Kconfig files."""
         return self.comments
 
+    def get_top_level_items(self):
+        """Returns a list containing the items (symbols, menus, choices, and
+        comments) at the top level of the configuration -- that is, all items
+        that do not appear within a menu or choice. The items appear in the
+        same order as within the configuration."""
+        return self.top_block
+
+    def load_config(self, filename, replace=True):
+        """Loads symbol values from a file in the familiar .config format.
+        Equivalent to calling Symbol.set_user_value() to set each of the
+        values.
+
+        "# CONFIG_FOO is not set" within a .config file is treated specially
+        and sets the user value of FOO to 'n'. The C implementation works the
+        same way.
+
+        filename: The .config file to load. $-references to existing
+          environment variables will be expanded. For scripts to work even when
+          an alternative build directory is used with the Linux kernel, you
+          need to refer to the top-level kernel directory with "$srctree".
+
+        replace (default: True): True if the configuration should replace the
+           old configuration; False if it should add to it."""
+
+        # Put this first so that a missing file doesn't screw up our state
+        filename = os.path.expandvars(filename)
+        line_feeder = _FileFeed(filename)
+
+        self.config_filename = filename
+
+        #
+        # Read header
+        #
+
+        def is_header_line(line):
+            return line is not None and line.startswith("#") and \
+                   not _unset_re_match(line)
+
+        self.config_header = None
+
+        line = line_feeder.peek_next()
+        if is_header_line(line):
+            self.config_header = ""
+            while is_header_line(line_feeder.peek_next()):
+                self.config_header += line_feeder.get_next()[1:]
+            # Remove trailing newline
+            if self.config_header.endswith("\n"):
+                self.config_header = self.config_header[:-1]
+
+        #
+        # Read assignments. Hotspot for some workloads.
+        #
+
+        def warn_override(filename, linenr, name, old_user_val, new_user_val):
+            self._warn('overriding the value of {0}. '
+                       'Old value: "{1}", new value: "{2}".'
+                       .format(name, old_user_val, new_user_val),
+                       filename, linenr)
+
+        # Invalidate everything to keep things simple. It might be possible to
+        # improve performance for the case where multiple configurations are
+        # loaded by only invalidating a symbol (and its dependent symbols) if
+        # the new user value differs from the old. One complication would be
+        # that symbols not mentioned in the .config must lose their user value
+        # when replace = True, which is the usual case.
+        if replace:
+            self.unset_user_values()
+        else:
+            self._invalidate_all()
+
+        while 1:
+            line = line_feeder.get_next()
+            if line is None:
+                return
+
+            line = line.rstrip()
+
+            set_match = _set_re_match(line)
+            if set_match:
+                name, val = set_match.groups()
+
+                if val.startswith('"'):
+                    if len(val) < 2 or val[-1] != '"':
+                        _parse_error(line, "malformed string literal",
+                                     line_feeder.filename, line_feeder.linenr)
+                    # Strip quotes and remove escapings. The unescaping
+                    # procedure should be safe since " can only appear as \"
+                    # inside the string.
+                    val = val[1:-1].replace('\\"', '"').replace("\\\\", "\\")
+
+                if name in self.syms:
+                    sym = self.syms[name]
+                    if sym.user_val is not None:
+                        warn_override(line_feeder.filename, line_feeder.linenr,
+                                      name, sym.user_val, val)
+
+                    if sym.is_choice_sym:
+                        user_mode = sym.parent.user_mode
+                        if user_mode is not None and user_mode != val:
+                            self._warn("assignment to {0} changes mode of "
+                                       'containing choice from "{1}" to "{2}".'
+                                       .format(name, val, user_mode),
+                                       line_feeder.filename,
+                                       line_feeder.linenr)
+
+                    sym._set_user_value_no_invalidate(val, True)
+                else:
+                    if self.print_undef_assign:
+                        _stderr_msg('note: attempt to assign the value "{0}" '
+                                    "to the undefined symbol {1}."
+                                    .format(val, name),
+                                    line_feeder.filename, line_feeder.linenr)
+            else:
+                unset_match = _unset_re_match(line)
+                if unset_match:
+                    name = unset_match.group(1)
+                    if name in self.syms:
+                        sym = self.syms[name]
+                        if sym.user_val is not None:
+                            warn_override(line_feeder.filename,
+                                          line_feeder.linenr,
+                                          name, sym.user_val, "n")
+
+                        sym._set_user_value_no_invalidate("n", True)
+
+    def write_config(self, filename, header=None):
+        """Writes out symbol values in the familiar .config format.
+
+        Kconfiglib makes sure the format matches what the C implementation
+        would generate, down to whitespace. This eases testing.
+
+        filename: The filename under which to save the configuration.
+
+        header (default: None): A textual header that will appear at the
+           beginning of the file, with each line commented out automatically.
+           None means no header."""
+
+        for sym in self.syms_iter():
+            sym.already_written = False
+
+        with open(filename, "w") as f:
+            # Write header
+            if header is not None:
+                f.write(_comment(header))
+                f.write("\n")
+
+            # Build and write configuration
+            conf_strings = []
+            _make_block_conf(self.top_block, conf_strings.append)
+            f.write("\n".join(conf_strings))
+            f.write("\n")
+
     def eval(self, s):
         """Returns the value of the expression 's' -- where 's' is represented
         as a string -- in the context of the configuration. Raises
@@ -521,478 +543,125 @@ class Config():
         conditional expressions in the configuration as well as in the C
         implementation. "m" and m are rewritten as '"m" && MODULES' and 'm &&
         MODULES', respectively, and a result of "m" will get promoted to "y" if
-        we're running without modules."""
+        we're running without modules.
+
+        Syntax checking is somewhat lax, partly to be compatible with lax
+        parsing in the C implementation."""
         return self._eval_expr(self._parse_expr(self._tokenize(s, True), # Feed
-                                                None, # Current symbol or choice
+                                                None, # Current symbol/choice
                                                 s))   # line
 
-    def get_config_header(self):
-        """Returns the (uncommented) textual header of the .config file most
-        recently loaded with load_config(). Returns None if no .config file has
-        been loaded or if the most recently loaded .config file has no header.
-        The header comprises all lines up to but not including the first line
-        that either
-
-        1. Does not start with "#"
-        2. Has the form "# CONFIG_FOO is not set."
-        """
-        return self.config_header
-
-    def get_base_dir(self):
-        """Returns the base directory relative to which 'source' statements
-        will work, passed as an argument to Config.__init__()."""
-        return self.base_dir
+    def unset_user_values(self):
+        """Resets the values of all symbols, as if Config.load_config() or
+        Symbol.set_user_value() had never been called."""
+        for sym in self.syms_iter():
+            sym._unset_user_value_no_recursive_invalidate()
 
     def set_print_warnings(self, print_warnings):
         """Determines whether warnings related to this configuration (for
         things like attempting to assign illegal values to symbols with
         Symbol.set_user_value()) should be printed to stderr.
 
-        print_warnings -- True if warnings should be
-                          printed, otherwise False."""
+        print_warnings: True if warnings should be printed."""
         self.print_warnings = print_warnings
 
     def set_print_undef_assign(self, print_undef_assign):
         """Determines whether informational messages related to assignments to
         undefined symbols should be printed to stderr for this configuration.
 
-        print_undef_assign -- If True, such messages will be printed."""
+        print_undef_assign: If True, such messages will be printed."""
         self.print_undef_assign = print_undef_assign
 
-    def __getitem__(self, key):
-        """Returns the symbol with name 'name'. Raises KeyError if the symbol
-        does not appear in the configuration."""
-        return self.syms[key]
-
-    def __iter__(self):
-        """Convenience function for iterating over the set of all defined
-        symbols in the configuration, used like
-
-        for sym in conf:
-            ...
-
-        The iteration happens in the order of definition within the Kconfig
-        configuration files. Symbols only referred to but not defined will not
-        be included, nor will the special symbols n, m, and y. If you want to
-        include such symbols as well, see config.get_symbols()."""
-        return iter(self.kconfig_syms)
-
-    def unset_user_values(self):
-        """Resets the values of all symbols, as if Config.load_config() or
-        Symbol.set_user_value() had never been called."""
-        for sym in self.syms.itervalues():
-            sym._unset_user_value_no_recursive_invalidate()
-
-    def __str__(self):
-        """Returns a string containing various information about the Config."""
-        return _sep_lines("Configuration",
-                          "File                                   : " + self.filename,
-                          "Base directory                         : " + self.base_dir,
-                          "Value of $ARCH at creation time        : " +
-                            ("(not set)" if self.arch is None else self.arch),
-                          "Value of $SRCARCH at creation time     : " +
-                            ("(not set)" if self.srcarch is None else self.srcarch),
-                          "Source tree (derived from $srctree;",
-                          "defaults to '.' if $srctree isn't set) : " + self.srctree,
-                          "Most recently loaded .config           : " +
-                            ("(no .config loaded)" if self.config_filename is None else
-                             self.config_filename),
-                          "Print warnings                         : " +
-                            bool_str[self.print_warnings],
-                          "Print assignments to undefined symbols : " +
-                            bool_str[self.print_undef_assign])
-
+    def __str__(self):
+        """Returns a string containing various information about the Config."""
+        return _lines("Configuration",
+                      "File                                   : " +
+                        self.filename,
+                      "Base directory                         : " +
+                        self.base_dir,
+                      "Value of $ARCH at creation time        : " +
+                        ("(not set)" if self.arch is None else self.arch),
+                      "Value of $SRCARCH@creation time     : " +
+                        ("(not set)" if self.srcarch is None else
+                                        self.srcarch),
+                      "Source tree (derived from $srctree;",
+                      "defaults to '.' if $srctree isn't set) : " +
+                        self.srctree,
+                      "Most recently loaded .config           : " +
+                        ("(no .config loaded)"
+                          if self.config_filename is None else
+                             self.config_filename),
+                      "Print warnings                         : " +
+                        BOOL_STR[self.print_warnings],
+                      "Print assignments to undefined symbols : " +
+                        BOOL_STR[self.print_undef_assign])
 
     #
     # Private methods
     #
 
-    def _invalidate_all(self):
-        for sym in self.syms.itervalues():
-            sym._invalidate()
-
-    def _tokenize(self,
-                  s,
-                  for_eval = False,
-                  filename = None,
-                  linenr = None):
-        """Returns a _Feed instance containing tokens derived from the string
-        's'. Registers any new symbols encountered (via _sym_lookup()).
-
-        (I experimented with a pure regular expression implementation, but it
-        came out slower, less readable, and wouldn't have been as flexible.)
-
-        for_eval -- True when parsing an expression for a call to
-                    Config.eval(), in which case we should not treat the first
-                    token specially nor register new symbols."""
-        s = s.lstrip()
-        if s == "" or s[0] == "#":
-            return _Feed([])
-
-        if for_eval:
-            i = 0 # The current index in the string being tokenized
-            previous = None # The previous token seen
-            tokens = []
-        else:
-            # The initial word on a line is parsed specially. Let
-            # command_chars = [A-Za-z0-9_]. Then
-            #  - leading non-command_chars characters on the line are ignored, and
-            #  - the first token consists the following one or more command_chars
-            #    characters.
-            # This is why things like "----help--" are accepted.
-
-            initial_token_match = initial_token_re.match(s)
-            if initial_token_match is None:
-                return _Feed([])
-            # The current index in the string being tokenized
-            i = initial_token_match.end()
-
-            keyword = keywords.get(initial_token_match.group(1))
-            if keyword is None:
-                # We expect a keyword as the first token
-                _tokenization_error(s, len(s), filename, linenr)
-            if keyword == T_HELP:
-                # Avoid junk after "help", e.g. "---", being registered as a
-                # symbol
-                return _Feed([T_HELP])
-            tokens = [keyword]
-            previous = keyword
-
-        # _tokenize() is a hotspot during parsing, and this speeds things up a
-        # bit
-        strlen = len(s)
-        append = tokens.append
-
-        # Main tokenization loop. (Handles tokens past the first one.)
-        while i < strlen:
-            # Test for an identifier/keyword preceded by whitespace first; this
-            # is the most common case.
-            id_keyword_match = id_keyword_re.match(s, i)
-            if id_keyword_match:
-                # We have an identifier or keyword. The above also stripped any
-                # whitespace for us.
-                name = id_keyword_match.group(1)
-                # Jump past it
-                i = id_keyword_match.end()
-
-                # Keyword?
-                keyword = keywords.get(name)
-                if keyword is not None:
-                    append(keyword)
-                # What would ordinarily be considered a name is treated as a
-                # string after certain tokens.
-                elif previous in string_lex:
-                    append(name)
-                else:
-                    # We're dealing with a symbol. _sym_lookup() will take care
-                    # of allocating a new Symbol instance if it's the first
-                    # time we see it.
-                    sym = self._sym_lookup(name, not for_eval)
-
-                    if previous == T_CONFIG or previous == T_MENUCONFIG:
-                        # If the previous token is T_(MENU)CONFIG
-                        # ("(menu)config"), we're tokenizing the first line of
-                        # a symbol definition, and should remember this as a
-                        # location where the symbol is defined.
-                        sym.def_locations.append((filename, linenr))
-                    else:
-                        # Otherwise, it's a reference to the symbol
-                        sym.ref_locations.append((filename, linenr))
-
-                    append(sym)
-
-            else:
-                # This restrips whitespace that could have been stripped in the
-                # regex above, but it's worth it since identifiers/keywords are
-                # more common
-                s = s[i:].lstrip()
-                if s == "":
-                    break
-                strlen = len(s)
-                i = 0
-                c = s[0]
-
-                # String literal (constant symbol)
-                if c == '"' or c == "'":
-                    i += 1
-
-                    if "\\" in s:
-                        # Slow path: This could probably be sped up, but it's a
-                        # very unusual case anyway.
-                        quote = c
-                        value = ""
-                        while 1:
-                            if i >= strlen:
-                                _tokenization_error(s, strlen, filename,
-                                                    linenr)
-                            c = s[i]
-                            if c == quote:
-                                break
-                            if c == "\\":
-                                if i + 1 >= strlen:
-                                    _tokenization_error(s, strlen, filename,
-                                                        linenr)
-                                value += s[i + 1]
-                                i += 2
-                            else:
-                                value += c
-                                i += 1
-                        i += 1
-                        append(value)
-                    else:
-                        # Fast path: If the string contains no backslashes (almost
-                        # always) we can simply look for the matching quote.
-                        end = s.find(c, i)
-                        if end == -1:
-                            _tokenization_error(s, strlen, filename, linenr)
-                        append(s[i:end])
-                        i = end + 1
-
-                elif c == "&":
-                    if i + 1 >= strlen:
-                        # Invalid characters are ignored
-                        continue
-                    if s[i + 1] != "&":
-                        # Invalid characters are ignored
-                        i += 1
-                        continue
-                    append(T_AND)
-                    i += 2
-
-                elif c == "|":
-                    if i + 1 >= strlen:
-                        # Invalid characters are ignored
-                        continue
-                    if s[i + 1] != "|":
-                        # Invalid characters are ignored
-                        i += 1
-                        continue
-                    append(T_OR)
-                    i += 2
-
-                elif c == "!":
-                    if i + 1 >= strlen:
-                        _tokenization_error(s, strlen, filename, linenr)
-                    if s[i + 1] == "=":
-                        append(T_UNEQUAL)
-                        i += 2
-                    else:
-                        append(T_NOT)
-                        i += 1
-
-                elif c == "=":
-                    append(T_EQUAL)
-                    i += 1
-
-                elif c == "(":
-                    append(T_OPEN_PAREN)
-                    i += 1
-
-                elif c == ")":
-                    append(T_CLOSE_PAREN)
-                    i += 1
-
-                elif c == "#":
-                    break
-
-                else:
-                    # Invalid characters are ignored
-                    i += 1
-                    continue
-
-            previous = tokens[-1]
-
-        return _Feed(tokens)
-
     #
-    # Parsing
-    #
-
-    # Expression grammar:
+    # Kconfig parsing
     #
-    # <expr> -> <symbol>
-    #           <symbol> '=' <symbol>
-    #           <symbol> '!=' <symbol>
-    #           '(' <expr> ')'
-    #           '!' <expr>
-    #           <expr> '&&' <expr>
-    #           <expr> '||' <expr>
-
-    def _parse_expr(self,
-                    feed,
-                    cur_sym_or_choice,
-                    line,
-                    filename = None,
-                    linenr = None,
-                    transform_m = True):
-        """Parse an expression from the tokens in 'feed' using a simple
-        top-down approach. The result has the form (<operator>, <list
-        containing parsed operands>).
-
-        feed -- _Feed instance containing the tokens for the expression.
-
-        cur_sym_or_choice -- The symbol or choice currently being parsed, or
-                             None if we're not parsing a symbol or choice.
-                             Used for recording references to symbols.
-
-        line -- The line containing the expression being parsed.
-
-        filename (default: None) -- The file containing the expression.
-
-        linenr (default: None) -- The line number containing the expression.
-
-        transform_m (default: False) -- Determines if 'm' should be rewritten to
-                                        'm && MODULES' -- see
-                                        parse_val_and_cond()."""
-
-        # Use instance variables to avoid having to pass these as arguments
-        # through the top-down parser in _parse_expr_2(), which is tedious and
-        # obfuscates the code. A profiler run shows no noticeable performance
-        # difference.
-        self.parse_expr_cur_sym_or_choice = cur_sym_or_choice
-        self.parse_expr_line = line
-        self.parse_expr_filename = filename
-        self.parse_expr_linenr = linenr
-        self.parse_expr_transform_m = transform_m
-
-        return self._parse_expr_2(feed)
-
-    def _parse_expr_2(self, feed):
-        or_terms = [self._parse_or_term(feed)]
-        # Keep parsing additional terms while the lookahead is '||'
-        while feed.check(T_OR):
-            or_terms.append(self._parse_or_term(feed))
-
-        return or_terms[0] if len(or_terms) == 1 else (OR, or_terms)
-
-    def _parse_or_term(self, feed):
-        and_terms = [self._parse_factor(feed)]
-        # Keep parsing additional terms while the lookahead is '&&'
-        while feed.check(T_AND):
-            and_terms.append(self._parse_factor(feed))
 
-        return and_terms[0] if len(and_terms) == 1 else (AND, and_terms)
-
-    def _parse_factor(self, feed):
-        if feed.check(T_OPEN_PAREN):
-            expr_parse = self._parse_expr_2(feed)
-
-            if not feed.check(T_CLOSE_PAREN):
-                _parse_error(self.parse_expr_line,
-                             "missing end parenthesis.",
-                             self.parse_expr_filename,
-                             self.parse_expr_linenr)
-
-            return expr_parse
-
-        if feed.check(T_NOT):
-            return (NOT, self._parse_factor(feed))
-
-        sym_or_string = feed.get_next()
-
-        if not isinstance(sym_or_string, (Symbol, str)):
-            _parse_error(self.parse_expr_line,
-                         "malformed expression.",
-                         self.parse_expr_filename,
-                         self.parse_expr_linenr)
-
-        if self.parse_expr_cur_sym_or_choice is not None and \
-           isinstance(sym_or_string, Symbol):
-            self.parse_expr_cur_sym_or_choice.referenced_syms.add(sym_or_string)
-
-        next_token = feed.peek_next()
-
-        # For conditional expressions ('depends on <expr>', '... if <expr>',
-        # etc.), "m" and m are rewritten to "m" && MODULES.
-        if next_token != T_EQUAL and next_token != T_UNEQUAL:
-            if self.parse_expr_transform_m and (sym_or_string is self.m or
-                                                sym_or_string == "m"):
-                return (AND, ["m", self._sym_lookup("MODULES")])
-            return sym_or_string
-
-        relation = EQUAL if (feed.get_next() == T_EQUAL) else UNEQUAL
-        sym_or_string_2 = feed.get_next()
-
-        if self.parse_expr_cur_sym_or_choice is not None and \
-           isinstance(sym_or_string_2, Symbol):
-            self.parse_expr_cur_sym_or_choice.referenced_syms.add(sym_or_string_2)
-
-        if sym_or_string is self.m:
-            sym_or_string = "m"
-
-        if sym_or_string_2 is self.m:
-            sym_or_string_2 = "m"
-
-        return (relation, sym_or_string, sym_or_string_2)
-
-    def _parse_file(self, filename, parent, deps, visible_if_deps, res = None):
-        """Parse the Kconfig file 'filename'. The result is a _Block with all
-        items from the file. See _parse_block() for the meaning of the
-        parameters."""
-        line_feeder = _FileFeed(_get_lines(filename), filename)
-        return self._parse_block(line_feeder, None, parent, deps, visible_if_deps, res)
+    def _parse_file(self, filename, parent, deps, visible_if_deps, res=None):
+        """Parses the Kconfig file 'filename'. Returns a list with the Items in
+        the file. See _parse_block() for the meaning of the parameters."""
+        return self._parse_block(_FileFeed(filename), None, parent, deps,
+                                 visible_if_deps, res)
 
     def _parse_block(self, line_feeder, end_marker, parent, deps,
-                     visible_if_deps = None, res = None):
+                     visible_if_deps, res=None):
         """Parses a block, which is the contents of either a file or an if,
-        menu, or choice statement. The result is a _Block with the items from
-        the block.
+        menu, or choice statement. Returns a list with the Items in the block.
 
-        end_marker -- The token that ends the block, e.g. T_ENDIF ("endif") for
-                      if's. None for files.
+        line_feeder: A _FileFeed instance feeding lines from a file. The
+          Kconfig language is line-based in practice.
 
-        parent -- The enclosing menu, choice or if, or None if we're at the top
-                  level.
+        end_marker: The token that ends the block, e.g. T_ENDIF ("endif") for
+           ifs. None for files.
 
-        deps -- Dependencies from enclosing menus, choices and if's.
+        parent: The enclosing menu or choice, or None if we're at the top
+           level.
 
-        visible_if_deps (default: None) -- 'visible if' dependencies from
-                        enclosing menus.
+        deps: Dependencies from enclosing menus, choices and ifs.
 
-        res (default: None) -- The _Block to add items to. If None, a new
-                               _Block is created to hold the items."""
+        visible_if_deps (default: None): 'visible if' dependencies from
+           enclosing menus.
 
-        block = _Block() if res is None else res
+        res (default: None): The list to add items to. If None, a new list is
+           created to hold the items."""
 
-        filename = line_feeder.get_filename()
+        block = [] if res is None else res
 
         while 1:
-
             # Do we already have a tokenized line that we determined wasn't
             # part of whatever we were parsing earlier? See comment in
             # Config.__init__().
             if self.end_line is not None:
-                assert self.end_line_tokens is not None
-                tokens = self.end_line_tokens
-                tokens.go_to_start()
-
                 line = self.end_line
-                linenr = line_feeder.get_linenr()
+                tokens = self.end_line_tokens
+                tokens.unget_all()
 
                 self.end_line = None
                 self.end_line_tokens = None
-
             else:
                 line = line_feeder.get_next()
                 if line is None:
                     if end_marker is not None:
-                        raise Kconfig_Syntax_Error, (
-                                "Unexpected end of file {0}."
-                                .format(line_feeder.get_filename()))
+                        raise Kconfig_Syntax_Error("Unexpected end of file {0}"
+                                                 .format(line_feeder.filename))
                     return block
 
-                linenr = line_feeder.get_linenr()
-
-                tokens = self._tokenize(line, False, filename, linenr)
-
-            if tokens.is_empty():
-                continue
+                tokens = self._tokenize(line, False, line_feeder.filename,
+                                        line_feeder.linenr)
 
             t0 = tokens.get_next()
+            if t0 is None:
+                continue
 
-            # Have we reached the end of the block?
-            if t0 == end_marker:
-                return block
+            # Cases are ordered roughly by frequency, which speeds things up a
+            # bit
 
             if t0 == T_CONFIG or t0 == T_MENUCONFIG:
                 # The tokenizer will automatically allocate a new Symbol object
@@ -1001,162 +670,154 @@ class Config():
                 sym = tokens.get_next()
 
                 # Symbols defined in multiple places get the parent of their
-                # first definition. However, for symbols whose parents are choice
-                # statements, the choice statement takes precedence.
+                # first definition. However, for symbols whose parents are
+                # choice statements, the choice statement takes precedence.
                 if not sym.is_defined_ or isinstance(parent, Choice):
                     sym.parent = parent
 
                 sym.is_defined_ = True
 
                 self.kconfig_syms.append(sym)
-                block.add_item(sym)
+                block.append(sym)
 
                 self._parse_properties(line_feeder, sym, deps, visible_if_deps)
 
+            elif t0 == T_SOURCE:
+                kconfig_file = tokens.get_next()
+                exp_kconfig_file = self._expand_sym_refs(kconfig_file)
+                f = os.path.join(self.base_dir, exp_kconfig_file)
+                if not os.path.exists(f):
+                    raise IOError('{0}:{1}: sourced file "{2}" (expands to '
+                                  '"{3}") not found. Perhaps base_dir '
+                                  '(argument to Config.__init__(), currently '
+                                  '"{4}") is set to the wrong value.'
+                                  .format(line_feeder.filename,
+                                          line_feeder.linenr,
+                                          kconfig_file, exp_kconfig_file,
+                                          self.base_dir))
+                # Add items to the same block
+                self._parse_file(f, parent, deps, visible_if_deps, block)
+
+            elif t0 == end_marker:
+                # We have reached the end of the block
+                return block
+
+            elif t0 == T_IF:
+                # If statements are treated as syntactic sugar for adding
+                # dependencies to enclosed items and do not have an explicit
+                # object representation.
+
+                dep_expr = self._parse_expr(tokens, None, line,
+                                            line_feeder.filename,
+                                            line_feeder.linenr)
+                # Add items to the same block
+                self._parse_block(line_feeder, T_ENDIF, parent,
+                                  _make_and(dep_expr, deps),
+                                  visible_if_deps, block)
+
+            elif t0 == T_COMMENT:
+                comment = Comment()
+
+                comment.config = self
+                comment.parent = parent
+                comment.filename = line_feeder.filename
+                comment.linenr = line_feeder.linenr
+                comment.text = tokens.get_next()
+
+                self.comments.append(comment)
+                block.append(comment)
+
+                self._parse_properties(line_feeder, comment, deps,
+                                       visible_if_deps)
+
             elif t0 == T_MENU:
                 menu = Menu()
-                self.menus.append(menu)
+
                 menu.config = self
                 menu.parent = parent
+                menu.filename = line_feeder.filename
+                menu.linenr = line_feeder.linenr
                 menu.title = tokens.get_next()
 
-                menu.filename = filename
-                menu.linenr = linenr
+                self.menus.append(menu)
+                block.append(menu)
 
                 # Parse properties and contents
-                self._parse_properties(line_feeder, menu, deps, visible_if_deps)
-                menu.block = self._parse_block(line_feeder,
-                                               T_ENDMENU,
-                                               menu,
+                self._parse_properties(line_feeder, menu, deps,
+                                       visible_if_deps)
+                menu.block = self._parse_block(line_feeder, T_ENDMENU, menu,
                                                menu.dep_expr,
                                                _make_and(visible_if_deps,
                                                          menu.visible_if_expr))
 
-                block.add_item(menu)
-
-            elif t0 == T_IF:
-                # If statements are treated as syntactic sugar for adding
-                # dependencies to enclosed items and do not have an explicit
-                # object representation.
-
-                dep_expr = self._parse_expr(tokens, None, line, filename, linenr)
-                self._parse_block(line_feeder,
-                                  T_ENDIF,
-                                  parent,
-                                  _make_and(dep_expr, deps),
-                                  visible_if_deps,
-                                  block) # Add items to the same block
-
             elif t0 == T_CHOICE:
-                # We support named choices
-                already_defined = False
-                name = None
-                if len(tokens) > 1 and isinstance(tokens[1], str):
-                    name = tokens[1]
-                    already_defined = name in self.named_choices
-
-                if already_defined:
-                    choice = self.named_choices[name]
-                else:
+                name = tokens.get_next()
+                if name is None:
                     choice = Choice()
                     self.choices.append(choice)
-                    if name is not None:
+                else:
+                    # Named choice
+                    choice = self.named_choices.get(name)
+                    if choice is None:
+                        choice = Choice()
                         choice.name = name
                         self.named_choices[name] = choice
+                        self.choices.append(choice)
 
                 choice.config = self
                 choice.parent = parent
 
-                choice.def_locations.append((filename, linenr))
+                choice.def_locations.append((line_feeder.filename,
+                                             line_feeder.linenr))
 
                 # Parse properties and contents
-                self._parse_properties(line_feeder, choice, deps, visible_if_deps)
-                choice.block = self._parse_block(line_feeder,
-                                                 T_ENDCHOICE,
-                                                 choice,
-                                                 deps,
-                                                 visible_if_deps)
+                self._parse_properties(line_feeder, choice, deps,
+                                       visible_if_deps)
+                choice.block = self._parse_block(line_feeder, T_ENDCHOICE,
+                                                 choice, deps, visible_if_deps)
 
                 choice._determine_actual_symbols()
 
-                # If no type is set for the choice, its type is that of the first
-                # choice item
+                # If no type is specified for the choice, its type is that of
+                # the first choice item with a specified type
                 if choice.type == UNKNOWN:
-                    for item in choice.get_symbols():
+                    for item in choice.actual_symbols:
                         if item.type != UNKNOWN:
                             choice.type = item.type
                             break
 
                 # Each choice item of UNKNOWN type gets the type of the choice
-                for item in choice.get_symbols():
+                for item in choice.actual_symbols:
                     if item.type == UNKNOWN:
                         item.type = choice.type
 
-                # For named choices defined in multiple locations, only record
-                # at the first definition
-                if not already_defined:
-                    block.add_item(choice)
-
-            elif t0 == T_COMMENT:
-                comment = Comment()
-                comment.config = self
-                comment.parent = parent
-
-                comment.filename = filename
-                comment.linenr = linenr
-
-                comment.text = tokens.get_next()
-                self._parse_properties(line_feeder, comment, deps, visible_if_deps)
-
-                block.add_item(comment)
-                self.comments.append(comment)
-
-            elif t0 == T_SOURCE:
-                kconfig_file = tokens.get_next()
-                exp_kconfig_file = self._expand_sym_refs(kconfig_file)
-                f = os.path.join(self.base_dir, exp_kconfig_file)
-
-                if not os.path.exists(f):
-                    raise IOError, ('{0}:{1}: sourced file "{2}" (expands to\n'
-                                    '"{3}") not found. Perhaps base_dir\n'
-                                    '(argument to Config.__init__(), currently\n'
-                                    '"{4}") is set to the wrong value.'
-                                    .format(filename,
-                                            linenr,
-                                            kconfig_file,
-                                            exp_kconfig_file,
-                                            self.base_dir))
-
-                # Add items to the same block
-                self._parse_file(f, parent, deps, visible_if_deps, block)
+                block.append(choice)
 
             elif t0 == T_MAINMENU:
                 text = tokens.get_next()
-
                 if self.mainmenu_text is not None:
                     self._warn("overriding 'mainmenu' text. "
                                'Old value: "{0}", new value: "{1}".'
-                                .format(self.mainmenu_text, text),
-                               filename,
-                               linenr)
-
+                               .format(self.mainmenu_text, text),
+                               line_feeder.filename, line_feeder.linenr)
                 self.mainmenu_text = text
 
             else:
-                _parse_error(line, "unrecognized construct.", filename, linenr)
+                _parse_error(line, "unrecognized construct",
+                             line_feeder.filename, line_feeder.linenr)
 
     def _parse_properties(self, line_feeder, stmt, deps, visible_if_deps):
-        """Parsing of properties for symbols, menus, choices, and comments."""
+        """Parsing of properties for symbols, menus, choices, and comments.
+        Takes care of propagating dependencies from enclosing menus and ifs."""
 
         def parse_val_and_cond(tokens, line, filename, linenr):
             """Parses '<expr1> if <expr2>' constructs, where the 'if' part is
             optional. Returns a tuple containing the parsed expressions, with
             None as the second element if the 'if' part is missing."""
             val = self._parse_expr(tokens, stmt, line, filename, linenr, False)
-
             if tokens.check(T_IF):
-                return (val, self._parse_expr(tokens, stmt, line, filename, linenr))
-
+                return (val, self._parse_expr(tokens, stmt, line, filename,
+                                              linenr))
             return (val, None)
 
         # In case the symbol is defined in multiple locations, we need to
@@ -1175,85 +836,61 @@ class Config():
             if line is None:
                 break
 
-            filename = line_feeder.get_filename()
-            linenr = line_feeder.get_linenr()
+            filename = line_feeder.filename
+            linenr = line_feeder.linenr
 
             tokens = self._tokenize(line, False, filename, linenr)
 
-            if tokens.is_empty():
+            t0 = tokens.get_next()
+            if t0 is None:
                 continue
 
-            t0 = tokens.get_next()
+            # Cases are ordered roughly by frequency, which speeds things up a
+            # bit
 
-            if t0 == T_HELP:
-                # Find first non-empty line and get its indentation
+            if t0 == T_DEPENDS:
+                if not tokens.check(T_ON):
+                    _parse_error(line, 'expected "on" after "depends"',
+                                 filename, linenr)
 
-                line_feeder.remove_while(str.isspace)
-                line = line_feeder.get_next()
+                parsed_deps = self._parse_expr(tokens, stmt, line, filename,
+                                               linenr)
+
+                if isinstance(stmt, (Menu, Comment)):
+                    stmt.orig_deps = _make_and(stmt.orig_deps, parsed_deps)
+                else:
+                    depends_on_expr = _make_and(depends_on_expr, parsed_deps)
 
+            elif t0 == T_HELP:
+                # Find first non-blank (not all-space) line and get its
+                # indentation
+                line = line_feeder.next_nonblank()
                 if line is None:
                     stmt.help = ""
                     break
-
                 indent = _indentation(line)
-
-                # If the first non-empty lines has zero indent, there is no
-                # help text
                 if indent == 0:
+                    # If the first non-empty lines has zero indent, there is no
+                    # help text
                     stmt.help = ""
-                    line_feeder.go_back()
+                    line_feeder.unget()
                     break
 
-                help_lines = [_deindent(line, indent)]
-
                 # The help text goes on till the first non-empty line with less
                 # indent
+                help_lines = [_deindent(line, indent)]
                 while 1:
                     line = line_feeder.get_next()
-                    if (line is None) or \
+                    if line is None or \
                        (not line.isspace() and _indentation(line) < indent):
                         stmt.help = "".join(help_lines)
                         break
-
                     help_lines.append(_deindent(line, indent))
 
                 if line is None:
                     break
 
-                line_feeder.go_back()
-
-            elif t0 == T_PROMPT:
-                # 'prompt' properties override each other within a single
-                # definition of a symbol, but additional prompts can be added
-                # by defining the symbol multiple times; hence 'new_prompt'
-                # instead of 'prompt'.
-                new_prompt = parse_val_and_cond(tokens, line, filename, linenr)
-
-            elif t0 == T_DEFAULT:
-                new_def_exprs.append(parse_val_and_cond(tokens, line, filename, linenr))
-
-            elif t0 == T_DEPENDS:
-                if not tokens.check(T_ON):
-                    _parse_error(line, 'expected "on" after "depends".', filename, linenr)
-
-                parsed_deps = self._parse_expr(tokens, stmt, line, filename, linenr)
-
-                if isinstance(stmt, (Menu, Comment)):
-                    stmt.dep_expr = _make_and(stmt.dep_expr, parsed_deps)
-                else:
-                    depends_on_expr = _make_and(depends_on_expr, parsed_deps)
-
-            elif t0 == T_VISIBLE:
-                if not tokens.check(T_IF):
-                    _parse_error(line, 'expected "if" after "visible".', filename, linenr)
-                if not isinstance(stmt, Menu):
-                    _parse_error(line,
-                                 "'visible if' is only valid for menus.",
-                                 filename,
-                                 linenr)
-
-                parsed_deps = self._parse_expr(tokens, stmt, line, filename, linenr)
-                stmt.visible_if_expr = _make_and(stmt.visible_if_expr, parsed_deps)
+                line_feeder.unget()
 
             elif t0 == T_SELECT:
                 target = tokens.get_next()
@@ -1263,47 +900,52 @@ class Config():
 
                 if tokens.check(T_IF):
                     new_selects.append((target,
-                                        self._parse_expr(tokens, stmt, line, filename, linenr)))
+                                        self._parse_expr(tokens, stmt, line,
+                                                         filename, linenr)))
                 else:
                     new_selects.append((target, None))
 
             elif t0 in (T_BOOL, T_TRISTATE, T_INT, T_HEX, T_STRING):
-                stmt.type = token_to_type[t0]
+                stmt.type = TOKEN_TO_TYPE[t0]
+                if tokens.peek_next() is not None:
+                    new_prompt = parse_val_and_cond(tokens, line, filename,
+                                                    linenr)
+
+            elif t0 == T_DEFAULT:
+                new_def_exprs.append(parse_val_and_cond(tokens, line, filename,
+                                                        linenr))
+
+            elif t0 == T_DEF_BOOL:
+                stmt.type = BOOL
+                if tokens.peek_next() is not None:
+                    new_def_exprs.append(parse_val_and_cond(tokens, line,
+                                                            filename, linenr))
 
-                if len(tokens) > 1:
-                    new_prompt = parse_val_and_cond(tokens, line, filename, linenr)
+            elif t0 == T_PROMPT:
+                # 'prompt' properties override each other within a single
+                # definition of a symbol, but additional prompts can be added
+                # by defining the symbol multiple times; hence 'new_prompt'
+                # instead of 'prompt'.
+                new_prompt = parse_val_and_cond(tokens, line, filename, linenr)
 
             elif t0 == T_RANGE:
-                lower = tokens.get_next()
-                upper = tokens.get_next()
-                stmt.referenced_syms.add(lower)
-                stmt.referenced_syms.add(upper)
+                low = tokens.get_next()
+                high = tokens.get_next()
+                stmt.referenced_syms.add(low)
+                stmt.referenced_syms.add(high)
 
                 if tokens.check(T_IF):
-                    stmt.ranges.append((lower, upper,
-                                        self._parse_expr(tokens, stmt, line, filename, linenr)))
+                    stmt.ranges.append((low, high,
+                                        self._parse_expr(tokens, stmt, line,
+                                                         filename, linenr)))
                 else:
-                    stmt.ranges.append((lower, upper, None))
-
-            elif t0 == T_DEF_BOOL:
-                stmt.type = BOOL
-
-                if len(tokens) > 1:
-                    new_def_exprs.append(parse_val_and_cond(tokens, line, filename, linenr))
+                    stmt.ranges.append((low, high, None))
 
             elif t0 == T_DEF_TRISTATE:
                 stmt.type = TRISTATE
-
-                if len(tokens) > 1:
-                    new_def_exprs.append(parse_val_and_cond(tokens, line, filename, linenr))
-
-            elif t0 == T_OPTIONAL:
-                if not isinstance(stmt, Choice):
-                    _parse_error(line,
-                                 '"optional" is only valid for choices.',
-                                 filename,
-                                 linenr)
-                stmt.optional = True
+                if tokens.peek_next() is not None:
+                    new_def_exprs.append(parse_val_and_cond(tokens, line,
+                                                            filename, linenr))
 
             elif t0 == T_OPTION:
                 if tokens.check(T_ENV) and tokens.check(T_EQUAL):
@@ -1313,44 +955,74 @@ class Config():
                     stmt.is_from_env = True
 
                     if env_var not in os.environ:
-                        self._warn("""
-The symbol {0} references the non-existent environment variable {1} and will
-get the empty string as its value.
-
-If you're using kconfiglib via 'make (i)scriptconfig' it should have set up the
-environment correctly for you. If you still got this message, that might be an
-error, and you should e-mail kconfiglib at gmail.com.
-."""                               .format(stmt.name, env_var),
-                                   filename,
-                                   linenr)
-
-                        stmt.cached_value = ""
+                        self._warn("The symbol {0} references the "
+                                   "non-existent environment variable {1} and "
+                                   "will get the empty string as its value. "
+                                   "If you're using Kconfiglib via "
+                                   "'make (i)scriptconfig', it should have "
+                                   "set up the environment correctly for you. "
+                                   "If you still got this message, that "
+                                   "might be an error, and you should email "
+                                   "ulfalizer a.t Google's email service."""
+                                   .format(stmt.name, env_var),
+                                   filename, linenr)
+
+                        stmt.cached_val = ""
                     else:
-                        stmt.cached_value = os.environ[env_var]
+                        stmt.cached_val = os.environ[env_var]
 
                 elif tokens.check(T_DEFCONFIG_LIST):
                     self.defconfig_sym = stmt
 
                 elif tokens.check(T_MODULES):
-                    self._warn("the 'modules' option is not supported. "
-                               "Let me know if this is a problem for you; "
-                               "it shouldn't be that hard to implement. "
-                               "(Note that modules are still supported -- "
-                               "Kconfiglib just assumes the symbol name "
-                               "MODULES.)",
-                               filename,
-                               linenr)
+                    # To reduce warning spam, only warn if 'option modules' is
+                    # set on some symbol that isn't MODULES, which should be
+                    # safe. I haven't run into any projects that make use
+                    # modules besides the kernel yet, and there it's likely to
+                    # keep being called "MODULES".
+                    if stmt.name != "MODULES":
+                        self._warn("the 'modules' option is not supported. "
+                                   "Let me know if this is a problem for you; "
+                                   "it shouldn't be that hard to implement. "
+                                   "(Note that modules are still supported -- "
+                                   "Kconfiglib just assumes the symbol name "
+                                   "MODULES, like older versions of the C "
+                                   "implementation did when 'option modules' "
+                                   "wasn't used.)",
+                                   filename, linenr)
 
                 elif tokens.check(T_ALLNOCONFIG_Y):
                     if not isinstance(stmt, Symbol):
                         _parse_error(line,
-                                     "the 'allnoconfig_y' option is only valid for symbols.",
-                                     filename,
-                                     linenr)
+                                     "the 'allnoconfig_y' option is only "
+                                     "valid for symbols",
+                                     filename, linenr)
                     stmt.allnoconfig_y = True
 
                 else:
-                    _parse_error(line, "unrecognized option.", filename, linenr)
+                    _parse_error(line, "unrecognized option", filename, linenr)
+
+            elif t0 == T_VISIBLE:
+                if not tokens.check(T_IF):
+                    _parse_error(line, 'expected "if" after "visible"',
+                                 filename, linenr)
+                if not isinstance(stmt, Menu):
+                    _parse_error(line,
+                                 "'visible if' is only valid for menus",
+                                 filename, linenr)
+
+                parsed_deps = self._parse_expr(tokens, stmt, line, filename,
+                                               linenr)
+                stmt.visible_if_expr = _make_and(stmt.visible_if_expr,
+                                                 parsed_deps)
+
+            elif t0 == T_OPTIONAL:
+                if not isinstance(stmt, Choice):
+                    _parse_error(line,
+                                 '"optional" is only valid for choices',
+                                 filename,
+                                 linenr)
+                stmt.optional = True
 
             else:
                 # See comment in Config.__init__()
@@ -1358,123 +1030,367 @@ error, and you should e-mail kconfiglib at gmail.com.
                 self.end_line_tokens = tokens
                 break
 
-        # Propagate dependencies from enclosing menus and if's.
+        # Done parsing properties. Now propagate 'depends on' and enclosing
+        # menu/if dependencies to expressions.
 
-        # For menus and comments..
-        if isinstance(stmt, (Menu, Comment)):
-            stmt.orig_deps = stmt.dep_expr
-            stmt.deps_from_containing = deps
-            stmt.dep_expr = _make_and(stmt.dep_expr, deps)
+        # The set of symbols referenced directly by the statement plus all
+        # symbols referenced by enclosing menus and ifs
+        stmt.all_referenced_syms = stmt.referenced_syms | _get_expr_syms(deps)
 
-            stmt.all_referenced_syms = \
-              stmt.referenced_syms | _get_expr_syms(deps)
+        # Save original dependencies from enclosing menus and ifs
+        stmt.deps_from_containing = deps
 
-        # For symbols and choices..
+        if isinstance(stmt, (Menu, Comment)):
+            stmt.dep_expr = _make_and(stmt.orig_deps, deps)
         else:
+            # Symbol or Choice
 
             # See comment for 'menu_dep'
             stmt.menu_dep = depends_on_expr
 
-            # Propagate dependencies specified with 'depends on' to any new
-            # default expressions, prompts, and selections. ("New" since a
-            # symbol might be defined in multiple places and the dependencies
-            # should only apply to the local definition.)
-
-            new_def_exprs = [(val_expr, _make_and(cond_expr, depends_on_expr))
-                             for (val_expr, cond_expr) in new_def_exprs]
-
-            new_selects = [(target, _make_and(cond_expr, depends_on_expr))
-                           for (target, cond_expr) in new_selects]
+            # Propagate dependencies to prompts
 
             if new_prompt is not None:
+                # Propagate 'visible if' dependencies from enclosing menus
                 prompt, cond_expr = new_prompt
-
-                # 'visible if' dependencies from enclosing menus get propagated
-                # to prompts
-                if visible_if_deps is not None:
-                    cond_expr = _make_and(cond_expr, visible_if_deps)
-
+                cond_expr = _make_and(cond_expr, visible_if_deps)
+                # Propagate 'depends on' dependencies
                 new_prompt = (prompt, _make_and(cond_expr, depends_on_expr))
+                # Save original
+                stmt.orig_prompts.append(new_prompt)
+                # Finalize with dependencies from enclosing menus and ifs
+                stmt.prompts.append((new_prompt[0],
+                                     _make_and(new_prompt[1], deps)))
 
-            # We save the original expressions -- before any menu and if
-            # conditions have been propagated -- so these can be retrieved
-            # later.
+            # Propagate dependencies to defaults
 
+            # Propagate 'depends on' dependencies
+            new_def_exprs = [(val_expr, _make_and(cond_expr, depends_on_expr))
+                             for val_expr, cond_expr in new_def_exprs]
+            # Save original
             stmt.orig_def_exprs.extend(new_def_exprs)
-            if new_prompt is not None:
-                stmt.orig_prompts.append(new_prompt)
+            # Finalize with dependencies from enclosing menus and ifs
+            stmt.def_exprs.extend([(val_expr, _make_and(cond_expr, deps))
+                                   for val_expr, cond_expr in new_def_exprs])
+
+            # Propagate dependencies to selects
 
             # Only symbols can select
             if isinstance(stmt, Symbol):
+                # Propagate 'depends on' dependencies
+                new_selects = [(target, _make_and(cond_expr, depends_on_expr))
+                               for target, cond_expr in new_selects]
+                # Save original
                 stmt.orig_selects.extend(new_selects)
+                # Finalize with dependencies from enclosing menus and ifs
+                for target, cond in new_selects:
+                    target.rev_dep = _make_or(target.rev_dep,
+                                              _make_and(stmt,
+                                                        _make_and(cond, deps)))
 
-            # Save dependencies from enclosing menus and if's
-            stmt.deps_from_containing = deps
+    def _parse_expr(self, feed, cur_item, line, filename=None, linenr=None,
+                    transform_m=True):
+        """Parses an expression from the tokens in 'feed' using a simple
+        top-down approach. The result has the form
+        '(<operator>, [<parsed operands>])', where <operator> is e.g.
+        kconfiglib.AND. If there is only one operand (i.e., no && or ||), then
+        the operand is returned directly. This also goes for subexpressions.
 
-            # The set of symbols referenced directly by the symbol/choice plus
-            # all symbols referenced by enclosing menus and if's.
-            stmt.all_referenced_syms = \
-              stmt.referenced_syms | _get_expr_syms(deps)
+        feed: _Feed instance containing the tokens for the expression.
 
-            # Propagate dependencies from enclosing menus and if's
+        cur_item: The item (Symbol, Choice, Menu, or Comment) currently being
+           parsed, or None if we're not parsing an item. Used for recording
+           references to symbols.
 
-            stmt.def_exprs.extend([(val_expr, _make_and(cond_expr, deps))
-                                   for (val_expr, cond_expr) in new_def_exprs])
+        line: The line containing the expression being parsed.
 
-            for (target, cond) in new_selects:
-                target.rev_dep = _make_or(target.rev_dep,
-                                          _make_and(stmt,
-                                                    _make_and(cond, deps)))
+        filename (default: None): The file containing the expression.
 
-            if new_prompt is not None:
-                prompt, cond_expr = new_prompt
-                stmt.prompts.append((prompt, _make_and(cond_expr, deps)))
+        linenr (default: None): The line number containing the expression.
 
-    #
-    # Symbol table manipulation
-    #
+        transform_m (default: False): Determines if 'm' should be rewritten to
+           'm && MODULES' -- see parse_val_and_cond().
+
+        Expression grammar, in decreasing order of precedence:
+
+        <expr> -> <symbol>
+                  <symbol> '=' <symbol>
+                  <symbol> '!=' <symbol>
+                  '(' <expr> ')'
+                  '!' <expr>
+                  <expr> '&&' <expr>
+                  <expr> '||' <expr>"""
+
+        # Use instance variables to avoid having to pass these as arguments
+        # through the top-down parser in _parse_expr_rec(), which is tedious
+        # and obfuscates the code. A profiler run shows no noticeable
+        # performance difference.
+        self._cur_item = cur_item
+        self._transform_m = transform_m
+        self._line = line
+        self._filename = filename
+        self._linenr = linenr
+
+        return self._parse_expr_rec(feed)
+
+    def _parse_expr_rec(self, feed):
+        or_term = self._parse_or_term(feed)
+        if not feed.check(T_OR):
+            # Common case -- no need for an OR node since it's just a single
+            # operand
+            return or_term
+        or_terms = [or_term, self._parse_or_term(feed)]
+        while feed.check(T_OR):
+            or_terms.append(self._parse_or_term(feed))
+        return (OR, or_terms)
+
+    def _parse_or_term(self, feed):
+        and_term = self._parse_factor(feed)
+        if not feed.check(T_AND):
+            # Common case -- no need for an AND node since it's just a single
+            # operand
+            return and_term
+        and_terms = [and_term, self._parse_factor(feed)]
+        while feed.check(T_AND):
+            and_terms.append(self._parse_factor(feed))
+        return (AND, and_terms)
+
+    def _parse_factor(self, feed):
+        token = feed.get_next()
+
+        if isinstance(token, (Symbol, str)):
+            if self._cur_item is not None and isinstance(token, Symbol):
+                self._cur_item.referenced_syms.add(token)
+
+            next_token = feed.peek_next()
+            # For conditional expressions ('depends on <expr>',
+            # '... if <expr>', # etc.), "m" and m are rewritten to
+            # "m" && MODULES.
+            if next_token != T_EQUAL and next_token != T_UNEQUAL:
+                if self._transform_m and (token is self.m or token == "m"):
+                    return (AND, ["m", self._sym_lookup("MODULES")])
+                return token
+
+            relation = EQUAL if (feed.get_next() == T_EQUAL) else UNEQUAL
+            token_2 = feed.get_next()
+            if self._cur_item is not None and isinstance(token_2, Symbol):
+                self._cur_item.referenced_syms.add(token_2)
+            return (relation, token, token_2)
+
+        if token == T_NOT:
+            return (NOT, self._parse_factor(feed))
+
+        if token == T_OPEN_PAREN:
+            expr_parse = self._parse_expr_rec(feed)
+            if not feed.check(T_CLOSE_PAREN):
+                _parse_error(self._line, "missing end parenthesis",
+                             self._filename, self._linenr)
+            return expr_parse
+
+        _parse_error(self._line, "malformed expression", self._filename,
+                     self._linenr)
+
+    def _tokenize(self, s, for_eval, filename=None, linenr=None):
+        """Returns a _Feed instance containing tokens derived from the string
+        's'. Registers any new symbols encountered (via _sym_lookup()).
+
+        (I experimented with a pure regular expression implementation, but it
+        came out slower, less readable, and wouldn't have been as flexible.)
+
+        for_eval: True when parsing an expression for a call to Config.eval(),
+           in which case we should not treat the first token specially nor
+           register new symbols."""
+
+        s = s.strip()
+        if s == "" or s[0] == "#":
+            return _Feed([])
+
+        if for_eval:
+            previous = None # The previous token seen
+            tokens = []
+            i = 0 # The current index in the string being tokenized
+
+        else:
+            # The initial word on a line is parsed specially. Let
+            # command_chars = [A-Za-z0-9_]. Then
+            #  - leading non-command_chars characters are ignored, and
+            #  - the first token consists the following one or more
+            #    command_chars characters.
+            # This is why things like "----help--" are accepted.
+            initial_token_match = _initial_token_re_match(s)
+            if initial_token_match is None:
+                return _Feed([])
+            keyword = _get_keyword(initial_token_match.group(1))
+            if keyword == T_HELP:
+                # Avoid junk after "help", e.g. "---", being registered as a
+                # symbol
+                return _Feed([T_HELP])
+            if keyword is None:
+                # We expect a keyword as the first token
+                _tokenization_error(s, filename, linenr)
+
+            previous = keyword
+            tokens = [keyword]
+            # The current index in the string being tokenized
+            i = initial_token_match.end()
+
+        # _tokenize() is a hotspot during parsing, and this speeds things up a
+        # bit
+        strlen = len(s)
+        append = tokens.append
+
+        # Main tokenization loop. (Handles tokens past the first one.)
+        while i < strlen:
+            # Test for an identifier/keyword preceded by whitespace first; this
+            # is the most common case.
+            id_keyword_match = _id_keyword_re_match(s, i)
+            if id_keyword_match:
+                # We have an identifier or keyword. The above also stripped any
+                # whitespace for us.
+                name = id_keyword_match.group(1)
+                # Jump past it
+                i = id_keyword_match.end()
+
+                keyword = _get_keyword(name)
+                if keyword is not None:
+                    # It's a keyword
+                    append(keyword)
+                elif previous in STRING_LEX:
+                    # What would ordinarily be considered an identifier is
+                    # treated as a string after certain tokens
+                    append(name)
+                else:
+                    # It's a symbol name. _sym_lookup() will take care of
+                    # allocating a new Symbol instance if it's the first time
+                    # we see it.
+                    sym = self._sym_lookup(name, for_eval)
+
+                    if previous == T_CONFIG or previous == T_MENUCONFIG:
+                        # If the previous token is T_(MENU)CONFIG
+                        # ("(menu)config"), we're tokenizing the first line of
+                        # a symbol definition, and should remember this as a
+                        # location where the symbol is defined
+                        sym.def_locations.append((filename, linenr))
+                    else:
+                        # Otherwise, it's a reference to the symbol
+                        sym.ref_locations.append((filename, linenr))
+
+                    append(sym)
+
+            else:
+                # Not an identifier/keyword
+
+                while i < strlen and s[i].isspace():
+                    i += 1
+                if i == strlen:
+                    break
+                c = s[i]
+                i += 1
+
+                # String literal (constant symbol)
+                if c == '"' or c == "'":
+                    if "\\" in s:
+                        # Slow path: This could probably be sped up, but it's a
+                        # very unusual case anyway.
+                        quote = c
+                        val = ""
+                        while 1:
+                            if i >= len(s):
+                                _tokenization_error(s, filename, linenr)
+                            c = s[i]
+                            if c == quote:
+                                break
+                            if c == "\\":
+                                if i + 1 >= len(s):
+                                    _tokenization_error(s, filename, linenr)
+                                val += s[i + 1]
+                                i += 2
+                            else:
+                                val += c
+                                i += 1
+                        i += 1
+                        append(val)
+                    else:
+                        # Fast path: If the string contains no backslashes
+                        # (almost always) we can simply look for the matching
+                        # quote.
+                        end = s.find(c, i)
+                        if end == -1:
+                            _tokenization_error(s, filename, linenr)
+                        append(s[i:end])
+                        i = end + 1
+
+                elif c == "&":
+                    # Invalid characters are ignored
+                    if i >= len(s) or s[i] != "&": continue
+                    append(T_AND)
+                    i += 1
+
+                elif c == "|":
+                    # Invalid characters are ignored
+                    if i >= len(s) or s[i] != "|": continue
+                    append(T_OR)
+                    i += 1
+
+                elif c == "!":
+                    if i < len(s) and s[i] == "=":
+                        append(T_UNEQUAL)
+                        i += 1
+                    else:
+                        append(T_NOT)
+
+                elif c == "=": append(T_EQUAL)
+                elif c == "(": append(T_OPEN_PAREN)
+                elif c == ")": append(T_CLOSE_PAREN)
+                elif c == "#": break # Comment
 
-    def _sym_lookup(self, name, add_sym_if_not_exists = True):
-        """Fetches the symbol 'name' from the symbol table, optionally adding
-        it if it does not exist (this is usually what we want)."""
+                else: continue # Invalid characters are ignored
+
+            previous = tokens[-1]
+
+        return _Feed(tokens)
+
+    def _sym_lookup(self, name, for_eval=False):
+        """Fetches the symbol 'name' from the symbol table, creating and
+        registering it if it does not exist. If 'for_eval' is True, the symbol
+        won't be added to the symbol table if it does not exist -- this is for
+        Config.eval()."""
         if name in self.syms:
             return self.syms[name]
 
         new_sym = Symbol()
         new_sym.config = self
         new_sym.name = name
-
-        if add_sym_if_not_exists:
-            self.syms[name] = new_sym
-        else:
-            # This warning is generated while evaluating an expression
-            # containing undefined symbols using Config.eval()
+        if for_eval:
             self._warn("no symbol {0} in configuration".format(name))
-
+        else:
+            self.syms[name] = new_sym
         return new_sym
 
     #
-    # Evaluation of symbols and expressions
+    # Expression evaluation
     #
 
     def _eval_expr(self, expr):
-        """Evaluates an expression and returns one of the tristate values "n",
-        "m" or "y"."""
-        res = self._eval_expr_2(expr)
-
-        # Promote "m" to "y" if we're running without modules. Internally, "m"
-        # is often rewritten to "m" && MODULES by both the C implementation and
-        # kconfiglib, which takes care of cases where "m" should be false if
-        # we're running without modules.
-        if res == "m" and not self._has_modules():
-            return "y"
-
-        return res
+        """Evaluates an expression to "n", "m", or "y"."""
 
-    def _eval_expr_2(self, expr):
+        # Handles e.g. an "x if y" condition where the "if y" part is missing.
         if expr is None:
             return "y"
 
+        res = self._eval_expr_rec(expr)
+        if res == "m":
+            # Promote "m" to "y" if we're running without modules.
+            #
+            # Internally, "m" is often rewritten to "m" && MODULES by both the
+            # C implementation and Kconfiglib, which takes care of cases where
+            # "m" should be demoted to "n" instead.
+            modules_sym = self.syms.get("MODULES")
+            if modules_sym is None or modules_sym.get_value() != "y":
+                return "y"
+        return res
+
+    def _eval_expr_rec(self, expr):
         if isinstance(expr, Symbol):
             # Non-bool/tristate symbols are always "n" in a tristate sense,
             # regardless of their value
@@ -1485,89 +1401,65 @@ error, and you should e-mail kconfiglib at gmail.com.
         if isinstance(expr, str):
             return expr if (expr == "y" or expr == "m") else "n"
 
-        first_expr = expr[0]
-
-        if first_expr == OR:
-            res = "n"
-
-            for subexpr in expr[1]:
-                ev = self._eval_expr_2(subexpr)
-
-                # Return immediately upon discovering a "y" term
-                if ev == "y":
-                    return "y"
-
-                if ev == "m":
-                    res = "m"
-
-            # 'res' is either "n" or "m" here; we already handled the
-            # short-circuiting "y" case in the loop.
-            return res
+        # Ordered by frequency
 
-        if first_expr == AND:
+        if expr[0] == AND:
             res = "y"
-
             for subexpr in expr[1]:
-                ev = self._eval_expr_2(subexpr)
-
+                ev = self._eval_expr_rec(subexpr)
                 # Return immediately upon discovering an "n" term
                 if ev == "n":
                     return "n"
-
                 if ev == "m":
                     res = "m"
-
             # 'res' is either "m" or "y" here; we already handled the
             # short-circuiting "n" case in the loop.
             return res
 
-        if first_expr == NOT:
-            ev = self._eval_expr_2(expr[1])
-
+        if expr[0] == NOT:
+            ev = self._eval_expr_rec(expr[1])
             if ev == "y":
                 return "n"
-
             return "y" if (ev == "n") else "m"
 
-        if first_expr == EQUAL:
-            return "y" if (self._get_str_value(expr[1]) ==
-                           self._get_str_value(expr[2])) else "n"
-
-        if first_expr == UNEQUAL:
-            return "y" if (self._get_str_value(expr[1]) !=
-                           self._get_str_value(expr[2])) else "n"
-
-        _internal_error("Internal error while evaluating expression: "
-                        "unknown operation {0}.".format(first_expr))
+        if expr[0] == OR:
+            res = "n"
+            for subexpr in expr[1]:
+                ev = self._eval_expr_rec(subexpr)
+                # Return immediately upon discovering a "y" term
+                if ev == "y":
+                    return "y"
+                if ev == "m":
+                    res = "m"
+            # 'res' is either "n" or "m" here; we already handled the
+            # short-circuiting "y" case in the loop.
+            return res
 
-    def _get_str_value(self, obj):
-        if isinstance(obj, str):
-            return obj
-        # obj is a Symbol
-        return obj.get_value()
+        if expr[0] == EQUAL:
+            return "y" if (_str_val(expr[1]) == _str_val(expr[2])) else "n"
+
+        if expr[0] == UNEQUAL:
+            return "y" if (_str_val(expr[1]) != _str_val(expr[2])) else "n"
+
+        _internal_error("Internal error while evaluating expression: "
+                        "unknown operation {0}.".format(expr[0]))
 
     def _eval_min(self, e1, e2):
+        """Returns the minimum value of the two expressions. Equates None with
+        'y'."""
         e1_eval = self._eval_expr(e1)
         e2_eval = self._eval_expr(e2)
-
         return e1_eval if tri_less(e1_eval, e2_eval) else e2_eval
 
     def _eval_max(self, e1, e2):
+        """Returns the maximum value of the two expressions. Equates None with
+        'y'."""
         e1_eval = self._eval_expr(e1)
         e2_eval = self._eval_expr(e2)
-
         return e1_eval if tri_greater(e1_eval, e2_eval) else e2_eval
 
     #
-    # Methods related to the MODULES symbol
-    #
-
-    def _has_modules(self):
-        modules_sym = self.syms.get("MODULES")
-        return (modules_sym is not None) and (modules_sym.get_value() == "y")
-
-    #
-    # Dependency tracking
+    # Dependency tracking (for caching and invalidation)
     #
 
     def _build_dep(self):
@@ -1577,8 +1469,6 @@ error, and you should e-mail kconfiglib at gmail.com.
         for caching/invalidation purposes. The calculated sets might be larger
         than necessary as we don't do any complicated analysis of the
         expressions."""
-        for sym in self.syms.itervalues():
-            sym.dep = set()
 
         # Adds 'sym' as a directly dependent symbol to all symbols that appear
         # in the expression 'e'
@@ -1593,62 +1483,121 @@ error, and you should e-mail kconfiglib at gmail.com.
         #    (these won't be included in 'dep' as that makes the dependency
         #    graph unwieldy, but Symbol._get_dependent() will include them)
         #  - Any symbols in a choice statement that depends on the symbol
-        for sym in self.syms.itervalues():
-            for (_, e) in sym.prompts:
+        for sym in self.syms_iter():
+            for _, e in sym.prompts:
                 add_expr_deps(e, sym)
 
-            for (v, e) in sym.def_exprs:
+            for v, e in sym.def_exprs:
                 add_expr_deps(v, sym)
                 add_expr_deps(e, sym)
 
             add_expr_deps(sym.rev_dep, sym)
 
-            for (l, u, e) in sym.ranges:
+            for l, u, e in sym.ranges:
                 add_expr_deps(l, sym)
                 add_expr_deps(u, sym)
                 add_expr_deps(e, sym)
 
-            if sym.is_choice_symbol_:
+            if sym.is_choice_sym:
                 choice = sym.parent
-
-                for (_, e) in choice.prompts:
+                for _, e in choice.prompts:
                     add_expr_deps(e, sym)
-
-                for (_, e) in choice.def_exprs:
+                for _, e in choice.def_exprs:
                     add_expr_deps(e, sym)
 
-    def _expr_val_str(self, expr, no_value_str = "(none)", get_val_instead_of_eval = False):
-        # Since values are valid expressions, _expr_to_str() will get a nice
-        # string representation for those as well.
+    def _eq_to_sym(self, eq):
+        """_expr_depends_on() helper. For (in)equalities of the form sym = y/m
+        or sym != n, returns sym. For other (in)equalities, returns None."""
+        relation, left, right = eq
+
+        def transform_y_m_n(item):
+            if item is self.y: return "y"
+            if item is self.m: return "m"
+            if item is self.n: return "n"
+            return item
+
+        left = transform_y_m_n(left)
+        right = transform_y_m_n(right)
+
+        # Make sure the symbol (if any) appears to the left
+        if not isinstance(left, Symbol):
+            left, right = right, left
+        if not isinstance(left, Symbol):
+            return None
+        if (relation == EQUAL and (right == "y" or right == "m")) or \
+           (relation == UNEQUAL and right == "n"):
+            return left
+        return None
 
+    def _expr_depends_on(self, expr, sym):
+        """Reimplementation of expr_depends_symbol() from mconf.c. Used to
+        determine if a submenu should be implicitly created, which influences
+        what items inside choice statements are considered choice items."""
         if expr is None:
-            return no_value_str
+            return False
 
-        if get_val_instead_of_eval:
+        def rec(expr):
             if isinstance(expr, str):
-                return _expr_to_str(expr)
-            val = expr.get_value()
-        else:
-            val = self._eval_expr(expr)
+                return False
+            if isinstance(expr, Symbol):
+                return expr is sym
 
-        return "{0} (value: {1})".format(_expr_to_str(expr), _expr_to_str(val))
+            if expr[0] in (EQUAL, UNEQUAL):
+                return self._eq_to_sym(expr) is sym
+            if expr[0] == AND:
+                for and_expr in expr[1]:
+                    if rec(and_expr):
+                        return True
+            return False
+
+        return rec(expr)
+
+    def _invalidate_all(self):
+        for sym in self.syms_iter():
+            sym._invalidate()
+
+    #
+    # Printing and misc.
+    #
 
     def _expand_sym_refs(self, s):
         """Expands $-references to symbols in 's' to symbol values, or to the
         empty string for undefined symbols."""
 
         while 1:
-            sym_ref_re_match = sym_ref_re.search(s)
-            if sym_ref_re_match is None:
+            sym_ref_match = _sym_ref_re_search(s)
+            if sym_ref_match is None:
                 return s
 
-            sym_name = sym_ref_re_match.group(0)[1:]
+            sym_name = sym_ref_match.group(0)[1:]
             sym = self.syms.get(sym_name)
             expansion = "" if sym is None else sym.get_value()
 
-            s = s[:sym_ref_re_match.start()] + \
+            s = s[:sym_ref_match.start()] + \
                 expansion + \
-                s[sym_ref_re_match.end():]
+                s[sym_ref_match.end():]
+
+    def _expr_val_str(self, expr, no_value_str="(none)",
+                      get_val_instead_of_eval=False):
+        """Printing helper. Returns a string with 'expr' and its value.
+
+        no_value_str: String to return when 'expr' is missing (None).
+
+        get_val_instead_of_eval: Assume 'expr' is a symbol or string (constant
+          symbol) and get its value directly instead of evaluating it to a
+          tristate value."""
+
+        if expr is None:
+            return no_value_str
+
+        if get_val_instead_of_eval:
+            if isinstance(expr, str):
+                return _expr_to_str(expr)
+            val = expr.get_value()
+        else:
+            val = self._eval_expr(expr)
+
+        return "{0} (value: {1})".format(_expr_to_str(expr), _expr_to_str(val))
 
     def _get_sym_or_choice_str(self, sc):
         """Symbols and choices have many properties in common, so we factor out
@@ -1662,679 +1611,300 @@ error, and you should e-mail kconfiglib at gmail.com.
         # Common symbol/choice properties
         #
 
-        user_value_str = "(no user value)" if sc.user_val is None else s(sc.user_val)
-
-        visibility_str = s(sc.get_visibility())
+        user_val_str = "(no user value)" if sc.user_val is None else \
+                       s(sc.user_val)
 
         # Build prompts string
-        if sc.prompts == []:
+        if not sc.prompts:
             prompts_str = " (no prompts)"
         else:
             prompts_str_rows = []
-
-            for (prompt, cond_expr) in sc.orig_prompts:
+            for prompt, cond_expr in sc.orig_prompts:
                 if cond_expr is None:
                     prompts_str_rows.append(' "{0}"'.format(prompt))
                 else:
-                    prompts_str_rows.append(' "{0}" if '.format(prompt) +
-                                            self._expr_val_str(cond_expr))
-
+                    prompts_str_rows.append(
+                      ' "{0}" if {1}'.format(prompt,
+                                             self._expr_val_str(cond_expr)))
             prompts_str = "\n".join(prompts_str_rows)
 
         # Build locations string
-        if sc.def_locations == []:
+        if not sc.def_locations:
             locations_str = "(no locations)"
         else:
             locations_str = " ".join(["{0}:{1}".format(filename, linenr) for
                                       (filename, linenr) in sc.def_locations])
 
-        # Build additional-dependencies-from-menus-and-if's string
-        additional_deps_str = " " + self._expr_val_str(sc.deps_from_containing,
-                                                       "(no additional dependencies)")
+        # Build additional-dependencies-from-menus-and-ifs string
+        additional_deps_str = " " + \
+          self._expr_val_str(sc.deps_from_containing,
+                             "(no additional dependencies)")
 
         #
         # Symbol-specific stuff
         #
 
         if isinstance(sc, Symbol):
-
-            # Build value string
-            value_str = s(sc.get_value())
-
             # Build ranges string
             if isinstance(sc, Symbol):
-                if sc.ranges == []:
+                if not sc.ranges:
                     ranges_str = " (no ranges)"
                 else:
                     ranges_str_rows = []
-
-                    for (l, u, cond_expr) in sc.ranges:
+                    for l, u, cond_expr in sc.ranges:
                         if cond_expr is None:
-                            ranges_str_rows.append(" [{0}, {1}]".format(s(l), s(u)))
+                            ranges_str_rows.append(" [{0}, {1}]".format(s(l),
+                                                                        s(u)))
                         else:
                             ranges_str_rows.append(" [{0}, {1}] if {2}"
-                                                   .format(s(l), s(u), self._expr_val_str(cond_expr)))
-
+                              .format(s(l), s(u),
+                                      self._expr_val_str(cond_expr)))
                     ranges_str = "\n".join(ranges_str_rows)
 
             # Build default values string
-            if sc.def_exprs == []:
+            if not sc.def_exprs:
                 defaults_str = " (no default values)"
             else:
                 defaults_str_rows = []
-
-                for (val_expr, cond_expr) in sc.orig_def_exprs:
-                    row_str = " " + self._expr_val_str(val_expr, "(none)", sc.type == STRING)
+                for val_expr, cond_expr in sc.orig_def_exprs:
+                    row_str = " " + self._expr_val_str(val_expr, "(none)",
+                                                       sc.type == STRING)
                     defaults_str_rows.append(row_str)
-                    defaults_str_rows.append("  Condition: " + self._expr_val_str(cond_expr))
-
+                    defaults_str_rows.append("  Condition: " +
+                                               self._expr_val_str(cond_expr))
                 defaults_str = "\n".join(defaults_str_rows)
 
             # Build selects string
-            if sc.orig_selects == []:
+            if not sc.orig_selects:
                 selects_str = " (no selects)"
-            else:
-                selects_str_rows = []
-
-                for (target, cond_expr) in sc.orig_selects:
-                    if cond_expr is None:
-                        selects_str_rows.append(" {0}".format(target.name))
-                    else:
-                        selects_str_rows.append(" {0} if ".format(target.name) +
-                                                self._expr_val_str(cond_expr))
-
-                selects_str = "\n".join(selects_str_rows)
-
-            # Build reverse dependencies string
-            if sc.rev_dep == "n":
-                rev_dep_str = " (no reverse dependencies)"
-            else:
-                rev_dep_str = " " + self._expr_val_str(sc.rev_dep)
-
-            res = _sep_lines("Symbol " + (sc.name if sc.name is not None else "(no name)"),
-                             "Type           : " + typename[sc.type],
-                             "Value          : " + value_str,
-                             "User value     : " + user_value_str,
-                             "Visibility     : " + visibility_str,
-                             "Is choice item : " + bool_str[sc.is_choice_symbol_],
-                             "Is defined     : " + bool_str[sc.is_defined_],
-                             "Is from env.   : " + bool_str[sc.is_from_env],
-                             "Is special     : " + bool_str[sc.is_special_] + "\n")
-
-            if sc.ranges != []:
-                res += _sep_lines("Ranges:",
-                                  ranges_str + "\n")
-
-            res += _sep_lines("Prompts:",
-                              prompts_str,
-                              "Default values:",
-                              defaults_str,
-                              "Selects:",
-                              selects_str,
-                              "Reverse dependencies:",
-                              rev_dep_str,
-                              "Additional dependencies from enclosing menus and if's:",
-                              additional_deps_str,
-                              "Locations: " + locations_str)
-
-            return res
-
-        #
-        # Choice-specific stuff
-        #
-
-        # Build name string (for named choices)
-        if sc.name is None:
-            name_str = "(no name)"
-        else:
-            name_str = sc.name
-
-        # Build selected symbol string
-        sel = sc.get_selection()
-        if sel is None:
-            sel_str = "(no selection)"
-        else:
-            sel_str = sel.name
-
-        # Build mode string
-        mode_str = s(sc.get_mode())
-
-        # Build default values string
-        if sc.def_exprs == []:
-            defaults_str = " (no default values)"
-        else:
-            defaults_str_rows = []
-
-            for (sym, cond_expr) in sc.orig_def_exprs:
-                if cond_expr is None:
-                    defaults_str_rows.append(" {0}".format(sym.name))
-                else:
-                    defaults_str_rows.append(" {0} if ".format(sym.name) +
-                                             self._expr_val_str(cond_expr))
-
-            defaults_str = "\n".join(defaults_str_rows)
-
-        # Build contained symbols string
-        names = [sym.name for sym in sc.get_symbols()]
-
-        if names == []:
-            syms_string = "(empty)"
-        else:
-            syms_string = " ".join(names)
-
-        return _sep_lines("Choice",
-                          "Name (for named choices): " + name_str,
-                          "Type            : " + typename[sc.type],
-                          "Selected symbol : " + sel_str,
-                          "User value      : " + user_value_str,
-                          "Mode            : " + mode_str,
-                          "Visibility      : " + visibility_str,
-                          "Optional        : " + bool_str[sc.optional],
-                          "Prompts:",
-                          prompts_str,
-                          "Defaults:",
-                          defaults_str,
-                          "Choice symbols:",
-                          " " + syms_string,
-                          "Additional dependencies from enclosing menus and if's:",
-                          additional_deps_str,
-                          "Locations: " + locations_str)
-
-    def _expr_depends_on(self, expr, sym):
-        """Reimplementation of expr_depends_symbol() from mconf.c. Used to
-        determine if a submenu should be implicitly created, which influences what
-        items inside choice statements are considered choice items."""
-        if expr is None:
-            return False
-
-        def rec(expr):
-            if isinstance(expr, str):
-                return False
-
-            if isinstance(expr, Symbol):
-                return expr is sym
-
-            e0 = expr[0]
-
-            if e0 == EQUAL or e0 == UNEQUAL:
-                return self._eq_to_sym(expr) is sym
-
-            if e0 == AND:
-                for and_expr in expr[1]:
-                    if rec(and_expr):
-                        return True
-
-            return False
-
-        return rec(expr)
-
-    def _eq_to_sym(self, eq):
-        """_expr_depends_on() helper. For (in)equalities of the form sym = y/m
-        or sym != n, returns sym. For other (in)equalities, returns None."""
-        relation, left, right = eq
-
-        left  = self._transform_n_m_y(left)
-        right = self._transform_n_m_y(right)
-
-        # Make sure the symbol (if any) appears to the left
-        if not isinstance(left, Symbol):
-            left, right = right, left
-
-        if not isinstance(left, Symbol):
-            return None
-
-        if (relation == EQUAL   and (right == "m" or right == "y")) or \
-           (relation == UNEQUAL and right == "n"):
-            return left
-
-        return None
-
-    def _transform_n_m_y(self, item):
-        """_eq_to_sym() helper. Translates the symbols n, m, and y to their
-        string equivalents."""
-        if item is self.n:
-            return "n"
-        if item is self.m:
-            return "m"
-        if item is self.y:
-            return "y"
-        return item
-
-    def _warn(self, msg, filename = None, linenr = None):
-        """For printing warnings to stderr."""
-        if self.print_warnings:
-            self._warn_or_undef_assign(msg, WARNING, filename, linenr)
-
-    def _undef_assign(self, msg, filename = None, linenr = None):
-        """For printing informational messages related to assignments
-        to undefined variables to stderr."""
-        if self.print_undef_assign:
-            self._warn_or_undef_assign(msg, UNDEF_ASSIGN, filename, linenr)
-
-    def _warn_or_undef_assign(self, msg, msg_type, filename, linenr):
-        if filename is not None:
-            sys.stderr.write("{0}:".format(_clean_up_path(filename)))
-        if linenr is not None:
-            sys.stderr.write("{0}:".format(linenr))
-
-        if msg_type == WARNING:
-            sys.stderr.write("warning: ")
-        elif msg_type == UNDEF_ASSIGN:
-            sys.stderr.write("info: ")
-        else:
-            _internal_error('Internal error while printing warning: unknown warning type "{0}".'
-                            .format(msg_type))
-
-        sys.stderr.write(msg + "\n")
-
-def _get_expr_syms(expr):
-    """Returns the set() of symbols appearing in expr."""
-    res = set()
-    if expr is None:
-        return res
-
-    def rec(expr):
-        if isinstance(expr, Symbol):
-            res.add(expr)
-            return
-
-        if isinstance(expr, str):
-            return
-
-        e0 = expr[0]
-
-        if e0 == OR or e0 == AND:
-            for term in expr[1]:
-                rec(term)
-
-        elif e0 == NOT:
-            rec(expr[1])
-
-        elif e0 == EQUAL or e0 == UNEQUAL:
-            _, v1, v2 = expr
-
-            if isinstance(v1, Symbol):
-                res.add(v1)
-
-            if isinstance(v2, Symbol):
-                res.add(v2)
-
-        else:
-            _internal_error("Internal error while fetching symbols from an "
-                            "expression with token stream {0}.".format(expr))
-
-    rec(expr)
-    return res
-
-
-#
-# Construction of expressions
-#
-
-# These functions as well as the _eval_min/max() functions above equate
-# None with "y", which is usually what we want, but needs to be kept in
-# mind.
-
-def _make_or(e1, e2):
-    # Perform trivial simplification and avoid None's (which
-    # correspond to y's)
-    if e1 is None or e2 is None or \
-       e1 == "y" or e2 == "y":
-        return "y"
-
-    if e1 == "n":
-        return e2
-
-    if e2 == "n":
-        return e1
-
-    # Prefer to merge/update argument list if possible instead of creating
-    # a new OR node
-
-    if isinstance(e1, tuple) and e1[0] == OR:
-        if isinstance(e2, tuple) and e2[0] == OR:
-            return (OR, e1[1] + e2[1])
-        return (OR, e1[1] + [e2])
-
-    if isinstance(e2, tuple) and e2[0] == OR:
-        return (OR, e2[1] + [e1])
-
-    return (OR, [e1, e2])
-
-# Note: returns None if e1 == e2 == None
-
-def _make_and(e1, e2):
-    if e1 == "n" or e2 == "n":
-        return "n"
-
-    if e1 is None or e1 == "y":
-        return e2
-
-    if e2 is None or e2 == "y":
-        return e1
-
-    # Prefer to merge/update argument list if possible instead of creating
-    # a new AND node
-
-    if isinstance(e1, tuple) and e1[0] == AND:
-        if isinstance(e2, tuple) and e2[0] == AND:
-            return (AND, e1[1] + e2[1])
-        return (AND, e1[1] + [e2])
-
-    if isinstance(e2, tuple) and e2[0] == AND:
-        return (AND, e2[1] + [e1])
-
-    return (AND, [e1, e2])
-
-#
-# Constants and functions related to types, parsing, evaluation and printing,
-# put globally to unclutter the Config class a bit.
-#
-
-# Tokens
-(T_OR, T_AND, T_NOT,
- T_OPEN_PAREN, T_CLOSE_PAREN,
- T_EQUAL, T_UNEQUAL,
- T_MAINMENU, T_MENU, T_ENDMENU,
- T_SOURCE, T_CHOICE, T_ENDCHOICE,
- T_COMMENT, T_CONFIG, T_MENUCONFIG,
- T_HELP, T_IF, T_ENDIF, T_DEPENDS, T_ON,
- T_OPTIONAL, T_PROMPT, T_DEFAULT,
- T_BOOL, T_TRISTATE, T_HEX, T_INT, T_STRING,
- T_DEF_BOOL, T_DEF_TRISTATE,
- T_SELECT, T_RANGE, T_OPTION, T_ALLNOCONFIG_Y, T_ENV,
- T_DEFCONFIG_LIST, T_MODULES, T_VISIBLE) = range(0, 39)
-
-# Keyword to token map
-keywords = {
-        "mainmenu"       : T_MAINMENU,
-        "menu"           : T_MENU,
-        "endmenu"        : T_ENDMENU,
-        "endif"          : T_ENDIF,
-        "endchoice"      : T_ENDCHOICE,
-        "source"         : T_SOURCE,
-        "choice"         : T_CHOICE,
-        "config"         : T_CONFIG,
-        "comment"        : T_COMMENT,
-        "menuconfig"     : T_MENUCONFIG,
-        "help"           : T_HELP,
-        "if"             : T_IF,
-        "depends"        : T_DEPENDS,
-        "on"             : T_ON,
-        "optional"       : T_OPTIONAL,
-        "prompt"         : T_PROMPT,
-        "default"        : T_DEFAULT,
-        "bool"           : T_BOOL,
-        "boolean"        : T_BOOL,
-        "tristate"       : T_TRISTATE,
-        "int"            : T_INT,
-        "hex"            : T_HEX,
-        "def_bool"       : T_DEF_BOOL,
-        "def_tristate"   : T_DEF_TRISTATE,
-        "string"         : T_STRING,
-        "select"         : T_SELECT,
-        "range"          : T_RANGE,
-        "option"         : T_OPTION,
-        "allnoconfig_y"  : T_ALLNOCONFIG_Y,
-        "env"            : T_ENV,
-        "defconfig_list" : T_DEFCONFIG_LIST,
-        "modules"        : T_MODULES,
-        "visible"        : T_VISIBLE }
-
-# Strings to use for True and False
-bool_str = { False : "false", True : "true" }
-
-# Tokens after which identifier-like lexemes are treated as strings. T_CHOICE
-# is included to avoid symbols being registered for named choices.
-string_lex = frozenset((T_BOOL, T_TRISTATE, T_INT, T_HEX, T_STRING, T_CHOICE,
-                        T_PROMPT, T_MENU, T_COMMENT, T_SOURCE, T_MAINMENU))
-
-# Matches the initial token on a line; see _tokenize().
-initial_token_re = re.compile(r"[^\w]*(\w+)")
-
-# Matches an identifier/keyword optionally preceded by whitespace
-id_keyword_re = re.compile(r"\s*([\w./-]+)")
-
-# Regular expressions for parsing .config files
-set_re   = re.compile(r"CONFIG_(\w+)=(.*)")
-unset_re = re.compile(r"# CONFIG_(\w+) is not set")
-
-# Regular expression for finding $-references to symbols in strings
-sym_ref_re = re.compile(r"\$[A-Za-z0-9_]+")
-
-# Integers representing symbol types
-UNKNOWN, BOOL, TRISTATE, STRING, HEX, INT = range(0, 6)
-
-# Strings to use for types
-typename = {
-        UNKNOWN  : "unknown",
-        BOOL     : "bool",
-        TRISTATE : "tristate",
-        STRING   : "string",
-        HEX      : "hex",
-        INT      : "int" }
-
-# Token to type mapping
-token_to_type = { T_BOOL     : BOOL,
-                  T_TRISTATE : TRISTATE,
-                  T_STRING   : STRING,
-                  T_INT      : INT,
-                  T_HEX      : HEX }
-
-# Default values for symbols of different types (the value the symbol gets if
[… more lines in the raw message]

Lines of this file are not shown. View the raw message.

The rest of this diff is not shown. View the raw message.

Signature · 1 line
2.1.4

Part of the diff is not shown here. View the raw message.

#3 lore raw

PATCH v2 1/1 kconfiglib: update to the latest version

In reply to Ulf Magnusson
Quoted 6 lines · Ulf Magnusson

Corresponds to ba71a0e (Fix _parse_block() 'parent' documentation re. ifs.) from upstream, just adding the SPDX tag.

Has performance improvements, code cleanup, Python 3 support, and various small fixes, including the following:

Quoted 6 lines

- Unset user values when loading a zero-byte .config. (5e54e2c)
- Ignore indented .config assignments. (f8a7510)
- Do not require $srctree to be set for non-kernel projects. (d56e9c1)
- Report correct locations in the presence of continuation lines.

    (0cebc87)

Applied to u-boot/master, thanks!

Signature · 8 lines
Tom
-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 836 bytes
Desc: Digital signature
URL: <http://lists.denx.de/pipermail/u-boot/attachments/20150828/6920e59d/attachment.sig>