#  (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.

# Print a stack backtrace leading to this rule's caller. Each
# argument represents a line of output to be printed after the first
# line of the backtrace.
rule backtrace ( skip-frames messages * : * )
{
    local frame-skips = 5 9 13 17 21 25 29 33 37 41 45 49 53 57 61 65 69 73 77 81 ;
    local drop-elements = $(frame-skips[$(skip-frames)]) ;
    if ! ( $(skip-frames) in 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 )
    {
        ECHO warning: backtrace doesn't support skipping
          $(skip-frames) frames; using 1 instead. ;
        drop-elements = 5 ;
    }
    
    # get the whole backtrace, then drop the initial quadruples
    # corresponding to the frames that must be skipped.
    local bt = [ BACKTRACE ] ;
    bt = $(bt[$(drop-elements)-]) ; 
    
    local args = messages 2 3 4 5 6 7 8 9 ;
    while $(bt)
    {
        ECHO $(bt[1]):$(bt[2]): "in" $(bt[4]) ;
        
        # the first time through, print each argument on a separate
        # line
        for local n in $(args)
        {
            if $($(n))-is-not-empty
            {
                ECHO $($(n)) ;
            }
        }
        args = ; # kill args so that this never happens again
        
        # Move on to the next quadruple
        bt = $(bt[5-]) ;
    }
}

module local args = messages 2 3 4 5 6 7 8 9 ;
module local disabled last-$(args) ;

# try-catch --
#
# This is not really an exception-handling mechanism, but it does
# allow us to perform some error-checking on our
# error-checking. Errors are suppressed after a try, and the first one
# is recorded. Use catch to check that the error message matched
# expectations.

# begin looking for error messages
rule try ( )
{
    disabled += true ;
    last-$(args) = ;
}

# stop looking for error messages; generate an error if an argument of
# messages is not found in the corresponding argument in the error call.
rule catch ( messages * : * )
{
    import sequence ;
    disabled = $(disabled[2-]) ; # pop the stack

    for local n in $(args)
    {
        if ! $($(n)) in $(last-$(n))
        {
            local v = [ sequence.join $($(n)) : " " ] ;
            v ?= "" ;
            local joined = [ sequence.join $(last-$(n)) : " " ] ;
            
            last-$(args) = ;
            error-skip-frames 3 expected \"$(v)\" in argument $(n) of error
              : got \"$(joined)\" instead ;
        }
    }
}

rule error-skip-frames ( skip-frames messages * : * )
{
    if ! $(disabled)
    {
        backtrace $(skip-frames) error: $(messages) : $(2) : $(3) : $(4) : $(5) : $(6) : $(7) : $(8) : $(9) ;
        EXIT ;
    }
    else if ! $(last-$(args))
    {
        for local n in $(args)
        {
            last-$(n) = $($(n)) ;
        }
    }
}

# Print an error message with a stack backtrace and exit.
rule error ( messages * : * )
{
    error-skip-frames 3 $(1) : $(2) : $(3) : $(4) : $(5) : $(6) : $(7) : $(8) : $(9) ;
}

# Print a warning message with a stack backtrace and exit.
rule warning
{
    backtrace 2 warning: $(1) : $(2) : $(3) : $(4) : $(5) : $(6) : $(7) : $(8) : $(9) ;
}

# convert an arbitrary argument list into a list with ":" separators
# and quoted elements representing the same information. This is
# mostly useful for formatting descriptions of the arguments with
# which a rule was called when reporting an error.
rule lol->list ( * )
{
    local result ;
    local remaining = 1 2 3 4 5 6 7 8 9 ;
    while $($(remaining))
    {
        local n = $(remaining[1]) ;
        remaining = $(remaining[2-]) ;
        
        if $(n) != 1
        {
            result += ":" ;
        }
        result += \"$(n)\" ;
    }
    return $(result) ;
}

rule __test__ ( )
{
    # show that we can correctly catch an expected error
    try ;
    {
        error an error occurred : somewhere ;
    }
    catch an error occurred : somewhere ;
    
    # show that unexpected errors generate real errors
    try ;
    {
        try ;
        {
            error an error occurred : somewhere ;
        }
        catch an error occurred : nowhere ;
    }
    catch expected \"nowhere\" in argument 2 ;
}
