Change expansion and function resolution to yield results instead of joining them early. I'm not actually sure this is helping performance, but I'm committing it because, hey, with version control you can go back in time!
authorBenjamin Smedberg <benjamin@smedbergs.us>
Thu, 26 Feb 2009 07:38:10 -0500
changeset 180 79707812e432
parent 179 56f8cbd6b7b7
child 183 aeb76d986c59
push id104
push userbsmedberg@mozilla.com
push dateThu, 26 Feb 2009 12:38:22 +0000
Change expansion and function resolution to yield results instead of joining them early. I'm not actually sure this is helping performance, but I'm committing it because, hey, with version control you can go back in time!
pymake/data.py
pymake/functions.py
pymake/parserdata.py
--- a/pymake/data.py
+++ b/pymake/data.py
@@ -126,18 +126,25 @@ class Expansion(object):
         @param setting (Variable instance) the variable currently
                being set, if any. Setting variables must avoid self-referential
                loops.
         """
         assert isinstance(makefile, Makefile)
         assert isinstance(variables, Variables)
         assert isinstance(setting, list)
 
-        return ''.join( (_if_else(isinstance(i, str), lambda: i, lambda: i.resolve(makefile, variables, setting))
-                         for i in self._elements) )
+        for i in self._elements:
+            if isinstance(i, str):
+                yield i
+            else:
+                for j in i.resolve(makefile, variables, setting):
+                    yield j
+                    
+    def resolvestr(self, makefile, variables, setting=[]):
+        return ''.join(self.resolve(makefile, variables, setting))
 
     def __len__(self):
         return len(self._elements)
 
     def __getitem__(self, key):
         return self._elements[key]
 
     def __setitem__(self, key, v):
@@ -281,17 +288,17 @@ class Variables(object):
             # TODO: log a warning?
             return
 
         if prevflavor == self.FLAVOR_SIMPLE:
             exp, err = expand()
             if err is not None:
                 raise err
 
-            val = exp.resolve(makefile, variables, [name])
+            val = exp.resolvestr(makefile, variables, [name])
             self._map[name] = prevflavor, prevsource, prevvalue + ' ' + val, None, None
             return
 
         newvalue = prevvalue + ' ' + value
         try:
             d = parser.Data.fromstring(newvalue, parserdata.Location("Expansion of variable '%s'" % (name,), 1, 0))
             valueexp, t, o = parser.parsemakesyntax(d, 0, (), parser.iterdata)
             err = None
@@ -633,17 +640,17 @@ class Target(object):
             self.vpathtarget = self.target
             self.mtime = None
             return
 
         if self.target.startswith('-l'):
             stem = self.target[2:]
             f, s, e = makefile.variables.get('.LIBPATTERNS')
             if e is not None:
-                libpatterns = [Pattern(stripdotslash(s)) for s in e.resolve(makefile, makefile.variables).split()]
+                libpatterns = [Pattern(stripdotslash(s)) for s in e.resolvestr(makefile, makefile.variables).split()]
                 if len(libpatterns):
                     searchdirs = ['']
                     searchdirs.extend(makefile.getvpath(self.target))
 
                     for lp in libpatterns:
                         if not lp.ispattern():
                             raise DataError('.LIBPATTERNS contains a non-pattern')
 
@@ -953,17 +960,17 @@ def getcommandsforrule(rule, target, mak
     v = Variables(parent=target.variables)
     setautomaticvariables(v, makefile, target, prerequisites)
     if stem is not None:
         setautomatic(v, '*', [stem])
 
     env = makefile.getsubenvironment(v)
 
     for c in rule.commands:
-        cstring = c.resolve(makefile, v)
+        cstring = c.resolvestr(makefile, v)
         for cline in splitcommand(cstring):
             cline, isHidden, isRecursive, ignoreErrors = findmodifiers(cline)
             if isHidden:
                 echo = None
             else:
                 echo = "%s$ %s" % (c.loc, cline)
             yield CommandWrapper(cline, ignoreErrors=ignoreErrors, env=env, cwd=makefile.workdir, loc=c.loc, context=makefile.context,
                                  echo=echo)
@@ -1170,24 +1177,24 @@ class Makefile(object):
         Various activities, such as "eval", are not allowed after parsing is
         finished. In addition, various warnings and errors can only be issued
         after the parsing data model is complete. All dependency resolution
         and rule execution requires that parsing be finished.
         """
         self.parsingfinished = True
 
         flavor, source, value = self.variables.get('GPATH')
-        if value is not None and value.resolve(self, self.variables, ['GPATH']).strip() != '':
+        if value is not None and value.resolvestr(self, self.variables, ['GPATH']).strip() != '':
             raise DataError('GPATH was set: pymake does not support GPATH semantics')
 
         flavor, source, value = self.variables.get('VPATH')
         if value is None:
             self._vpath = []
         else:
-            self._vpath = filter(lambda e: e != '', re.split('[:\s]+', value.resolve(self, self.variables, ['VPATH'])))
+            self._vpath = filter(lambda e: e != '', re.split('[:\s]+', value.resolvestr(self, self.variables, ['VPATH'])))
 
         targets = list(self._targets.itervalues())
         for t in targets:
             t.explicit = True
             for r in t.rules:
                 for p in r.prerequisites:
                     self.gettarget(p).explicit = True
 
