#  (C) Copyright David Abrahams 2001. Permission to copy, use, modify, sell and
#  distribute this software is granted provided this copyright notice appears in
#  all copies. This software is provided "as is" without express or implied
#  warranty, and with no claim as to its suitability for any purpose.

import errors : error lol->list ;
import property ;
import sequence ;
import regex ;
import set ;

module local all-attributes = 
    
    implicit    # features whose values alone identify the
                # feature. For example, a user is not required to
                # write "<toolset>gcc", but can simply write
                # "gcc". Implicit feature names also don't appear in
                # variant paths, although the values do. Thus:
                # bin/gcc/... as opposed to bin/toolset-gcc/.... There
                # should typically be only a few such features, to
                # avoid possible name clashes.

    executed    # the feature corresponds to the name of a module
                # containing an execute rule, used to actually prepare
                # the build. For example, the toolset feature would be
                # executed.
    

    composite   # features which actually correspond to groups of
                # properties. For example, a build variant is a
                # composite feature. When generating targets from a
                # set of build properties, composite features are
                # recursively expanded and /added/ to the build
                # property set, so rules can find them if
                # neccessary. Non-composite non-free features override
                # components of composite features in a build property
                # set.
    

    optional    # An optional feature is allowed to have no value at
                # all in a particular build. Normal non-free features
                # are always given the first of their values if no
                # value is otherwise specified.
    

    symmetric   # A symmetric feature has no default value, and is
		# therefore not automatically included in all
		# variants. A symmetric feature, when relevant to the
		# toolset, always generates a corresponding subvariant
		# directory.

    free        # as described in previous documentation

    path        # the (free) feature's value describes a path which
		# might be given relative to the directory of the
		# Jamfile.

    dependency  # the value of the (free) feature specifies a
		# dependency of the target.

    propagated  # when used in a build request, the (free) feature is
		# propagated to top-level targets which are
		# dependencies of the requested build. Propagated
		# features would be most useful for settings such as
		# warning levels, output message style, etc., which
		# don't affect the built products at all.
  ;

module local all-features ;
module local all-implicit-values ;

# declare a new feature with the given name, values, and attributes.
rule feature ( name : values * : attributes * )
{
    local error ;
    
    # if there are any unknown attributes...
    if ! ( $(attributes) in $(all-attributes) )
    {
        error = unknown attributes:
          [ set.difference $(attributes) : $(all-attributes) ] ;
    }
    else if $(name) in $(all-features)
    {
        error = feature already defined: ;
    }
    else if implicit in $(attributes)
    {
        if free in $(attributes)
        {
            error = free features cannot also be implicit ;
        }
    }
    
    if $(error)
    {
        error $(error)
          : "in" feature declaration:
          : feature [ errors.lol->list $(1) : $(2) : $(3) ] ;
    }

    module local $(name).values ;
    module local $(name).attributes = $(attributes) ;
    module local $(name).subfeatures = ;
    
    all-features += $(name) ;
    extend $(name) : $(values) ;
}

# returns true iff all elements of names are valid features.
rule valid ( name + )
{
    if $(names) in $(all-features)
    {
        return true ;
    }
}

# return the attibutes of the given feature
rule attributes ( feature )
{
    return $($(feature).attributes) ;
}

# return the values of the given feature
rule values ( feature )
{
    return $($(feature).values) ;
}

rule implied-feature ( implicit-value )
{
    local feature = $($(implicit-value).implicit-feature) ;
    if ! $(feature)
    {
        error \"$(implicit-value)\" is not a value of an implicit feature ;
    }
    return $(feature) ;
}

local rule find-implied-subfeature ( feature subvalue : value-string ? )
{
    local v
      = subfeature($(feature),$(subvalue)) 
        subfeature($(feature),$(value-string),$(subvalue)) ;
    
    # declaring these module local here prevents us from picking up
    # enclosing definitions.
    module local $(v) ;
    
    local subfeature = $($(v)) ;
    return $(subfeature[1]) ;
}

rule implied-subfeature ( feature subvalue : value-string ? )
{
    local subfeature = [ find-implied-subfeature $(feature) $(subvalue)
      : $(value-string) ] ;
      
    if ! $(subfeature)
    {
        error \"$(subvalue)\" is not a known subfeature value of
          feature \"$(feature)\" ;
    }
    
    return $(subfeature) ;
}

# generate an error if the feature is unknown
local rule validate-feature ( feature )
{
    if ! $(feature) in $(all-features)
    {
        error unknown feature \"$(feature)\" ;
    }
}

# expand-subfeatures toolset : gcc-2.95.2-linux-x86 -> <toolset>gcc <toolset-version>2.95.2 <toolset-os>linux <toolset-cpu>x86
# equivalent to:
# expand-subfeatures : gcc-2.95.2-linux-x86
local rule expand-subfeatures ( feature ? : value )
{
    if $(feature)
    {
        validate-feature $(feature) ;
    }
    
    local components = [ regex.split $(value) "-" ] ;
    if ! $(feature)
    {
        feature = [ implied-feature $(components[1]) ] ;
    }
    
    # get the top-level feature's value
    local value = $(components[1]:G=) ;
    
    local result = $(components[1]:G=$(feature)) ;
    for local subvalue in $(components[2-])
    {
        local subfeature = [ implied-subfeature $(feature) $(subvalue) : $(value) ] ;
        result += $(subvalue:G=$(feature)-$(subfeature)) ;
    }
    
    return $(result) ;
}

