# Copyright 2003 Dave Abrahams 
# Copyright 2002, 2003, 2004, 2005 Vladimir Prus 
# Distributed under the Boost Software License, Version 1.0. 
# (See accompanying file LICENSE_1_0.txt or http://www.boost.org/LICENSE_1_0.txt) 

#  Implements scanners: objects that compute implicit dependencies for
#  files, such as includes in C++.
#
#  Scanner has a regular expression used to find dependencies, some
#  data needed to interpret those dependencies (for example, include
#  paths), and a code which actually established needed relationship
#  between actual jam targets.
#
#  Scanner objects are created by actions, when they try to actualize
#  virtual targets, passed to 'virtual-target.actualize' method and are
#  then associated with actual targets. It is possible to use
#  several scanners for a virtual-target. For example, a single source
#  might be used by to compile actions, with different include paths.
#  In this case, two different actual targets will be created, each 
#  having scanner of its own.
#
#  Typically, scanners are created from target type and action's 
#  properties, using the rule 'get' in this module. Directly creating
#  scanners is not recommended, because it might create many equvivalent
#  but different instances, and lead in unneeded duplication of
#  actual targets. However, actions can also create scanners in a special
#  way, instead of relying on just target type.

import "class" : new ;
import property virtual-target property-set ;
import errors : error ;

#  Base scanner class. 
class scanner 
{
    rule __init__ ( )
    {
    }
    
    # Returns a pattern to use for scanning
    rule pattern ( )
    {
        error "method must be overriden" ;
    }

    # Establish necessary relationship between targets,
    # given actual target beeing scanned, and a list of
    # pattern matches in that file.
    rule process ( target : matches * )
    {
        error "method must be overriden" ;
    }        
}

# Registers a new generator class, specifying a set of 
# properties relevant to this scanner.  Ctor for that class
# should have one parameter: list of properties.
rule register ( scanner-class : relevant-properties * )
{
    .registered += $(scanner-class) ;
    .relevant-properties.$(scanner-class) = $(relevant-properties) ;
}

# Common scanner class, which can be used when there's only one
# kind of includes (unlike C, where "" and <> includes have different
# search paths).
class common-scanner : scanner 
{
    import scanner ;
    rule __init__ ( includes * )
    {
        scanner.__init__ ;
        self.includes = $(includes) ;
    }
            
    rule process ( target : matches * : binding )
    {
        local target_path = [ NORMALIZE_PATH $(binding:D) ] ;

        NOCARE $(matches) ;
        INCLUDES $(target) : $(matches) ;
        SEARCH on $(matches) = $(target_path) $(self.includes:G=) ;
        ISFILE $(matches) ;
    
        scanner.propagate $(__name__) : $(matches) : $(target) ;     
    }
}


# Returns an instance of previously registered scanner,
# with the specified properties.
rule get ( scanner-class : property-set ) 
{
    if ! $(scanner-class) in $(.registered)
    {
        error "attempt to get unregisted scanner" ;
    }
    
    local r = $(.rv-cache.$(property-set)) ;
    if ! $(r)
    {
        r = [ property-set.create 
            [ property.select $(.relevant-properties.$(scanner-class)) :
              [ $(property-set).raw ] ] ] ;        
        .rv-cache.$(property-set) = $(r) ;
    }
        
    if ! $(scanner.$(scanner-class).$(r:J=-))
    {
        scanner.$(scanner-class).$(r:J=-) = [ new $(scanner-class) [ $(r).raw ] ] ;
    }
    return $(scanner.$(scanner-class).$(r:J=-)) ;    
}


# Installs the specified scanner on actual target 'target'. 
rule install ( scanner : target 
    vtarget # virtual target from which 'target' was actualized
)
{
    HDRSCAN on $(target) = [ $(scanner).pattern ] ;
    SCANNER on $(target) = $(scanner) ;
    HDRRULE on $(target) = scanner.hdrrule ;
    
    # scanner reflects difference in properties affecting    
    # binding of 'target', which will be known when processing
    # includes for it, will give information on how to
    # interpret quoted includes.
    HDRGRIST on $(target) = $(scanner) ;
}

# Propagate scanner setting from 'including-target' to 'targets'.
rule propagate ( scanner : targets * : including-target )
{
    HDRSCAN on $(targets) = [ on $(including-target) return $(HDRSCAN) ] ;
    SCANNER on $(targets) = $(scanner) ;
    HDRRULE on $(targets) = scanner.hdrrule ;
    HDRGRIST on $(targets) = [ on $(including-target) return $(HDRGRIST) ] ;
}


rule hdrrule ( target : matches * : binding )
{
    local scanner = [ on $(target) return $(SCANNER) ] ;
    $(scanner).process $(target) : $(matches) : $(binding) ;
}
# hdrrule must be available at global scope so that it can be invoked
# by header scanning
IMPORT scanner : hdrrule : : scanner.hdrrule ;