@@ -1265,24 +1272,24 @@ class Makefile(object):
 
     def getsubenvironment(self, variables):
         env = dict(self.env)
         for vname in self.exportedvars:
             flavor, source, val = variables.get(vname)
             if val is None:
                 strval = ''
             else:
-                strval = val.resolve(self, variables, [vname])
+                strval = val.resolvestr(self, variables, [vname])
             env[vname] = strval
 
         makeflags = ''
 
         flavor, source, val = variables.get('MAKEFLAGS')
         if val is not None:
-            flagsval = val.resolve(self, variables, ['MAKEFLAGS'])
+            flagsval = val.resolvestr(self, variables, ['MAKEFLAGS'])
             if flagsval != '':
                 makeflags = flagsval
 
         makeflags += ' -- '
         makeflags += ' '.join((self.flagescape.sub(r'\\\1', o) for o in self.overrides))
 
         env['MAKEFLAGS'] = makeflags
 
--- a/pymake/functions.py
+++ b/pymake/functions.py
@@ -16,17 +16,17 @@ class Function(object):
     An object that represents a function call. This class is always subclassed
     with the following methods and attributes:
 
     minargs = minimum # of arguments
     maxargs = maximum # of arguments (0 means unlimited)
 
     def resolve(self, makefile, variables, setting)
         Calls the function
