1# Status: ported, except for unit tests.
2# Base revision: 64488
3#
4# Copyright 2001, 2002, 2003 Dave Abrahams
5# Copyright 2002, 2006 Rene Rivera
6# Copyright 2002, 2003, 2004, 2005, 2006 Vladimir Prus
7# Distributed under the Boost Software License, Version 1.0.
8# (See accompanying file LICENSE_1_0.txt or http://www.boost.org/LICENSE_1_0.txt)
9
10import re
11
12from b2.manager import get_manager
13from b2.util import utility, bjam_signature, is_iterable_typed
14import b2.util.set
15from b2.util.utility import add_grist, get_grist, ungrist, replace_grist, to_seq
16from b2.exceptions import *
17
18__re_split_subfeatures = re.compile ('<(.*):(.*)>')
19__re_no_hyphen = re.compile ('^([^:]+)$')
20__re_slash_or_backslash = re.compile (r'[\\/]')
21
22VALID_ATTRIBUTES = {
23    'implicit',
24    'composite',
25    'optional',
26    'symmetric',
27    'free',
28    'incidental',
29    'path',
30    'dependency',
31    'propagated',
32    'link-incompatible',
33    'subfeature',
34    'order-sensitive'
35}
36
37
38class Feature(object):
39    def __init__(self, name, values, attributes):
40        assert isinstance(name, basestring)
41        assert is_iterable_typed(values, basestring)
42        assert is_iterable_typed(attributes, basestring)
43        self.name = name
44        self.values = values
45        self.default = None
46        self.subfeatures = []
47        self.parent = None
48        self.attributes_string_list = []
49        self._hash = hash(self.name)
50
51        for attr in attributes:
52            self.attributes_string_list.append(attr)
53            attr = attr.replace("-", "_")
54            setattr(self, attr, True)
55
56    def add_values(self, values):
57        assert is_iterable_typed(values, basestring)
58        self.values.extend(values)
59
60    def set_default(self, value):
61        assert isinstance(value, basestring)
62        for attr in ('free', 'optional'):
63            if getattr(self, attr):
64                get_manager().errors()('"{}" feature "<{}>" cannot have a default value.'
65                                       .format(attr, self.name))
66
67        self.default = value
68
69    def add_subfeature(self, name):
70        assert isinstance(name, Feature)
71        self.subfeatures.append(name)
72
73    def set_parent(self, feature, value):
74        assert isinstance(feature, Feature)
75        assert isinstance(value, basestring)
76        self.parent = (feature, value)
77
78    def __hash__(self):
79        return self._hash
80
81    def __str__(self):
82        return self.name
83
84
85def reset ():
86    """ Clear the module state. This is mainly for testing purposes.
87    """
88    global __all_attributes, __all_features, __implicit_features, __composite_properties
89    global __subfeature_from_value, __all_top_features, __free_features
90    global __all_subfeatures
91
92    # sets the default value of False for each valid attribute
93    for attr in VALID_ATTRIBUTES:
94        setattr(Feature, attr.replace("-", "_"), False)
95
96    # A map containing all features. The key is the feature name.
97    # The value is an instance of Feature class.
98    __all_features = {}
99
100    # All non-subfeatures.
101    __all_top_features = []
102
103    # Maps valus to the corresponding implicit feature
104    __implicit_features = {}
105
106    # A map containing all composite properties. The key is a Property instance,
107    # and the value is a list of Property instances
108    __composite_properties = {}
109
110    # Maps a value to the corresponding subfeature name.
111    __subfeature_from_value = {}
112
113    # All free features
114    __free_features = []
115
116    __all_subfeatures = []
117
118reset ()
119
120def enumerate ():
121    """ Returns an iterator to the features map.
122    """
123    return __all_features.iteritems ()
124
125def get(name):
126    """Return the Feature instance for the specified name.
127
128    Throws if no feature by such name exists
129    """
130    assert isinstance(name, basestring)
131    return __all_features[name]
132
133# FIXME: prepare-test/finish-test?
134
135@bjam_signature((["name"], ["values", "*"], ["attributes", "*"]))
136def feature (name, values, attributes = []):
137    """ Declares a new feature with the given name, values, and attributes.
138        name: the feature name
139        values: a sequence of the allowable values - may be extended later with feature.extend
140        attributes: a sequence of the feature's attributes (e.g. implicit, free, propagated, ...)
141    """
142    __validate_feature_attributes (name, attributes)
143
144    feature = Feature(name, [], attributes)
145    __all_features[name] = feature
146    # Temporary measure while we have not fully moved from 'gristed strings'
147    __all_features["<" + name + ">"] = feature
148
149    name = add_grist(name)
150
151    if 'subfeature' in attributes:
152        __all_subfeatures.append(name)
153    else:
154        __all_top_features.append(feature)
155
156    extend (name, values)
157
158    # FIXME: why his is needed.
159    if 'free' in attributes:
160        __free_features.append (name)
161
162    return feature
163
164@bjam_signature((["feature"], ["value"]))
165def set_default (feature, value):
166    """ Sets the default value of the given feature, overriding any previous default.
167        feature: the name of the feature
168        value: the default value to assign
169    """
170    f = __all_features[feature]
171    bad_attribute = None
172
173    if f.free:
174        bad_attribute = "free"
175    elif f.optional:
176        bad_attribute = "optional"
177
178    if bad_attribute:
179        raise InvalidValue ("%s property %s cannot have a default" % (bad_attribute, f.name))
180
181    if value not in f.values:
182        raise InvalidValue ("The specified default value, '%s' is invalid.\n" % value + "allowed values are: %s" % f.values)
183
184    f.set_default(value)
185
186def defaults(features):
187    """ Returns the default property values for the given features.
188    """
189    assert is_iterable_typed(features, Feature)
190    # FIXME: should merge feature and property modules.
191    from . import property
192
193    result = []
194    for f in features:
195        if not f.free and not f.optional and f.default:
196            result.append(property.Property(f, f.default))
197
198    return result
199
200def valid (names):
201    """ Returns true iff all elements of names are valid features.
202    """
203    if isinstance(names, str):
204        names = [names]
205        assert is_iterable_typed(names, basestring)
206
207    return all(name in __all_features for name in names)
208
209def attributes (feature):
210    """ Returns the attributes of the given feature.
211    """
212    assert isinstance(feature, basestring)
213    return __all_features[feature].attributes_string_list
214
215def values (feature):
216    """ Return the values of the given feature.
217    """
218    assert isinstance(feature, basestring)
219    validate_feature (feature)
220    return __all_features[feature].values
221
222def is_implicit_value (value_string):
223    """ Returns true iff 'value_string' is a value_string
224    of an implicit feature.
225    """
226    assert isinstance(value_string, basestring)
227    if value_string in __implicit_features:
228        return __implicit_features[value_string]
229
230    v = value_string.split('-')
231
232    if v[0] not in __implicit_features:
233        return False
234
235    feature = __implicit_features[v[0]]
236
237    for subvalue in (v[1:]):
238        if not __find_implied_subfeature(feature, subvalue, v[0]):
239            return False
240
241    return True
242
243def implied_feature (implicit_value):
244    """ Returns the implicit feature associated with the given implicit value.
245    """
246    assert isinstance(implicit_value, basestring)
247    components = implicit_value.split('-')
248
249    if components[0] not in __implicit_features:
250        raise InvalidValue ("'%s' is not a value of an implicit feature" % implicit_value)
251
252    return __implicit_features[components[0]]
253
254def __find_implied_subfeature (feature, subvalue, value_string):
255    assert isinstance(feature, Feature)
256    assert isinstance(subvalue, basestring)
257    assert isinstance(value_string, basestring)
258
259    try:
260        return __subfeature_from_value[feature][value_string][subvalue]
261    except KeyError:
262        return None
263
264# Given a feature and a value of one of its subfeatures, find the name
265# of the subfeature. If value-string is supplied, looks for implied
266# subfeatures that are specific to that value of feature
267#  feature             # The main feature name
268#  subvalue            # The value of one of its subfeatures
269#  value-string        # The value of the main feature
270
271def implied_subfeature (feature, subvalue, value_string):
272    assert isinstance(feature, Feature)
273    assert isinstance(subvalue, basestring)
274    assert isinstance(value_string, basestring)
275    result = __find_implied_subfeature (feature, subvalue, value_string)
276    if not result:
277        raise InvalidValue ("'%s' is not a known subfeature value of '%s%s'" % (subvalue, feature, value_string))
278
279    return result
280
281def validate_feature (name):
282    """ Checks if all name is a valid feature. Otherwise, raises an exception.
283    """
284    assert isinstance(name, basestring)
285    if name not in __all_features:
286        raise InvalidFeature ("'%s' is not a valid feature name" % name)
287    else:
288        return __all_features[name]
289
290
291# Uses Property
292def __expand_subfeatures_aux (property_, dont_validate = False):
293    """ Helper for expand_subfeatures.
294        Given a feature and value, or just a value corresponding to an
295        implicit feature, returns a property set consisting of all component
296        subfeatures and their values. For example:
297
298          expand_subfeatures <toolset>gcc-2.95.2-linux-x86
299              -> <toolset>gcc <toolset-version>2.95.2 <toolset-os>linux <toolset-cpu>x86
300          equivalent to:
301              expand_subfeatures gcc-2.95.2-linux-x86
302
303        feature:        The name of the feature, or empty if value corresponds to an implicit property
304        value:          The value of the feature.
305        dont_validate:  If True, no validation of value string will be done.
306    """
307    from . import property  # no __debug__ since Property is used elsewhere
308    assert isinstance(property_, property.Property)
309    assert isinstance(dont_validate, int)  # matches bools
310
311    f = property_.feature
312    v = property_.value
313    if not dont_validate:
314        validate_value_string(f, v)
315
316    components = v.split ("-")
317
318    v = components[0]
319
320    result = [property.Property(f, components[0])]
321
322    subvalues = components[1:]
323
324    while len(subvalues) > 0:
325        subvalue = subvalues [0]    # pop the head off of subvalues
326        subvalues = subvalues [1:]
327
328        subfeature = __find_implied_subfeature (f, subvalue, v)
329
330        # If no subfeature was found, reconstitute the value string and use that
331        if not subfeature:
332            return [property.Property(f, '-'.join(components))]
333
334        result.append(property.Property(subfeature, subvalue))
335
336    return result
337
338def expand_subfeatures(properties, dont_validate = False):
339    """
340    Make all elements of properties corresponding to implicit features
341    explicit, and express all subfeature values as separate properties
342    in their own right. For example, the property
343
344       gcc-2.95.2-linux-x86
345
346    might expand to
347
348      <toolset>gcc <toolset-version>2.95.2 <toolset-os>linux <toolset-cpu>x86
349
350    properties:     A sequence with elements of the form
351                    <feature>value-string or just value-string in the
352                    case of implicit features.
353  : dont_validate:  If True, no validation of value string will be done.
354    """
355    if __debug__:
356        from .property import Property
357        assert is_iterable_typed(properties, Property)
358        assert isinstance(dont_validate, int)  # matches bools
359    result = []
360    for p in properties:
361        # Don't expand subfeatures in subfeatures
362        if p.feature.subfeature:
363            result.append (p)
364        else:
365            result.extend(__expand_subfeatures_aux (p, dont_validate))
366
367    return result
368
369
370
371# rule extend was defined as below:
372    # Can be called three ways:
373    #
374    #    1. extend feature : values *
375    #    2. extend <feature> subfeature : values *
376    #    3. extend <feature>value-string subfeature : values *
377    #
378    # * Form 1 adds the given values to the given feature
379    # * Forms 2 and 3 add subfeature values to the given feature
380    # * Form 3 adds the subfeature values as specific to the given
381    #   property value-string.
382    #
383    #rule extend ( feature-or-property subfeature ? : values * )
384#
385# Now, the specific rule must be called, depending on the desired operation:
386#   extend_feature
387#   extend_subfeature
388@bjam_signature([['name'], ['values', '*']])
389def extend (name, values):
390    """ Adds the given values to the given feature.
391    """
392    assert isinstance(name, basestring)
393    assert is_iterable_typed(values, basestring)
394    name = add_grist (name)
395    __validate_feature (name)
396    feature = __all_features [name]
397
398    if feature.implicit:
399        for v in values:
400            if v in __implicit_features:
401                raise BaseException ("'%s' is already associated with the feature '%s'" % (v, __implicit_features [v]))
402
403            __implicit_features[v] = feature
404
405    if values and not feature.values and not(feature.free or feature.optional):
406        # This is the first value specified for this feature,
407        # take it as default value
408        feature.set_default(values[0])
409
410    feature.add_values(values)
411
412def validate_value_string (f, value_string):
413    """ Checks that value-string is a valid value-string for the given feature.
414    """
415    assert isinstance(f, Feature)
416    assert isinstance(value_string, basestring)
417    if f.free or value_string in f.values:
418        return
419
420    values = [value_string]
421
422    if f.subfeatures:
423        if not value_string in f.values and \
424               not value_string in f.subfeatures:
425            values = value_string.split('-')
426
427    # An empty value is allowed for optional features
428    if not values[0] in f.values and \
429           (values[0] or not f.optional):
430        raise InvalidValue ("'%s' is not a known value of feature '%s'\nlegal values: '%s'" % (values [0], f.name, f.values))
431
432    for v in values [1:]:
433        # this will validate any subfeature values in value-string
434        implied_subfeature(f, v, values[0])
435
436
437""" Extends the given subfeature with the subvalues.  If the optional
438    value-string is provided, the subvalues are only valid for the given
439    value of the feature. Thus, you could say that
440    <target-platform>mingw is specific to <toolset>gcc-2.95.2 as follows:
441
442          extend-subfeature toolset gcc-2.95.2 : target-platform : mingw ;
443
444    feature:        The feature whose subfeature is being extended.
445
446    value-string:   If supplied, specifies a specific value of the
447                    main feature for which the new subfeature values
448                    are valid.
449
450    subfeature:     The name of the subfeature.
451
452    subvalues:      The additional values of the subfeature being defined.
453"""
454def extend_subfeature (feature_name, value_string, subfeature_name, subvalues):
455    assert isinstance(feature_name, basestring)
456    assert isinstance(value_string, basestring)
457    assert isinstance(subfeature_name, basestring)
458    assert is_iterable_typed(subvalues, basestring)
459    feature = validate_feature(feature_name)
460
461    if value_string:
462        validate_value_string(feature, value_string)
463
464    subfeature_name = feature_name + '-' + __get_subfeature_name (subfeature_name, value_string)
465
466    extend(subfeature_name, subvalues) ;
467    subfeature = __all_features[subfeature_name]
468
469    if value_string == None: value_string = ''
470
471    if feature not in __subfeature_from_value:
472        __subfeature_from_value[feature] = {}
473
474    if value_string not in __subfeature_from_value[feature]:
475        __subfeature_from_value[feature][value_string] = {}
476
477    for subvalue in subvalues:
478        __subfeature_from_value [feature][value_string][subvalue] = subfeature
479
480@bjam_signature((["feature_name", "value_string", "?"], ["subfeature"],
481                 ["subvalues", "*"], ["attributes", "*"]))
482def subfeature (feature_name, value_string, subfeature, subvalues, attributes = []):
483    """ Declares a subfeature.
484        feature_name:   Root feature that is not a subfeature.
485        value_string:   An optional value-string specifying which feature or
486                        subfeature values this subfeature is specific to,
487                        if any.
488        subfeature:     The name of the subfeature being declared.
489        subvalues:      The allowed values of this subfeature.
490        attributes:     The attributes of the subfeature.
491    """
492    parent_feature = validate_feature (feature_name)
493
494    # Add grist to the subfeature name if a value-string was supplied
495    subfeature_name = __get_subfeature_name (subfeature, value_string)
496
497    if subfeature_name in __all_features[feature_name].subfeatures:
498        message = "'%s' already declared as a subfeature of '%s'" % (subfeature, feature_name)
499        message += " specific to '%s'" % value_string
500        raise BaseException (message)
501
502    # First declare the subfeature as a feature in its own right
503    f = feature (feature_name + '-' + subfeature_name, subvalues, attributes + ['subfeature'])
504    f.set_parent(parent_feature, value_string)
505
506    parent_feature.add_subfeature(f)
507
508    # Now make sure the subfeature values are known.
509    extend_subfeature (feature_name, value_string, subfeature, subvalues)
510
511
512@bjam_signature((["composite_property_s"], ["component_properties_s", "*"]))
513def compose (composite_property_s, component_properties_s):
514    """ Sets the components of the given composite property.
515
516    All parameters are <feature>value strings
517    """
518    from . import property
519
520    component_properties_s = to_seq (component_properties_s)
521    composite_property = property.create_from_string(composite_property_s)
522    f = composite_property.feature
523
524    if len(component_properties_s) > 0 and isinstance(component_properties_s[0], property.Property):
525        component_properties = component_properties_s
526    else:
527        component_properties = [property.create_from_string(p) for p in component_properties_s]
528
529    if not f.composite:
530        raise BaseException ("'%s' is not a composite feature" % f)
531
532    if property in __composite_properties:
533        raise BaseException ('components of "%s" already set: %s' % (composite_property, str (__composite_properties[composite_property])))
534
535    if composite_property in component_properties:
536        raise BaseException ('composite property "%s" cannot have itself as a component' % composite_property)
537
538    __composite_properties[composite_property] = component_properties
539
540
541def expand_composite(property_):
542    if __debug__:
543        from .property import Property
544        assert isinstance(property_, Property)
545    result = [ property_ ]
546    if property_ in __composite_properties:
547        for p in __composite_properties[property_]:
548            result.extend(expand_composite(p))
549    return result
550
551@bjam_signature((['feature'], ['properties', '*']))
552def get_values (feature, properties):
553    """ Returns all values of the given feature specified by the given property set.
554    """
555    if feature[0] != '<':
556     feature = '<' + feature + '>'
557    result = []
558    for p in properties:
559        if get_grist (p) == feature:
560            result.append (replace_grist (p, ''))
561
562    return result
563
564def free_features ():
565    """ Returns all free features.
566    """
567    return __free_features
568
569def expand_composites (properties):
570    """ Expand all composite properties in the set so that all components
571        are explicitly expressed.
572    """
573    if __debug__:
574        from .property import Property
575        assert is_iterable_typed(properties, Property)
576    explicit_features = set(p.feature for p in properties)
577
578    result = []
579
580    # now expand composite features
581    for p in properties:
582        expanded = expand_composite(p)
583
584        for x in expanded:
585            if not x in result:
586                f = x.feature
587
588                if f.free:
589                    result.append (x)
590                elif not x in properties:  # x is the result of expansion
591                    if not f in explicit_features:  # not explicitly-specified
592                        if any(r.feature == f for r in result):
593                            raise FeatureConflict(
594                                "expansions of composite features result in "
595                                "conflicting values for '%s'\nvalues: '%s'\none contributing composite property was '%s'" %
596                                (f.name, [r.value for r in result if r.feature == f] + [x.value], p))
597                        else:
598                            result.append (x)
599                elif any(r.feature == f for r in result):
600                    raise FeatureConflict ("explicitly-specified values of non-free feature '%s' conflict\n"
601                    "existing values: '%s'\nvalue from expanding '%s': '%s'" % (f,
602                    [r.value for r in result if r.feature == f], p, x.value))
603                else:
604                    result.append (x)
605
606    return result
607
608# Uses Property
609def is_subfeature_of (parent_property, f):
610    """ Return true iff f is an ordinary subfeature of the parent_property's
611        feature, or if f is a subfeature of the parent_property's feature
612        specific to the parent_property's value.
613    """
614    if __debug__:
615        from .property import Property
616        assert isinstance(parent_property, Property)
617        assert isinstance(f, Feature)
618
619    if not f.subfeature:
620        return False
621
622    p = f.parent
623    if not p:
624        return False
625
626    parent_feature = p[0]
627    parent_value = p[1]
628
629    if parent_feature != parent_property.feature:
630        return False
631
632    if parent_value and parent_value != parent_property.value:
633        return False
634
635    return True
636
637def __is_subproperty_of (parent_property, p):
638    """ As is_subfeature_of, for subproperties.
639    """
640    if __debug__:
641        from .property import Property
642        assert isinstance(parent_property, Property)
643        assert isinstance(p, Property)
644    return is_subfeature_of (parent_property, p.feature)
645
646
647# Returns true iff the subvalue is valid for the feature.  When the
648# optional value-string is provided, returns true iff the subvalues
649# are valid for the given value of the feature.
650def is_subvalue(feature, value_string, subfeature, subvalue):
651    assert isinstance(feature, basestring)
652    assert isinstance(value_string, basestring)
653    assert isinstance(subfeature, basestring)
654    assert isinstance(subvalue, basestring)
655    if not value_string:
656        value_string = ''
657    try:
658        return  __subfeature_from_value[feature][value_string][subvalue] == subfeature
659    except KeyError:
660        return False
661
662
663# Uses Property
664def expand (properties):
665    """ Given a property set which may consist of composite and implicit
666        properties and combined subfeature values, returns an expanded,
667        normalized property set with all implicit features expressed
668        explicitly, all subfeature values individually expressed, and all
669        components of composite properties expanded. Non-free features
670        directly expressed in the input properties cause any values of
671        those features due to composite feature expansion to be dropped. If
672        two values of a given non-free feature are directly expressed in the
673        input, an error is issued.
674    """
675    if __debug__:
676        from .property import Property
677        assert is_iterable_typed(properties, Property)
678    expanded = expand_subfeatures(properties)
679    return expand_composites (expanded)
680
681# Accepts list of Property objects
682def add_defaults (properties):
683    """ Given a set of properties, add default values for features not
684        represented in the set.
685        Note: if there's there's ordinary feature F1 and composite feature
686        F2, which includes some value for F1, and both feature have default values,
687        then the default value of F1 will be added, not the value in F2. This might
688        not be right idea: consider
689
690          feature variant : debug ... ;
691               <variant>debug : .... <runtime-debugging>on
692          feature <runtime-debugging> : off on ;
693
694          Here, when adding default for an empty property set, we'll get
695
696            <variant>debug <runtime_debugging>off
697
698          and that's kind of strange.
699    """
700    if __debug__:
701        from .property import Property
702        assert is_iterable_typed(properties, Property)
703    # create a copy since properties will be modified
704    result = list(properties)
705
706    # We don't add default for conditional properties.  We don't want
707    # <variant>debug:<define>DEBUG to be takes as specified value for <variant>
708    handled_features = set(p.feature for p in properties if not p.condition)
709
710    missing_top = [f for f in __all_top_features if not f in handled_features]
711    more = defaults(missing_top)
712    result.extend(more)
713    handled_features.update(p.feature for p in more)
714
715    # Add defaults for subfeatures of features which are present
716    for p in result[:]:
717        subfeatures = [s for s in p.feature.subfeatures if not s in handled_features]
718        more = defaults(__select_subfeatures(p, subfeatures))
719        handled_features.update(h.feature for h in more)
720        result.extend(more)
721
722    return result
723
724def minimize (properties):
725    """ Given an expanded property set, eliminate all redundancy: properties
726        which are elements of other (composite) properties in the set will
727        be eliminated. Non-symmetric properties equal to default values will be
728        eliminated, unless the override a value from some composite property.
729        Implicit properties will be expressed without feature
730        grist, and sub-property values will be expressed as elements joined
731        to the corresponding main property.
732    """
733    if __debug__:
734        from .property import Property
735        assert is_iterable_typed(properties, Property)
736    # remove properties implied by composite features
737    components = []
738    component_features = set()
739    for property in properties:
740        if property in __composite_properties:
741            cs = __composite_properties[property]
742            components.extend(cs)
743            component_features.update(c.feature for c in cs)
744
745    properties = b2.util.set.difference (properties, components)
746
747    # handle subfeatures and implicit features
748
749    # move subfeatures to the end of the list
750    properties = [p for p in properties if not p.feature.subfeature] +\
751        [p for p in properties if p.feature.subfeature]
752
753    result = []
754    while properties:
755        p = properties[0]
756        f = p.feature
757
758        # locate all subproperties of $(x[1]) in the property set
759        subproperties = [x for x in properties if is_subfeature_of(p, x.feature)]
760
761        if subproperties:
762            # reconstitute the joined property name
763            subproperties.sort ()
764            joined = b2.build.property.Property(p.feature, p.value + '-' + '-'.join ([sp.value for sp in subproperties]))
765            result.append(joined)
766
767            properties = b2.util.set.difference(properties[1:], subproperties)
768
769        else:
770            # eliminate properties whose value is equal to feature's
771            # default and which are not symmetric and which do not
772            # contradict values implied by composite properties.
773
774            # since all component properties of composites in the set
775            # have been eliminated, any remaining property whose
776            # feature is the same as a component of a composite in the
777            # set must have a non-redundant value.
778            if p.value != f.default or f.symmetric or f in component_features:
779                result.append (p)
780
781            properties = properties[1:]
782
783    return result
784
785
786def split (properties):
787    """ Given a property-set of the form
788        v1/v2/...vN-1/<fN>vN/<fN+1>vN+1/...<fM>vM
789
790    Returns
791        v1 v2 ... vN-1 <fN>vN <fN+1>vN+1 ... <fM>vM
792
793    Note that vN...vM may contain slashes. This is resilient to the
794    substitution of backslashes for slashes, since Jam, unbidden,
795    sometimes swaps slash direction on NT.
796    """
797    assert isinstance(properties, basestring)
798    def split_one (properties):
799        pieces = re.split (__re_slash_or_backslash, properties)
800        result = []
801
802        for x in pieces:
803            if not get_grist (x) and len (result) > 0 and get_grist (result [-1]):
804                result = result [0:-1] + [ result [-1] + '/' + x ]
805            else:
806                result.append (x)
807
808        return result
809
810    if isinstance (properties, str):
811        return split_one (properties)
812
813    result = []
814    for p in properties:
815        result += split_one (p)
816    return result
817
818
819def compress_subproperties (properties):
820    """ Combine all subproperties into their parent properties
821
822        Requires: for every subproperty, there is a parent property.  All
823        features are explicitly expressed.
824
825        This rule probably shouldn't be needed, but
826        build-request.expand-no-defaults is being abused for unintended
827        purposes and it needs help
828    """
829    from .property import Property
830    assert is_iterable_typed(properties, Property)
831    result = []
832    matched_subs = set()
833    all_subs = set()
834    for p in properties:
835        f = p.feature
836
837        if not f.subfeature:
838            subs = [x for x in properties if is_subfeature_of(p, x.feature)]
839            if subs:
840
841                matched_subs.update(subs)
842
843                subvalues = '-'.join (sub.value for sub in subs)
844                result.append(Property(
845                    p.feature, p.value + '-' + subvalues,
846                    p.condition))
847            else:
848                result.append(p)
849
850        else:
851            all_subs.add(p)
852
853    # TODO: this variables are used just for debugging. What's the overhead?
854    assert all_subs == matched_subs
855
856    return result
857
858######################################################################################
859# Private methods
860
861def __select_subproperties (parent_property, properties):
862    if __debug__:
863        from .property import Property
864        assert is_iterable_typed(properties, Property)
865        assert isinstance(parent_property, Property)
866    return [ x for x in properties if __is_subproperty_of (parent_property, x) ]
867
868def __get_subfeature_name (subfeature, value_string):
869    assert isinstance(subfeature, basestring)
870    assert isinstance(value_string, basestring) or value_string is None
871    if value_string == None:
872        prefix = ''
873    else:
874        prefix = value_string + ':'
875
876    return prefix + subfeature
877
878
879def __validate_feature_attributes (name, attributes):
880    assert isinstance(name, basestring)
881    assert is_iterable_typed(attributes, basestring)
882    for attribute in attributes:
883        if attribute not in VALID_ATTRIBUTES:
884            raise InvalidAttribute ("unknown attributes: '%s' in feature declaration: '%s'" % (str (b2.util.set.difference (attributes, __all_attributes)), name))
885
886    if name in __all_features:
887            raise AlreadyDefined ("feature '%s' already defined" % name)
888    elif 'implicit' in attributes and 'free' in attributes:
889        raise InvalidAttribute ("free features cannot also be implicit (in declaration of feature '%s')" % name)
890    elif 'free' in attributes and 'propagated' in attributes:
891        raise InvalidAttribute ("free features cannot also be propagated (in declaration of feature '%s')" % name)
892
893
894def __validate_feature (feature):
895    """ Generates an error if the feature is unknown.
896    """
897    assert isinstance(feature, basestring)
898    if feature not in __all_features:
899        raise BaseException ('unknown feature "%s"' % feature)
900
901
902def __select_subfeatures (parent_property, features):
903    """ Given a property, return the subset of features consisting of all
904        ordinary subfeatures of the property's feature, and all specific
905        subfeatures of the property's feature which are conditional on the
906        property's value.
907    """
908    if __debug__:
909        from .property import Property
910        assert isinstance(parent_property, Property)
911        assert is_iterable_typed(features, Feature)
912    return [f for f in features if is_subfeature_of (parent_property, f)]
913
914# FIXME: copy over tests.
915