local rule extend-feature ( feature : values * )
{
    validate-feature $(feature) ;
    if implicit in $(attributes)
    {
        for local v in $(values)
        {
            module local $(v).implicit-feature ;
            if $($(v).implicit-feature)
            {
                error $(v) is already associated with the \"$($(v).implicit-feature)\" feature ;
            }
            $(v).implicit-feature = $(feature) ;
        }
        
        all-implicit-values += $(values) ;
    }
    $(feature).values += $(values) ;
}

local rule validate-value-string ( feature value-string )
{
    local values = $(value-string) ;
    if $($(feature).subfeatures)
    {
        values = [ regex.split $(value-string) - ] ;
    }
    
    if ! ( $(values[1]) in $($(feature).values) )
    {
        return \"$(values[1])\" is not a known value of feature \"$(feature)\" ;
    }
    
    if $(values[2])
    {
        # this will validate any subfeature values in value-string
        implied-subfeature $(feature) [ sequence.join $(values[2-]) - ]
          : $(values[1]) ;
    }
    
}

# extend-subfeature toolset gcc-2.95.2 : target-platform : mingw ;
# extend-subfeature toolset : target-platform : mingw ;
local rule extend-subfeature ( feature value-string ? : subfeature : subvalues * )
{
    validate-feature $(feature) ;
    if $(value-string)
    {
        validate-value-string $(feature) $(value-string) ;
    }
    
    for local subvalue in $(subvalues)
    {
        local v
          = subfeature($(feature),$(value-string),$(subvalue)) 
            subfeature($(feature),$(subvalue)) ;
        module local $(v[1]) = $(subfeature) ;
    }
}

rule extend ( feature-or-property subfeature ? : values * )
{
    local feature value-string ;
    if $(feature-or-property:G)
    {
        feature = [ property.get-feature $(feature-or-property) ] ;
        value-string = $(feature-or-property:G=) ;
    }
    else
    {
        feature = $(feature-or-property) ;
    }
    
    if $(subfeature)
    {
        extend-subfeature $(feature) $(value-string)
          : $(subfeature) : $(values) ;
    }
    else
    {
        if $(value-string)
        {
            error can only be specify a property as the first argument
              when extending a subfeature
              : usage:
              : "    extend" feature ":" values...
              : "  | extend" <feature>value-string subfeature ":" values...
              ;
        }
        
        extend-feature $(feature) : $(values) ;
    }
}

# subfeature
#
# subfeature toolset gcc-2.95.2 target-platform : aix linux mac cygwin
#
rule subfeature ( feature value-string ? : subfeature : subvalues * )
{
    validate-feature $(feature) ;
    if $(subfeature) in $($(feature).subfeatures)
    {
        error \"$(subfeature)\" already declared as a subfeature of \"$(feature)\" ;
    }
    $(feature).subfeatures += $(subfeature) ;
    extend-subfeature $(feature) $(value-string) : $(subfeature) : $(subvalues) ;
}

# tests of module features
local rule __test__ ( )
{
    import errors : try catch ;
    import assert ;
    feature __test__toolset : gcc : implicit executed ;
    feature __test__define : : free ;
    
    extend-feature __test__toolset : msvc metrowerks ;
    subfeature __test__toolset gcc : version : 2.95.2 2.95.3 2.95.4
      3.0 3.0.1 3.0.2 ;
    
    assert.result <__test__toolset>gcc <__test__toolset-version>3.0.1 :
      expand-subfeatures __test__toolset : gcc-3.0.1 ;
    
    assert.result <__test__toolset>gcc <__test__toolset-version>3.0.1 :
      expand-subfeatures : gcc-3.0.1 ;
    
    feature __test__dummy : dummy1 dummy2 ;
    subfeature __test__dummy : subdummy : x y z ;
    
    # test error checking
    try ;
    {
        validate-feature __test__foobar ;
    }
    catch unknown feature ;
    
    try ;
    {
        feature __test__foobar : : baz ;
    }
    catch unknown attributes: baz ;
    
    feature __test__feature1 ;
    try ;
    {
        feature __test__feature1 ;
    }
    catch feature already defined: ;
    
    try ;
    {
        feature __test__feature2 : : free implicit ;
    }
    catch free features cannot also be implicit ;
    
    try ;
    {
        implied-feature lackluster ;
    }
    catch \"lackluster\" is not a value of an implicit feature ;
    
    try ;
    {
        implied-subfeature __test__toolset 3.0.1 ;
    }
    catch \"3.0.1\" is not a known subfeature value of
          feature \"__test__toolset\" ;

    try ;
    {
        implied-subfeature __test__toolset not-a-version : gcc ;
    }
    catch \"not-a-version\" is not a known subfeature value of
          feature \"__test__toolset\" ;
}