-        @returns string
+        @yields strings
     """
     def __init__(self, loc):
         self._arguments = []
         self.loc = loc
         assert self.minargs > 0
 
     def __getitem__(self, key):
         return self._arguments[key]
@@ -51,214 +51,215 @@ class VariableRef(Function):
         self.loc = loc
         assert isinstance(vname, data.Expansion)
         self.vname = vname
         
     def setup(self):
         assert False, "Shouldn't get here"
 
     def resolve(self, makefile, variables, setting):
-        vname = self.vname.resolve(makefile, variables, setting)
+        vname = self.vname.resolvestr(makefile, variables, setting)
         if vname in setting:
             raise data.DataError("Setting variable '%s' recursively references itself." % (vname,), self.loc)
 
         flavor, source, value = variables.get(vname)
         if value is None:
             log.debug("%s: variable '%s' was not set" % (self.loc, vname))
-            return ''
+            return
 
-        return value.resolve(makefile, variables, setting + [vname])
+        for j in value.resolve(makefile, variables, setting + [vname]):
+            yield j
 
 class SubstitutionRef(Function):
     """$(VARNAME:.c=.o) and $(VARNAME:%.c=%.o)"""
     def __init__(self, loc, varname, substfrom, substto):
         self.loc = loc
         self.vname = varname
         self.substfrom = substfrom
         self.substto = substto
 
     def setup(self):
         assert False, "Shouldn't get here"
 
     def resolve(self, makefile, variables, setting):
-        vname = self.vname.resolve(makefile, variables, setting)
+        vname = self.vname.resolvestr(makefile, variables, setting)
         if vname in setting:
             raise data.DataError("Setting variable '%s' recursively references itself." % (vname,), self.loc)
 
-        substfrom = self.substfrom.resolve(makefile, variables, setting)
-        substto = self.substto.resolve(makefile, variables, setting)
+        substfrom = self.substfrom.resolvestr(makefile, variables, setting)
+        substto = self.substto.resolvestr(makefile, variables, setting)
 
         flavor, source, value = variables.get(vname)
         if value is None:
             log.debug("%s: variable '%s' was not set" % (self.loc, vname))
-            return ''
+            return
 
-        evalue = value.resolve(makefile, variables, setting + [vname])
+        evalue = value.resolvestr(makefile, variables, setting + [vname])
 
         f = data.Pattern(substfrom)
         if not f.ispattern():
             f = data.Pattern('%' + substfrom)
             substto = '%' + substto
 
-        return " ".join((f.subst(substto, word, False)
-                         for word in evalue.split()))
+        yield " ".join((f.subst(substto, word, False)
+                        for word in evalue.split()))
 
 class SubstFunction(Function):
     name = 'subst'
     minargs = 3
     maxargs = 3
 
     def resolve(self, makefile, variables, setting):
-        s = self._arguments[0].resolve(makefile, variables, setting)
-        r = self._arguments[1].resolve(makefile, variables, setting)
-        d = self._arguments[2].resolve(makefile, variables, setting)
-        return d.replace(s, r)
+        s = self._arguments[0].resolvestr(makefile, variables, setting)
+        r = self._arguments[1].resolvestr(makefile, variables, setting)
+        d = self._arguments[2].resolvestr(makefile, variables, setting)
+        yield d.replace(s, r)
 
 class PatSubstFunction(Function):
     name = 'patsubst'
     minargs = 3
     maxargs = 3
 
     def resolve(self, makefile, variables, setting):
-        s = self._arguments[0].resolve(makefile, variables, setting)
-        r = self._arguments[1].resolve(makefile, variables, setting)
-        d = self._arguments[2].resolve(makefile, variables, setting)
+        s = self._arguments[0].resolvestr(makefile, variables, setting)
+        r = self._arguments[1].resolvestr(makefile, variables, setting)
+        d = self._arguments[2].resolvestr(makefile, variables, setting)
 
         p = data.Pattern(s)
-        return ' '.join((p.subst(r, word, False)
-                         for word in d.split()))
+        yield ' '.join((p.subst(r, word, False)
+                        for word in d.split()))
 
 class StripFunction(Function):
     name = 'strip'
     minargs = 1
     maxargs = 1
 
     def resolve(self, makefile, variables, setting):
-        return ' '.join(self._arguments[0].resolve(makefile, variables, setting).split())
+        yield ' '.join(self._arguments[0].resolvestr(makefile, variables, setting).split())
 
 class FindstringFunction(Function):
     name = 'findstring'
     minargs = 2
     maxargs = 2
 
     def resolve(self, makefile, variables, setting):
-        s = self._arguments[0].resolve(makefile, variables, setting)
-        r = self._arguments[1].resolve(makefile, variables, setting)
+        s = self._arguments[0].resolvestr(makefile, variables, setting)
+        r = self._arguments[1].resolvestr(makefile, variables, setting)
         if r.find(s) == -1:
-            return ''
-        return s
+            return
+        yield s
 
 class FilterFunction(Function):
     name = 'filter'
     minargs = 2
     maxargs = 2
 
     def resolve(self, makefile, variables, setting):
-        ps = self._arguments[0].resolve(makefile, variables, setting)
-        d = self._arguments[1].resolve(makefile, variables, setting)
+        ps = self._arguments[0].resolvestr(makefile, variables, setting)
+        d = self._arguments[1].resolvestr(makefile, variables, setting)
         plist = [data.Pattern(p) for p in ps.split()]
         r = []
         for w in d.split():
             if any((p.match(w) for p in plist)):
                     r.append(w)
                 
-        return ' '.join(r)
+        yield ' '.join(r)
 
 class FilteroutFunction(Function):
     name = 'filter-out'
     minargs = 2
     maxargs = 2
 
     def resolve(self, makefile, variables, setting):
-        ps = self._arguments[0].resolve(makefile, variables, setting)
-        d = self._arguments[1].resolve(makefile, variables, setting)
+        ps = self._arguments[0].resolvestr(makefile, variables, setting)
+        d = self._arguments[1].resolvestr(makefile, variables, setting)
         plist = [data.Pattern(p) for p in ps.split()]
         r = []
         for w in d.split():
             found = False
             if not any((p.match(w) for p in plist)):
                 r.append(w)
 
-        return ' '.join(r)
+        yield ' '.join(r)
 
 class SortFunction(Function):
     name = 'sort'
     minargs = 1
     maxargs = 1
 
     def resolve(self, makefile, variables, setting):
-        d = self._arguments[0].resolve(makefile, variables, setting)
+        d = self._arguments[0].resolvestr(makefile, variables, setting)
         w = d.split()
         w.sort()
-        return ' '.join((w for w in data.withoutdups(w)))
+        yield ' '.join((w for w in data.withoutdups(w)))
 
 class WordFunction(Function):
     name = 'word'
     minargs = 2
     maxargs = 2
 
     def resolve(self, makefile, variables, setting):
-        n = self._arguments[0].resolve(makefile, variables, setting)
+        n = self._arguments[0].resolvestr(makefile, variables, setting)
         # TODO: provide better error if this doesn't convert
         n = int(n)
-        words = self._arguments[1].resolve(makefile, variables, setting).split()
+        words = self._arguments[1].resolvestr(makefile, variables, setting).split()
         if n < 1 or n > len(words):
-            return ''
-        return words[n - 1]
+            return
+        yield words[n - 1]
 
 class WordlistFunction(Function):
     name = 'wordlist'
     minargs = 3
     maxargs = 3
 
     def resolve(self, makefile, variables, setting):
-        nfrom = self._arguments[0].resolve(makefile, variables, setting)
-        nto = self._arguments[1].resolve(makefile, variables, setting)
+        nfrom = self._arguments[0].resolvestr(makefile, variables, setting)
+        nto = self._arguments[1].resolvestr(makefile, variables, setting)
         # TODO: provide better errors if this doesn't convert
         nfrom = int(nfrom)
         nto = int(nto)
 
-        words = self._arguments[2].resolve(makefile, variables, setting).split()
+        words = self._arguments[2].resolvestr(makefile, variables, setting).split()
 
         if nfrom < 1:
             nfrom = 1
         if nto < 1:
             nto = 1
 
-        return ' '.join(words[nfrom - 1:nto])
+        yield ' '.join(words[nfrom - 1:nto])
 
 class WordsFunction(Function):
     name = 'words'
     minargs = 1
     maxargs = 1
 
     def resolve(self, makefile, variables, setting):
-        return str(len(self._arguments[0].resolve(makefile, variables, setting).split()))
+        yield str(len(self._arguments[0].resolvestr(makefile, variables, setting).split()))
 
 class FirstWordFunction(Function):
     name = 'firstword'
     minargs = 1
     maxargs = 1
 
     def resolve(self, makefile, variables, setting):
-        wl = self._arguments[0].resolve(makefile, variables, setting).split()
+        wl = self._arguments[0].resolvestr(makefile, variables, setting).split()
         if len(wl) == 0:
-            return ''
-        return wl[0]
+            return
+        yield wl[0]
 
 class LastWordFunction(Function):
     name = 'lastword'
     minargs = 1
     maxargs = 1
 
     def resolve(self, makefile, variables, setting):
-        wl = self._arguments[0].resolve(makefile, variables, setting).split()
+        wl = self._arguments[0].resolvestr(makefile, variables, setting).split()
         if len(wl) == 0:
-            return ''
-        return wl[0]
+            return
+        yield wl[0]
 
 def pathsplit(path, default='./'):
     """
     Splits a path into dirpart, filepart on the last slash. If there is no slash, dirpart
     is ./
     """
     dir, slash, file = path.rpartition('/')
     if dir == '':
@@ -267,43 +268,43 @@ def pathsplit(path, default='./'):
     return dir + slash, file
 
 class DirFunction(Function):
     name = 'dir'
     minargs = 1
     maxargs = 1
 
     def resolve(self, makefile, variables, setting):
-        return ' '.join((pathsplit(path)[0]
-                         for path in self._arguments[0].resolve(makefile, variables, setting).split()))
+        yield ' '.join((pathsplit(path)[0]
+                        for path in self._arguments[0].resolvestr(makefile, variables, setting).split()))
 
 class NotDirFunction(Function):
     name = 'notdir'
     minargs = 1
     maxargs = 1
 
     def resolve(self, makefile, variables, setting):
-        return ' '.join((pathsplit(path)[1]
-                         for path in self._arguments[0].resolve(makefile, variables, setting).split()))
+        yield ' '.join((pathsplit(path)[1]
+                        for path in self._arguments[0].resolvestr(makefile, variables, setting).split()))
 
 class SuffixFunction(Function):
     name = 'suffix'
     minargs = 1
     maxargs = 1
 
     @staticmethod
     def suffixes(words):
         for w in words:
             dir, file = pathsplit(w)
             base, dot, suffix = file.rpartition('.')
             if base != '':
                 yield dot + suffix
 
     def resolve(self, makefile, variables, setting):
-        return ' '.join(self.suffixes(self._arguments[0].resolve(makefile, variables, setting).split()))
+        yield ' '.join(self.suffixes(self._arguments[0].resolvestr(makefile, variables, setting).split()))
 
 class BasenameFunction(Function):
     name = 'basename'
     minargs = 1
     maxargs = 1
 
     @staticmethod
     def basenames(words):
@@ -311,314 +312,311 @@ class BasenameFunction(Function):
             dir, file = pathsplit(w, '')
             base, dot, suffix = file.rpartition('.')
             if dot == '':
                 base = suffix
 
             yield dir + base
 
     def resolve(self, makefile, variables, setting):
-        return ' '.join(self.basenames(self._arguments[0].resolve(makefile, variables, setting).split()))
+        yield ' '.join(self.basenames(self._arguments[0].resolvestr(makefile, variables, setting).split()))
 
 class AddSuffixFunction(Function):
     name = 'addprefix'
     minargs = 2
     maxargs = 2
 
     def resolve(self, makefile, variables, setting):
-        suffix = self._arguments[0].resolve(makefile, variables, setting)
+        suffix = self._arguments[0].resolvestr(makefile, variables, setting)
 
-        return ' '.join((w + suffix for w in self._arguments[1].resolve(makefile, variables, setting).split()))
+        yield ' '.join((w + suffix for w in self._arguments[1].resolvestr(makefile, variables, setting).split()))
 
 class AddPrefixFunction(Function):
     name = 'addsuffix'
     minargs = 2
     maxargs = 2
 
     def resolve(self, makefile, variables, setting):
-        prefix = self._arguments[0].resolve(makefile, variables, setting)
+        prefix = self._arguments[0].resolvestr(makefile, variables, setting)
 
-        return ' '.join((prefix + w for w in self._arguments[1].resolve(makefile, variables, setting).split()))
+        yield ' '.join((prefix + w for w in self._arguments[1].resolvestr(makefile, variables, setting).split()))
 
 class JoinFunction(Function):
     name = 'join'
     minargs = 2
     maxargs = 2
 
     @staticmethod
     def iterjoin(l1, l2):
         for i in xrange(0, max(len(l1), len(l2))):
             i1 = i < len(l1) and l1[i] or ''
             i2 = i < len(l2) and l2[i] or ''
             yield i1 + i2
 
     def resolve(self, makefile, variables, setting):
-        list1 = self._arguments[0].resolve(makefile, variables, setting).split()
-        list2 = self._arguments[1].resolve(makefile, variables, setting).split()
+        list1 = self._arguments[0].resolvestr(makefile, variables, setting).split()
+        list2 = self._arguments[1].resolvestr(makefile, variables, setting).split()
 
-        return ' '.join(self.iterjoin(list1, list2))
+        yield ' '.join(self.iterjoin(list1, list2))
 
 class WildcardFunction(Function):
     name = 'wildcard'
     minargs = 1
     maxargs = 1
 
     def resolve(self, makefile, variables, setting):
-        patterns = self._arguments[0].resolve(makefile, variables, setting).split()
+        patterns = self._arguments[0].resolvestr(makefile, variables, setting).split()
 
         r = []
         for p in patterns:
             r.extend([x.replace('\\','/') for x in glob(makefile.workdir, p)])
-        return ' '.join(r)
+        yield ' '.join(r)
 
 class RealpathFunction(Function):
     name = 'realpath'
     minargs = 1
     maxargs = 1
 
     def resolve(self, makefile, variables, setting):
-        paths = self._arguments[0].resolve(makefile, variables, setting).split()
+        paths = self._arguments[0].resolvestr(makefile, variables, setting).split()
         fspaths = [os.path.join(makefile.workdir, path) for path in paths]
         realpaths = [os.path.realpath(path).replace('\\','/') for path in fspaths]
-        return ' '.join(realpaths)
+        yield ' '.join(realpaths)
 
 class AbspathFunction(Function):
     name = 'abspath'
     minargs = 1
     maxargs = 1
 
     def resolve(self, makefile, variables, setting):
         assert os.path.isabs(makefile.workdir)
-        paths = self._arguments[0].resolve(makefile, variables, setting).split()
+        paths = self._arguments[0].resolvestr(makefile, variables, setting).split()
         fspaths = [os.path.join(makefile.workdir, path).replace('\\','/') for path in paths]
-        return ' '.join(fspaths)
+        yield ' '.join(fspaths)
 
 class IfFunction(Function):
     name = 'if'
     minargs = 1
     maxargs = 3
 
     def setup(self):
         Function.setup(self)
         self._arguments[0].lstrip()
         self._arguments[0].rstrip()
 
     def resolve(self, makefile, variables, setting):
-        condition = self._arguments[0].resolve(makefile, variables, setting)
+        condition = self._arguments[0].resolvestr(makefile, variables, setting)
         if len(condition):
-            return self._arguments[1].resolve(makefile, variables, setting)
-
-        if len(self._arguments) > 2:
-            return self._arguments[2].resolve(makefile, variables, setting)
-
-        return ''
+            for j in self._arguments[1].resolvestr(makefile, variables, setting):
+                yield j
+        elif len(self._arguments) > 2:
+            for j in self._arguments[2].resolvestr(makefile, variables, setting):
+                yield j
 
 class OrFunction(Function):
     name = 'or'
     minargs = 1
     maxargs = 0
 
     def resolve(self, makefile, variables, setting):
         for arg in self._arguments:
-            r = arg.resolve(makefile, variables, setting)
+            r = arg.resolvestr(makefile, variables, setting)
             if r != '':
-                return r
-
-        return ''
+                yield r
+                return
 
 class AndFunction(Function):
     name = 'and'
     minargs = 1
     maxargs = 0
 
     def resolve(self, makefile, variables, setting):
         r = ''
 
         for arg in self._arguments:
-            r = arg.resolve(makefile, variables, setting)
+            r = arg.resolvestr(makefile, variables, setting)
             if r == '':
-                return ''
+                return
 
-        return r
+        yield r
 
 class ForEachFunction(Function):
     name = 'foreach'
     minargs = 3
     maxargs = 3
 
     def resolve(self, makefile, variables, setting):
-        vname = self._arguments[0].resolve(makefile, variables, setting)
+        vname = self._arguments[0].resolvestr(makefile, variables, setting)
+        words = self._arguments[1].resolvestr(makefile, variables, setting).split()
 
-        words = self._arguments[1].resolve(makefile, variables, setting).split()
         e = self._arguments[2]
 
-        results = []
+        v = data.Variables(parent=variables)
+        for i in xrange(0, len(words)):
+            w = words[i]
+            if i > 0:
+                yield ' '
 
-        v = data.Variables(parent=variables)
-        for w in words:
             v.set(vname, data.Variables.FLAVOR_SIMPLE, data.Variables.SOURCE_AUTOMATIC, w)
-            results.append(e.resolve(makefile, v, setting))
-
-        return ' '.join(results)
+            for j in e.resolve(makefile, v, setting):
+                yield j
 
 class CallFunction(Function):
     name = 'call'
     minargs = 1
     maxargs = 0
 
     def resolve(self, makefile, variables, setting):
-        vname = self._arguments[0].resolve(makefile, variables, setting)
+        vname = self._arguments[0].resolvestr(makefile, variables, setting)
         if vname in setting:
             raise data.DataError("Recursively setting variable '%s'" % (vname,))
 
         v = data.Variables(parent=variables)
         v.set('0', data.Variables.FLAVOR_SIMPLE, data.Variables.SOURCE_AUTOMATIC, vname)
         for i in xrange(1, len(self._arguments)):
-            param = self._arguments[i].resolve(makefile, variables, setting)
+            param = self._arguments[i].resolvestr(makefile, variables, setting)
             v.set(str(i), data.Variables.FLAVOR_SIMPLE, data.Variables.SOURCE_AUTOMATIC, param)
 
         flavor, source, e = variables.get(vname)
         if e is None:
-            return ''
+            return
 
         if flavor == data.Variables.FLAVOR_SIMPLE:
             log.warning("%s: calling variable '%s' which is simply-expanded" % (self.loc, vname))
 
         # but we'll do it anyway
-        return e.resolve(makefile, v, setting + [vname])
+        for j in e.resolve(makefile, v, setting + [vname]):
+            yield j
 
 class ValueFunction(Function):
     name = 'value'
     minargs = 1
     maxargs = 1
 
     def resolve(self, makefile, variables, setting):
-        varname = self._arguments[0].resolve(makefile, variables, setting)
+        varname = self._arguments[0].resolvestr(makefile, variables, setting)
 
         flavor, source, value = variables.get(varname, expand=False)
         if value is None:
-            return ''
+            return
 
-        return value
+        yield value
 
 class EvalFunction(Function):
     name = 'eval'
     minargs = 1
     maxargs = 1
 
     def resolve(self, makefile, variables, setting):
         if makefile.parsingfinished:
             # GNU make allows variables to be set by recursive expansion during
             # command execution. This seems really dumb to me, so I don't!
             raise data.DataError("$(eval) not allowed via recursive expansion after parsing is finished", self.loc)
 
-        text = StringIO(self._arguments[0].resolve(makefile, variables, setting))
+        text = StringIO(self._arguments[0].resolvestr(makefile, variables, setting))
         stmts = parser.parsestream(text, 'evaluation from %s' % self.loc)
         stmts.execute(makefile)
-        return ''
+        return
+        yield
 
 class OriginFunction(Function):
     name = 'origin'
     minargs = 1
     maxargs = 1
 
     def resolve(self, makefile, variables, setting):
-        vname = self._arguments[0].resolve(makefile, variables, setting)
+        vname = self._arguments[0].resolvestr(makefile, variables, setting)
 
         flavor, source, value = variables.get(vname)
         if source is None:
-            return 'undefined'
-
-        if source == data.Variables.SOURCE_OVERRIDE:
-            return 'override'
-
-        if source == data.Variables.SOURCE_MAKEFILE:
-            return 'file'
-
-        if source == data.Variables.SOURCE_ENVIRONMENT:
-            return 'environment'
-
-        if source == data.Variables.SOURCE_COMMANDLINE:
-            return 'command line'
-
-        if source == data.Variables.SOURCE_AUTOMATIC:
-            return 'automatic'
-
-        assert False, "Unexpected source value: %s" % source
+            yield 'undefined'
+        elif source == data.Variables.SOURCE_OVERRIDE:
+            yield 'override'
+        elif source == data.Variables.SOURCE_MAKEFILE:
+            yield 'file'
+        elif source == data.Variables.SOURCE_ENVIRONMENT:
+            yield 'environment'
+        elif source == data.Variables.SOURCE_COMMANDLINE:
+            yield 'command line'
+        elif source == data.Variables.SOURCE_AUTOMATIC:
+            yield 'automatic'
+        else:
+            assert False, "Unexpected source value: %s" % source
 
 class FlavorFunction(Function):
     name = 'flavor'
     minargs = 1
     maxargs = 1
 
     def resolve(self, makefile, variables, setting):
-        varname = self._arguments[0].resolve(makefile, variables, setting)
+        varname = self._arguments[0].resolvestr(makefile, variables, setting)
         
         flavor, source, value = variables.get(varname)
         if flavor is None:
-            return 'undefined'
-
-        if flavor == data.Variables.FLAVOR_RECURSIVE:
-            return 'recursive'
+            yield 'undefined'
+        elif flavor == data.Variables.FLAVOR_RECURSIVE:
+            yield 'recursive'
         elif flavor == data.Variables.FLAVOR_SIMPLE:
-            return 'simple'
-
-        assert False, "Neither simple nor recursive?"
+            yield 'simple'
+        else:
+            assert False, "Neither simple nor recursive?"
 
 class ShellFunction(Function):
     name = 'shell'
     minargs = 1
     maxargs = 1
 
     def resolve(self, makefile, variables, setting):
         #TODO: call this once up-front somewhere and save the result?
         shell, msys = util.checkmsyscompat()
-        cline = self._arguments[0].resolve(makefile, variables, setting)
+        cline = self._arguments[0].resolvestr(makefile, variables, setting)
 
         log.debug("%s: running shell command '%s'" % (self.loc, cline))
         if msys:
             cline = [shell, "-c", cline]
         p = subprocess.Popen(cline, shell=not msys, stdout=subprocess.PIPE, cwd=makefile.workdir)
         stdout, stderr = p.communicate()
 
         stdout = stdout.replace('\r\n', '\n')
         if len(stdout) > 1 and stdout[-1] == '\n':
             stdout = stdout[:-1]
         stdout = stdout.replace('\n', ' ')
 
-        return stdout
+        yield stdout
 
 class ErrorFunction(Function):
     name = 'error'
     minargs = 1
     maxargs = 1
 
     def resolve(self, makefile, variables, setting):
-        v = self._arguments[0].resolve(makefile, variables, setting)
+        v = self._arguments[0].resolvestr(makefile, variables, setting)
         raise data.DataError(v, self.loc)
 
 class WarningFunction(Function):
     name = 'warning'
     minargs = 1
     maxargs = 1
 
     def resolve(self, makefile, variables, setting):
-        v = self._arguments[0].resolve(makefile, variables, setting)
+        v = self._arguments[0].resolvestr(makefile, variables, setting)
         log.warning(v)
-        return ''
+        return
+        yield
 
 class InfoFunction(Function):
     name = 'info'
     minargs = 1
     maxargs = 1
 
     def resolve(self, makefile, variables, setting):
-        v = self._arguments[0].resolve(makefile, variables, setting)
+        v = self._arguments[0].resolvestr(makefile, variables, setting)
         log.info(v)
-        return ''
+        return
+        yield
 
 functionmap = {
     'subst': SubstFunction,
     'patsubst': PatSubstFunction,
     'strip': StripFunction,
     'findstring': FindstringFunction,
     'filter': FilterFunction,
     'filter-out': FilteroutFunction,
--- a/pymake/parserdata.py
+++ b/pymake/parserdata.py
@@ -110,29 +110,29 @@ class Rule(Statement):
         assert isinstance(targetexp, data.Expansion)
         assert isinstance(depexp, data.Expansion)
         
         self.targetexp = targetexp
         self.depexp = depexp
         self.doublecolon = doublecolon
 
     def execute(self, makefile, context):
-        atargets = data.stripdotslashes(self.targetexp.resolve(makefile, makefile.variables).split())
+        atargets = data.stripdotslashes(self.targetexp.resolvestr(makefile, makefile.variables).split())
         targets = [data.Pattern(p) for p in _expandwildcards(makefile, atargets)]
 
         if not len(targets):
             context.currule = DummyRule()
             return
 
         ispatterns = set((t.ispattern() for t in targets))
         if len(ispatterns) == 2:
             raise data.DataError("Mixed implicit and normal rule", self.targetexp.loc)
         ispattern, = ispatterns
 
-        deps = [p for p in _expandwildcards(makefile, data.stripdotslashes(self.depexp.resolve(makefile, makefile.variables).split()))]
+        deps = [p for p in _expandwildcards(makefile, data.stripdotslashes(self.depexp.resolvestr(makefile, makefile.variables).split()))]
         if ispattern:
             rule = data.PatternRule(targets, map(data.Pattern, deps), self.doublecolon, loc=self.targetexp.loc)
             makefile.appendimplicitrule(rule)
         else:
             rule = data.Rule(deps, self.doublecolon, loc=self.targetexp.loc)
             for t in targets:
                 makefile.gettarget(t.gettarget()).addrule(rule)
             makefile.foundtarget(targets[0].gettarget())
@@ -149,28 +149,28 @@ class StaticPatternRule(Statement):
         assert isinstance(depexp, data.Expansion)
 
         self.targetexp = targetexp
         self.patternexp = patternexp
         self.depexp = depexp
         self.doublecolon = doublecolon
 
     def execute(self, makefile, context):
-        targets = list(_expandwildcards(makefile, data.stripdotslashes(self.targetexp.resolve(makefile, makefile.variables).split())))
+        targets = list(_expandwildcards(makefile, data.stripdotslashes(self.targetexp.resolvestr(makefile, makefile.variables).split())))
 
         if not len(targets):
             context.currule = DummyRule()
             return
 
-        patterns = list(data.stripdotslashes(self.patternexp.resolve(makefile, makefile.variables).split()))
+        patterns = list(data.stripdotslashes(self.patternexp.resolvestr(makefile, makefile.variables).split()))
         if len(patterns) != 1:
             raise data.DataError("Static pattern rules must have a single pattern", self.patternexp.loc)
         pattern = data.Pattern(patterns[0])
 
-        deps = [data.Pattern(p) for p in _expandwildcards(makefile, data.stripdotslashes(self.depexp.resolve(makefile, makefile.variables).split()))]
+        deps = [data.Pattern(p) for p in _expandwildcards(makefile, data.stripdotslashes(self.depexp.resolvestr(makefile, makefile.variables).split()))]
 
         rule = data.PatternRule([pattern], deps, self.doublecolon, loc=self.targetexp.loc)
 
         for t in targets:
             if data.Pattern(t).ispattern():
                 raise data.DataError("Target '%s' of a static pattern rule must not be a pattern" % (t,), self.targetexp.loc)
             stem = pattern.match(t)
             if stem is None:
@@ -207,26 +207,26 @@ class SetVariable(Statement):
         self.vnameexp = vnameexp
         self.token = token
         self.value = value
         self.valueloc = valueloc
         self.targetexp = targetexp
         self.source = source
 
     def execute(self, makefile, context):
-        vname = self.vnameexp.resolve(makefile, makefile.variables)
+        vname = self.vnameexp.resolvestr(makefile, makefile.variables)
         if len(vname) == 0:
             raise data.DataError("Empty variable name", self.vnameexp.loc)
 
         if self.targetexp is None:
             setvariables = [makefile.variables]
         else:
             setvariables = []
 
-            targets = [data.Pattern(t) for t in data.stripdotslashes(self.targetexp.resolve(makefile, makefile.variables).split())]
+            targets = [data.Pattern(t) for t in data.stripdotslashes(self.targetexp.resolvestr(makefile, makefile.variables).split())]
             for t in targets:
                 if t.ispattern():
                     setvariables.append(makefile.getpatternvariables(t))
                 else:
                     setvariables.append(makefile.gettarget(t.gettarget()).variables)
 
         for v in setvariables:
             if self.token == '+=':
@@ -243,17 +243,17 @@ class SetVariable(Statement):
                 flavor = data.Variables.FLAVOR_RECURSIVE
                 value = self.value
             else:
                 assert self.token == ':='
 
                 flavor = data.Variables.FLAVOR_SIMPLE
                 d = parser.Data.fromstring(self.value, self.valueloc)
                 e, t, o = parser.parsemakesyntax(d, 0, (), parser.iterdata)
-                value = e.resolve(makefile, makefile.variables)
+                value = e.resolvestr(makefile, makefile.variables)
 
             v.set(vname, flavor, self.source, value)
 
     def dump(self, fd, indent):
         print >>fd, indent, "SetVariable %r value=%r" % (self.vnameexp, self.value)
 
 class Condition(object):
     """
