# Copyright 2003 Dave Abrahams
# Copyright 2002, 2003, 2004, 2005 Vladimir Prus
# Distributed under the Boost Software License, Version 1.0.
# (See accompanying file LICENSE.txt or copy at
# https://www.bfgroup.xyz/b2/LICENSE.txt)

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

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

# Base scanner class.
#
class scanner
{
    rule __init__ ( )
    {
    }

    # Returns a pattern to use for scanning.
    #
    rule pattern ( )
    {
        import errors : error : errors.error ;
        errors.error "method must be overridden" ;
    }

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


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


# Common scanner class, usable when there is 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 a previously registered scanner, with the specified
# properties.
#
rule get ( scanner-class : property-set )
{
    if ! $(scanner-class) in $(.registered)
    {
        import errors ;
        errors.error "attempt to get an unregistered 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=-))
    {
        local s = [ new $(scanner-class) [ $(r).raw ] ] ;
        scanner.$(scanner-class).$(r:J=-) = $(s) ;
    }
    return $(scanner.$(scanner-class).$(r:J=-)) ;
}


# Installs the specified scanner on the actual target 'target'.
#
rule install ( scanner : target )
{
    HDRSCAN on $(target) = [ $(scanner).pattern ] ;
    SCANNER on $(target) = $(scanner) ;
    HDRRULE on $(target) = scanner.hdrrule ;

    # Scanner reflects differences in properties affecting binding of 'target',
    # which will be known when processing includes for it, and give information
    # on how to interpret different include types (e.g. quoted vs. those in
    # angle brackets in C files).
    HDRGRIST on $(target) = $(scanner) ;
}


# Propagate scanner settings 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 it can be invoked by header
# scanning.
#
IMPORT scanner : hdrrule : : scanner.hdrrule ;