@@ -268,32 +268,32 @@ class EqCondition(Condition):
     def __init__(self, exp1, exp2):
         assert isinstance(exp1, data.Expansion)
         assert isinstance(exp2, data.Expansion)
 
         self.exp1 = exp1
         self.exp2 = exp2
 
     def evaluate(self, makefile):
-        r1 = self.exp1.resolve(makefile, makefile.variables)
-        r2 = self.exp2.resolve(makefile, makefile.variables)
+        r1 = self.exp1.resolvestr(makefile, makefile.variables)
+        r2 = self.exp2.resolvestr(makefile, makefile.variables)
         return (r1 == r2) == self.expected
 
     def __str__(self):
         return "ifeq (expected=%s) %r %r" % (self.expected, self.exp1, self.exp2)
 
 class IfdefCondition(Condition):
     expected = True
 
     def __init__(self, exp):
         assert isinstance(exp, data.Expansion)
         self.exp = exp
 
     def evaluate(self, makefile):
-        vname = self.exp.resolve(makefile, makefile.variables)
+        vname = self.exp.resolvestr(makefile, makefile.variables)
         flavor, source, value = makefile.variables.get(vname, expand=False)
 
         if value is None:
             return not self.expected
 
         return (len(value) > 0) == self.expected
 
     def __str__(self):
@@ -352,30 +352,30 @@ class ConditionBlock(Statement):
 
 class Include(Statement):
     def __init__(self, exp, required):
         assert isinstance(exp, data.Expansion)
         self.exp = exp
         self.required = required
 
     def execute(self, makefile, context):
-        files = self.exp.resolve(makefile, makefile.variables).split()
+        files = self.exp.resolvestr(makefile, makefile.variables).split()
         for f in files:
             makefile.include(f, self.required, loc=self.exp.loc)
 
     def dump(self, fd, indent):
         print >>fd, indent, "Include %r" % (self.exp,)
 
 class VPathDirective(Statement):
     def __init__(self, exp):
         assert isinstance(exp, data.Expansion)
         self.exp = exp
 
     def execute(self, makefile, context):
-        words = list(data.stripdotslashes(self.exp.resolve(makefile, makefile.variables).split()))
+        words = list(data.stripdotslashes(self.exp.resolvestr(makefile, makefile.variables).split()))
         if len(words) == 0:
             makefile.clearallvpaths()
         else:
             pattern = data.Pattern(words[0])
             mpaths = words[1:]
 
             if len(mpaths) == 0:
                 makefile.clearvpath(pattern)
@@ -393,35 +393,35 @@ class VPathDirective(Statement):
 class ExportDirective(Statement):
     def __init__(self, exp, single):
         assert isinstance(exp, data.Expansion)
         self.exp = exp
         self.single = single
 
     def execute(self, makefile, context):
         if self.single:
-            vlist = [self.exp.resolve(makefile, makefile.variables)]
+            vlist = [self.exp.resolvestr(makefile, makefile.variables)]
         else:
-            vlist = self.exp.resolve(makefile, makefile.variables).split()
+            vlist = self.exp.resolvestr(makefile, makefile.variables).split()
             if not len(vlist):
                 raise data.DataError("Exporting all variables is not supported", self.exp.loc)
 
         for v in vlist:
             makefile.exportedvars.add(v)
 
     def dump(self, fd, indent):
         print >>fd, indent, "Export (single=%s) %r" % (self.single, self.exp)
 
 class EmptyDirective(Statement):
     def __init__(self, exp):
         assert isinstance(exp, data.Expansion)
         self.exp = exp
 
     def execute(self, makefile, context):
-        v = self.exp.resolve(makefile, makefile.variables)
+        v = self.exp.resolvestr(makefile, makefile.variables)
         if v.strip() != '':
             raise data.DataError("Line expands to non-empty value", self.exp.loc)
 
     def dump(self, fd, indent):
         print >>fd, indent, "EmptyDirective: %r" % self.exp
 
 class StatementList(list):
     def append(self, statement):