#!/bin/sh
# The next line is executed by /bin/sh, but not tcl \
exec tclsh $0 ${1+"$@"}

##############################################################################
# SYNOPSIS: $argv0 [--prefix <prefix> | --prefix=<prefix>]
#                  [--postfix <postfix> | --postfix=<postfix>]
#                  [--backend=(table|list|latex|tabtable)]
#                  [--errorfile <filename>]
#                  [(-i|--intermediatefile) <filename>]
#                  [--stopafterfirstfailure]
#                  (-e|--extractionrulefile) <filename>
#                  (-p|--problemfile) <filename>
#                  (-o|--outputschemafile) <filename>
#                   -n=<N> [--] <executable1> [...]
# or
#           $argv0 --postprocess
#                  (-i|--intermediatefile) <filename>
#                  (-o|--outputschemafile) <filename>
#                  [--backend=(table|list|latex|tabtable)]
#
##############################################################################
#
# How to use bmtool to run benchmarks and create a protocol file?
#
# ../bmtool \
#   --errorfile lookahead-optimisation2.errors \
#   -e ../bmtool-statsextraction.Linux \
#   -p problems2 \
#   -o ../bmtool-statsschemata -n=1 \
#   -i lookahead-optimisation2.protocol \
#   --prefix /usr/bin/time --postfix "-stats++" \
#   $USER/dl-repository-2001-03-30 \
#   $USER/dl \
#   >&! hampath.lookahead-optimisation2
#
# Usually, you change the argument to --errorfile, to -p (unless you
# overwrite problems2), to -i (this is the most important file, you can
# reconstruct everything from that), obviously you should change the
# executables, and the last line redirects the output to some preliminary
# result file (assuming you use tcsh). If you don't use GNU/Linux, change
# also the -e argument.
#
# The preliminary result file won't be of great use, because there is no
# aggregation. Anyway this is what you need for running the benchmarks.
#
# If you are using the "table" backend and you want to compress the output
# (vertically) the following has proven useful:
#
#   sed -e 's/([0-9\.]*)//g' | sed -e 's/ | /|/g'

##############################################################################
# global options

set outfile /dev/null
set trace 0
set no_measure_string "n/a"
set not_const_string "varies"
set error_string "-"
set no_match_string "NV"
set not_a_number_string "NAN"

##############################################################################
##############################################################################
# FUNCTIONS
##############################################################################
##############################################################################

# parse_formatstring -- parse and modify a formatstring for specifying output
#
#   Analyses a formatstring (see man n format), modifies it to contain
#   variables in place of the format specifiers, and builds a list whose
#   entries are the substituted variable name, the original specifier
#   and a modified specifier to hold a string (currently just %s), each
#   triple represented again as a list.
#
#   XPG3 position specifiers and specifiers containing a * (indicating
#   that the field value is to be taken from a subsequent argument) are
#   unsupported. If they are encountered, terminate with error code 6 and 7,
#   respectively.
#
# Arguments:
#   formatstring   the original format string
# Results:
#   A list, the first argument of which is the modified format string,
#   and the second argument the list with specifier data.
#
proc parse_formatstring {formatstring} {
    set pattern {%([1-9][0-9]*\$)?[-+ 0#]?([0-9*]|[1-9][0-9]*)?(\.([0-9*]|[1-9][0-9]*))?[duioxXcsfeEgG]}
    set fs $formatstring
    set specs [list]
    set i 0
    while {[regexp -- $pattern $fs spec] != 0} {
        if {[regexp {[*]} $spec dummy] != 0} {
            puts stderr "Format specifier contains *, which is unsupported."
            exit 6
        }
        if {[regexp {%([1-9][0-9]*\$)} $spec dummy] != 0} {
            puts stderr "Format specifier contains XPG3 position specifier, which is unsupported."
            exit 7
        }
        lappend specs [list "spec$i" $spec %s]
        if {[regsub -- $pattern $fs "\$spec$i" fs] == 0} {
            puts stderr "Internal Error. Pattern didn't match a second time."
            exit 8
        }
        incr i
    }

    return [list $fs $specs]
}

# timeconv -- timestring conversion
#
#   Convert a time string (as returned by timex or similar) to a float
#
#   possible values: h:m:s.ms
#                      m:s.ms
#                        s.ms
#
# Arguments:
#   string      the time string
# Results:
#   Returns the time in seconds
#
proc timeconv {string} {
    global errorchannel

    if {[regexp {:?([0-9.]*)$} $string dummy seconds] == 0} {
	puts $errorchannel "Invalid time string: $string"
    } else {
	if {[regexp {:?(([0-9])?[0-9]):[^:]*$} $string dummy minutes] == 0} {
	    return $seconds
	} else {
	    if {[regexp {^([0-9]*):.*:} $string dummy hours] == 0} {
		return [expr $seconds + ($minutes * 60)]
	    } else {
		return [expr $seconds + ($minutes * 60) + ($hours * 3600)]
	    }
	}
    }
    return ""
}

# numberconv -- number conversion
#
#   simply returns its argument (just for uniformity elsewhere)
#
# Arguments:
#   string      the number
# Results:
#   Returns the argument
#
proc numberconv {string} {
    return $string
}

# subexp_dummies --
#
#   Looks how many subexpressions exist before a type field (@TYPE@)
#   and returns a string of the corresponding number of dummy variables
#   which are needed in order to put the typefield's match into a variable
#   with regexp
#
# Arguments:
#   regexp_string   The regexp which is scanned
# Results:
#   Returns a string of dummy arguments to regexp
#
proc subexp_dummies {regex_string} {
    set dummystring ""
    set escaped 0

    # Luckily the number of subexpressions is the same as of opening brackets

    set le [string length $regex_string]
    
    set chara [string index $regex_string 0]

    for {set i 1} {($i <= $le) && !($chara == "@" && $escaped == 1)} {incr i} {

	if {$chara == "("} {
	    set dummystring "$dummystring dummy"
	    set escaped 0
	} else {
	    if {$chara == "\\"} {
		set escaped 1
	    } else {
		set escaped 0
	    }
	}
	set chara [string index $regex_string $i]
    }

    return $dummystring
}

proc read_file {filename arrayname} {
    upvar 1 $arrayname array

    # Open the file.
    if {[catch "set file [open $filename r]"] != 0} {
        puts stderr "Error while opening file $filename"
        return 1
    }

    # store the lines from the file in the array lines
    # lines_count holds the number of read lines
    for {set lines_count 0} {[eof $file] == 0} {incr lines_count} {
        gets $file array($lines_count)
    }

    if {[catch "close $file"] != 0} {
        puts stderr "Error while closing file $filename"
        return 2
    }
}

#
#
#
#
proc parse_intermediatefile1.0 {linesname lineindex tagsname descname execname measuresname numberRunsname filename} {

    global trace

    upvar 1 $linesname lines

    upvar 2 $tagsname tags\
        $descname desc\
        $execname exec\
        $measuresname measures\
        $numberRunsname numberRuns

    if { [regexp {^@executables begin$} $lines($lineindex)] == 0} {
        puts stderr "Error: $filename does not start with \"@executables begin\"."
        return 3
    }

    set i $lineindex

    for {incr i} {$i < [array size lines] && [regexp {^@executables end$} $lines($i)] == 0} {incr i} {
        if { [regexp {^([0-9]+) (.*)$} $lines($i) dummy index exestring] == 0} {
            puts stderr "Error: $filename contains invalid executable specification on line [expr $i + 1]."
            return 3
        }
        set exec($index) $exestring
    }

    if {$i == [array size lines]} {
        puts stderr "Error: $filename is truncated. No problem descriptions found."
        return 3
    }

    incr i

    if {$i == [array size lines] || [regexp {^@problems begin$} $lines($i)] == 0} {
        puts stderr "Error: $filename is truncated. No problem descriptions found."
        return 3
    }

    set problemoffset [expr $i + 1]
    for {incr i} {$i < [array size lines] && [regexp {^@problems end$} $lines($i)] == 0} {incr i} {
        set problemlines([expr $i - $problemoffset]) $lines($i)
    }

    if {$i == [array size lines]} {
        puts stderr "Error: $filename is truncated. Problem descriptions unterminated."
        return 3
    }

    parse_problemarray problemlines tags desc dummyopts dummyfiles "" $filename $problemoffset

    set numberRuns 0
    for {incr i} {$i < [array size lines]} {incr i} {
        if {[regexp {^([0-9]+) ([0-9]+) ([0-9]+) ([^ ]+) (.+)$} $lines($i) dummy execindex problemindex runindex type value] == 0} {
            if {[regexp {^@comment} $lines($i)] == 0 && [regexp {^$} $lines($i)] == 0} {
                puts stderr "Parse Error in $filename on line [expr $i + 1]"
                return 3
            } else {
                continue
            }
        }
        set measures([list $execindex $problemindex $runindex $type]) $value
        if {$runindex > $numberRuns} {
            set numberRuns $runindex
        }
    }

    incr numberRuns

    return
}

#
#
#
#
#
proc parse_intermediatefile2.0 {linesname lineindex tagsname descname execname measuresname numberRunsname filename} {
    global trace

    upvar 1 $linesname lines

    upvar 2 $tagsname tags\
        $descname desc\
        $execname exec\
        $measuresname measures\
        $numberRunsname numberRuns

    if { [regexp {^@executables begin$} $lines($lineindex)] == 0} {
        puts stderr "Error: $filename does not start with \"@executables begin\"."
        return 3
    }

    set i $lineindex

    for {incr i} {$i < [array size lines] && [regexp {^@executables end$} $lines($i)] == 0} {incr i} {
        if { [regexp {^([0-9]+) (.*)$} $lines($i) dummy index exestring] == 0} {
            puts stderr "Error: $filename contains invalid executable specification on line [expr $i + 1]."
            return 3
        }
        set exec($index) $exestring
    }

    if {$i == [array size lines]} {
        puts stderr "Error: $filename is truncated. No problem descriptions found."
        return 3
    }

    incr i

    if {$i == [array size lines] || [regexp {^@problems begin$} $lines($i)] == 0} {
        puts stderr "Error: $filename is truncated. No problem descriptions found."
        return 3
    }

    set problemoffset [expr $i + 1]
    for {incr i} {$i < [array size lines] && [regexp {^@problems end$} $lines($i)] == 0} {incr i} {
        set problemlines([expr $i - $problemoffset]) $lines($i)
    }

    if {$i == [array size lines]} {
        puts stderr "Error: $filename is truncated. Problem descriptions unterminated."
        return 3
    }

    parse_problemarray problemlines tags desc dummyopts dummyfiles "" $filename $problemoffset

    incr i

    if {$i == [array size lines] || [regexp {^@typecount ([0-9]+)$} $lines($i) dummy typecount] == 0} {
        puts stderr "Error: $filename is corrupt. No typecount entry found."
        return 3
    }

    set numberRuns 0
    for {incr i} {$i < [array size lines]} {incr i} {
        if {[regexp {^@run ([0-9]+) ([0-9]+) ([0-9]+)$} $lines($i) dummy execindex problemindex runindex] == 0} {
            if {[regexp {^@comment} $lines($i)] == 0 && [regexp {^$} $lines($i)] == 0} {
                puts stderr "Parse Error in $filename on line [expr $i + 1]"
                return 3
            } else {
                continue
            }
        } else {
            # matched @run
            # now match typecount many type-value pairs
            set typevaluelimit [expr $i + $typecount]
            for {incr i} {$i <= $typevaluelimit} {incr i} {
                if {[regexp {^([^ ]+) (.+)$} $lines($i) dummy type value] == 0} {
                    puts stderr "Parse Error in $filename on line [expr $i + 1]"
                    return 3
                }
                set measures([list $execindex $problemindex $runindex $type]) $value
                if {$runindex > $numberRuns} {
                    set numberRuns $runindex
                }
            }
        }
    }

    incr numberRuns

    return 0
}


# parse_intermediatefile --
#
#   Parses a file which contains informations on the executables, the
#   problems and additionally contains all the measures.
#
# Arguments:
#   filename      the file name of the file to be parsed
#   tagsname      the name of the array which holds the problem tags
#   descname      the name of the array which holds the problems' 
#                 descriptions
#   execname      the name of the array holding the executables
#   measuresname  the name of the array holding the measures
#   numberRunsname name of the variable holding the number of runs
#
# Results: 0 upon success, > 0 upon failure:
#          1 couldn't open file
#          2 couldn't close file
#          3 parse error
#
proc parse_intermediatefile {filename tagsname descname execname measuresname numberRunsname} {


    # Read the file into the local array "lines".
    if {[set returncode [read_file $filename lines]] > 0} {
        return $returncode
    }

    global trace

    upvar 1 $tagsname tags\
        $descname desc\
        $execname exec\
        $measuresname measures\
        $numberRunsname numberRuns

    # now try to parse the file

    if { [array size lines] == 0 } {
        puts stderr "Error: $filename is empty."
        return 3
    }

    if { [regexp {^@formatversion ([0-9.]+)$} $lines(0) dummy formatversion] == 0} {
        # default version: 1.0
        set formatversion "1.0"
        # index of first line to parse is 0
	set lineindex 0
    } else {
        # formatversion already contains versionstring
        # index of first line to parse is 1
        set lineindex 1
    }

    set parseproc "parse_intermediatefile$formatversion"

    if {[info proc $parseproc] == ""} {
        puts stderr "Error: Unknown format version $formatversion."
        return 3
    }

    return [eval $parseproc lines $lineindex $tagsname $descname $execname $measuresname $numberRunsname $filename]
}

# parse_rulefile --
#
#   Parses a file which contains rules specifying how to extract
#   timing information from a string.
#
# Arguments:
#   rulefilename   the file name of the file to be parsed
#   typesname      the name of the list holding the types
#   reexpandname   the name of the associative array holding the expanded
#                  regular expressions
#   convname       the name of the associative array holding the names of
#                  the conversion functions
#   dummiesname    the name of the associative array holding the subexpression
#                  dummy match strings
#   intermediatechannel  the channel to the file in which intermediate
#                        values are stored
#
# Results: 0 upon success, > 0 upon failure:
#          1 couldn't open file
#          2 couldn't close file
#          3 no or multiple type specifyers in regular expression
#          4 no extraction rule for USER
#          5 no extraction rule for SYS
#
proc parse_rulefile {rulefilename typesname reexpandname convname dummiesname} {

    # Read the file into the local array "lines".
    if {[set returncode [read_file $rulefilename lines]] > 0} {
        return $returncode
    }

    global trace

    upvar 1 $typesname types\
        $reexpandname reexpand\
        $convname conv\
        $dummiesname dummies

    # now try to read in the rules from <rulefile>
    # skip blank lines and lines starting with #
    # a line should look like
    # <typespec><TAB>*"<regular expression>"

    # parse each line
    for {set i 0} {$i < [array size lines]} {incr i} {
        # ignore comments and blank lines
        if {[regexp {^[ 	]*$} $lines($i)] == 0 
            && [regexp {^\#.*$} $lines($i)] == 0} {
            if { [regexp {^([^	]*)	*"(.*)"$}\
                       $lines($i) dummy type re ] == 0} {
                puts stderr "Warning: bad specification on line\
                             [expr $i + 1] in $rulefilename, ignored"
            } else {
                # At most one line for each type, redefinitions override.
                if {[array names reexpand $type] != [list]} {
                    puts stderr "Warning: $type redefined on line\
                                 [expr $i + 1] in $rulefilename, overrides\
                                 previous definition"
                }

                # There should be exactly one type specifier (@TYPE@)
                # per regular expression.  It is replaced by a real
                # regular expression and is stored in
                # reexpand(<type>). The respective converter function
                # name is stored in conv(<type>). A string with dummy
                # variables which match all subexpressions before the
                # subexpression which corresponds to the type
                # specifyer is stored in dummies(<type>).
                if { [set timeoccurrences\
                          [regsub -all {@TIME@} $re \
                               {((((([12]?[0-9]:)?[0-5])?[0-9]:)?[0-5])?[0-9]\\.[0-9]*)} \
                               reexpand($type)]] > 0} {
                    if { ([regexp {@NUMBER@} $re] > 0)
                         || ($timeoccurrences > 1) } {
                        puts stderr "Error in line [expr $i + 1] of\
                                     $rulefilename: Multiple type specifiers\
                                     in regular expression"
                        return 3
                    }
                    set conv($type) "timeconv"
                    set dummies($type) [subexp_dummies $re]
                } else {
                    if { [regsub -all {@NUMBER@} $re \
                              {([0-9]+(\\.[0-9]*)?)} \
                              reexpand($type)] == 1} {
                        set conv($type) "numberconv"
                        set dummies($type) [subexp_dummies $re]
                    } else {
                        puts stderr "Error in line [expr $i + 1] of\
                                     $rulefilename: Multiple type specifiers\
                                     in regular expression"
                        return 3
                    }
                }   
            }
        }
    }

    set types [array names reexpand *]

    if {[array names reexpand USER] == [list]} {
        puts stderr "Error: $rulefilename: No extraction rule for USER specified"
        return 4
    }
    if {[array names reexpand SYS] == [list]} {
        puts stderr "Error: $rulefilename: No extraction rule for SYS specified"
        return 5
    }

    if { $trace } {
        puts "types: $types"

        foreach type $types {
            puts "${type}: $reexpand($type) $dummies($type) $conv($type)"
        }
    }

    return 0
}                   

# parse_outputschematafile --
#
#   Parses a file which contains schemata specifying 
#   the output
#
# Arguments:
#   schemafilename   the file name of the file to be parsed
#   outputschemataname  the name of the associative array holding the
#                       output schemata
#
# Results: 0 upon success, > 0 upon failure:
#          1 couldn't open file
#          2 couldn't close file
#
proc parse_outputschematafile {schemafilename outputschemataname} {

    upvar 1 $outputschemataname outputschemata
    global trace

    # Read the file into the local array "lines".
    if {[set returncode [read_file $schemafilename lines]] > 0} {
        return $returncode
    }

    # parse each line
    for {set i 0} {$i < [array size lines]} {incr i} {
        # ignore comments and blank lines
        if {[regexp {^[ 	]*$} $lines($i)] == 0 
            && [regexp {^\#.*$} $lines($i)] == 0} {
            if { [regexp {^([^	]*)	([^	]*)	(.*)$}\
                       $lines($i) dummy schema format rest ] == 0} {
                puts stderr "Warning: bad specification on line\
                             [expr $i + 1] in $schemafilename, ignored"
            } else {
                # At most one line for each schema, redefinitions override.
                if {[array names outputschemata $schema] != [list]} {
                    puts stderr "Warning: $schema redefined on line\
                                 [expr $i + 1] in $schemafilename, overrides\
                                 previous definition"
                }
		set outputschemata($schema) [list $format [split $rest "	"]]
	    }
	}
    }
}

proc add_to_array_index {arrayname index string {separator " "}} {
    upvar 1 $arrayname array

    if { [info exists array($index)] } {
        set array($index) "$array($index)$separator$string"
    } else {
        set array($index) $string
    }
}

proc ensure_completeness {index tagsname descname problemoptionsname filesname} {
    upvar 1 $tagsname tags\
        $descname descriptions\
        $problemoptionsname problemoptions\
        $filesname files
    add_to_array_index tags $index "" ""
    add_to_array_index descriptions $index "" "
"
    add_to_array_index problemoptions $index "" ""
    add_to_array_index files $index "" ""

    if { $files($index) == "" } {
        if { $tags($index) == "" } {
            puts stderr "Warning: Problem $tags($index) did not contain files definition."
        } else {
            puts stderr "Warning: Problem $tags($index) did not contain files definition."
        }
    }
}

# parse_problemarray --
#
#   Parses an array which contains definitions of problem instances.
#
# Arguments:
#   problemarray        the name of the array to be parsed
#   tagsname            the name of the array which holds the problem tags
#   descname            the name of the array which holds the problems' 
#                       descriptions
#   problemoptionsname  the name of the array which holds the problems' 
#                       commandline options
#   filesname           the name of the array which holds the problems' files
#                       for the commandline
#   intermediatechannel the channel to the file in which intermediate
#                       values are stored
#   problemfilename     the file from which the array is from
#   fileoffset          lineoffset in $problemfilename
#
proc parse_problemarray {problemarray tagsname descname problemoptionsname filesname intermediatechannel problemfilename fileoffset} {

    global trace argv0

    upvar 1 $problemarray lines\
        $tagsname tags\
        $descname descriptions\
        $problemoptionsname problemoptions\
        $filesname files

    # In each line, match the corresponding definitions.
    # If problem(<tag>) is encountered, a new problem is created.
    # If more than one definition is given for description, problemoptions, or files,
    # concatenation is applied.
    for {set problems_count 0; set lines_count 0} {$lines_count < [array size lines]} {incr lines_count} {
        set line $lines($lines_count)

        if { $line == "" || [regexp {^[ 	]*$} $line] == 1 || [regexp {^\\#.*$} $line] == 1 } {
            continue
        }
        if {[regexp {^problem\((.*)\)$} $line dummy tag] == 1 } {
            if { $problems_count > 0 } {
                ensure_completeness $problems_count tags descriptions problemoptions files
            }
            incr problems_count
            add_to_array_index tags $problems_count $tag
            continue
        }
        if {[regexp {^description\((.*)\)$} $line dummy desc] == 1} {
            add_to_array_index descriptions $problems_count $desc "
"
            continue
        }
        if {[regexp {^options\((.*)\)$} $line dummy opts] == 1 } {
            add_to_array_index problemoptions $problems_count $opts
            continue
        }
        if {[regexp {^files\((.*)\)$} $line dummy fls] == 1 } {
            add_to_array_index files $problems_count $fls
            continue
        }
        puts stderr "$argv0: Syntax error on line [expr $lines_count + $fileoffset + 1] in file $problemfilename."
    }

    ensure_completeness $problems_count tags descriptions problemoptions files

    set problemdir [file dirname $problemfilename]

    for {set i 1} {$i <= [array size files]} {incr i} {
        for {set j 0} {$j < [llength $files($i)]} {incr j} {
            set files($i)\
                [lreplace $files($i) $j $j\
                     "[file dirname $problemfilename]/[lindex $files($i) $j]"]
        }
    }

    if { $trace } {    
        for {set i 1} {$i <= [array size tags]} {incr i} {
            puts "Problem \#$i:"
            puts $descriptions($i)
            puts "dlv $problemoptions($i) $files($i)"
        }
    }
}

# parse_problemfile --
#
#   Parses a file which contains definitions of problem instances.
#
# Arguments:
#   problemfilename     the file name of the file to be parsed
#   tagsname            the name of the array which holds the problem tags
#   descname            the name of the array which holds the problems' 
#                       descriptions
#   problemoptionsname  the name of the array which holds the problems' 
#                       commandline options
#   filesname           the name of the array which holds the problems' files
#                       for the commandline
#   intermediatechannel the channel to the file in which intermediate
#                       values are stored
#
# Results: 0 upon success, > 0 upon failure:
#          1 couldn't open file
#          2 couldn't close file
#
proc parse_problemfile {problemfilename tagsname descname problemoptionsname filesname intermediatechannel} {

    # Read the file into the local array "lines".
    if {[set returncode [read_file $problemfilename lines]] > 0} {
        return $returncode
    }

    if {$intermediatechannel != ""} {
        puts $intermediatechannel "@problems begin"
        for {set lines_count 0} {$lines_count < [array size lines]} {incr lines_count} {
            puts $intermediatechannel $lines($lines_count)
        }
        puts $intermediatechannel "@problems end"
        flush $intermediatechannel
    }

    upvar 1 $tagsname tags\
        $descname descriptions\
        $problemoptionsname problemoptions\
        $filesname files

    parse_problemarray lines tags descriptions problemoptions files $intermediatechannel $problemfilename 0

}


# perform_test --
#
#   perform the tests and extract and compute the timings
#
# Arguments:
#   executablesname     name of the array containing the executable names
#   tagsname            name of the array containing the problem tags
#   problemoptionsname  name of the array containing the problem options
#   filesname           name of the array containing the problem files
#   typesname           name of the list holding the types
#   measuresname        name of the array which holds the measures
#   numberRuns          number of runs to be performed for each pair of
#                       executable and problem
#   intermediatechannel the channel to the file in which intermediate
#                       values are stored
#   options             name of the array holding the global options
# Results:
#   Returns nothing
#
proc perform_test {outfile executablesname tagsname problemoptionsname filesname typesname measuresname numberRuns intermediatechannel optionsname} {

    upvar 1 $executablesname executables\
        $tagsname tags\
        $problemoptionsname problemoptions\
        $filesname files\
        $typesname types\
        $measuresname measures\
        $optionsname options

    global reexpand conv dummies
    global errorCode trace errorchannel
    global error_string no_match_string

    # Do the following numberRuns times:
    for {set h 0} {$h < $numberRuns} {incr h} {

        for {set i 0} {$i < [array size executables]} {incr i} {
            set runexecutables($i) true
        }

        # Run each problem
        for {set j 1} {$j <= [array size tags]} {incr j} {
            # ... with each executable 
            for {set i 0} {$i < [array size executables]} {incr i} {
                if { $runexecutables($i) } {
                    # run it
                    if { $trace } {
                        puts "Running: $executables($i) $problemoptions($j) $files($j) > $outfile"
                    }

                    if { $intermediatechannel != "" } {
                        puts $intermediatechannel\
                            "@comment [clock format [clock seconds] -format "%Y-%m-%d %H:%M:%S"] on [info hostname]: $executables($i) $problemoptions($j) $files($j)"
                        # Flush output to the log after writing @comment but
                        # before writing @run, as the latter causes problems
                        # when we try to extract intermediate results.
                        flush $intermediatechannel
                        if { $options(intermediateformat) == 2.0} {
                            puts $intermediatechannel "@run $i $j $h"
                        }
                    }

                    # catch returns 1 if either the exec'ed command had an
                    # errorcode or something has been printed onto stderr.
                    # If the variable errorCode holds the string "NONE", then
                    # the exec'ed command terminated without errorcode but
                    # printed something onto stderr. If the variable errorCode
                    # holds some other value, then the exec'ed command
                    # terminated with an error.
                    if { [catch {eval exec "$executables($i) $problemoptions($j) $files($j) > $outfile"} text] && $errorCode != "NONE"} {
                        # we read something from stderr; if something was
                        # printed there, catch will return 1 (actually
                        # indicating failure) while errorCode will be set to
                        # NONE. If errorCode is something else, the call
                        # exited with an error.
                        puts $errorchannel "Warning: $executables($i)\
                            $problemoptions($j) $files($j) exited with error code\
                            $errorCode."

                        foreach type $types {
                            set measures([list $i $j $h $type]) $error_string
                        }

                        if { $options(stopafterfirstfailure) } {
                            set runexecutables($i) false
                        }
                    } else {
                        foreach type $types {
                            if {[regexp [string trim $reexpand($type)] $text\
                                     dummy [string trim\
                                                "$dummies($type) thismeasure"]]\
                                    < 1} {
                                puts $errorchannel "No match for $type."
                                set measures([list $i $j $h $type]) $no_match_string
                            } else {
                                # convert it to a number
                                set measures([list $i $j $h $type])\
                                    [$conv($type) $thismeasure]
                            }
                        }
                    }

                    if { $trace } {
                        puts "$executables($i) $problemoptions($j) $files($j) > $outfile produces:"
                        foreach type $types {
                            puts "$i $j $h $type [set measures([list $i $j $h $type])]"
                        }
                    }
                    if { $intermediatechannel != "" } {
                        foreach type $types {
                            if { $options(intermediateformat) == 2.0 } {
                                puts $intermediatechannel\
                                    "$type [set measures([list $i $j $h $type])]"
                                flush $intermediatechannel
                            }                            
                            if { $options(intermediateformat) == 1.0 } {
                                puts $intermediatechannel\
                                    "$i $j $h $type [set measures([list $i $j $h $type])]"
                                flush $intermediatechannel
                            }
                        }
                    }
                }
            }
        }
    }

}

# calculate_output --
#
#   calculates the output values
#
# Arguments:
#   tagsname           the name of the array containing the problem tags
#   executablesname    the name of the array containing the executables
#   measuresname       the name of the array containing the measures
#   outputschemataname the name of the array containing the outputschemata
#   outputname         the name of the array containing the output strings
#   numberRuns         number of runs to be performed for each pair of
#                      executable and problem
# Results:
#   Does not return anything.
#
proc calculate_output {tagsname executablesname measuresname\
                           outputschemataname outputname numberRuns} {

    upvar 1 $tagsname tags\
        $executablesname executables\
        $measuresname measures\
        $outputschemataname outputschemata\
        $outputname output

    global trace errorchannel

    proc is_valid_measure {string} {
        if { [info tclversion] >= 8.3 } {
            return [string is double -strict $string]
        } else {
            if { [catch {format %f $string} dummy] == 0 } {
                return 1
            } else {
                return 0
            }
        }
    }

    proc measures_flatten {measure_list} {
        global no_measure_string not_const_string

        if { [llength "$measure_list"] == 0 } {
            return $no_measure_string
        }
        set const_measure [lindex $measure_list 0]
        foreach measure $measure_list {
            if { $const_measure != $measure } {
                return $not_const_string
            }
        }
        return $const_measure
    }

    proc min {measure_list} {
        set goodmeasures 0

        foreach measure $measure_list {
            if { [is_valid_measure $measure] } {
                incr goodmeasures
                if { [info exists minimum] } {
                    if { $minimum > $measure } {
                        set minimum $measure
                    }
                } else {
                    set minimum $measure
                }
            }
        }
        if { $goodmeasures != 0 } {
            return $minimum
        } else {
            return [measures_flatten "$measure_list"]
        }
    }

    proc max {measure_list} {
        set goodmeasures 0

        foreach measure $measure_list {
            if { [is_valid_measure $measure] } {
                incr goodmeasures
                if { [info exists maximum] } {
                    if { $maximum < $measure } {
                        set maximum $measure
                    }
                } else {
                    set maximum $measure
                }
            }
        }
        if { $goodmeasures != 0 } {
            return $maximum
        } else {
            return [measures_flatten "$measure_list"]
        }
    }

    proc min_cautious {measure_list} {
        global not_a_number_string
        set goodmeasures 0

        foreach measure $measure_list {
            if { [is_valid_measure $measure] } {
                incr goodmeasures
                if { [info exists minimum] } {
                    if { $minimum > $measure } {
                        set minimum $measure
                    }
                } else {
                    set minimum $measure
                }
            } else {
                return $not_a_number_string
            }
        }
        if { $goodmeasures != 0 } {
            return $minimum
        } else {
            return [measures_flatten "$measure_list"]
        }
    }

    proc max_cautious {measure_list} {
        global not_a_number_string

        set goodmeasures 0

        foreach measure $measure_list {
            if { [is_valid_measure $measure] } {
                incr goodmeasures
                if { [info exists maximum] } {
                    if { $maximum < $measure } {
                        set maximum $measure
                    }
                } else {
                    set maximum $measure
                }
            } else {
                return $not_a_number_string
            }
        }
        if { $goodmeasures != 0 } {
            return $maximum
        } else {
            return [measures_flatten "$measure_list"]
        }
    }

    proc arithmetic_mean {measure_list} {
        set sum 0
        set goodmeasures 0

        foreach measure $measure_list {
            if { [is_valid_measure $measure] } {
                incr goodmeasures
                set sum [expr $sum + $measure]
            }
        }

        if { $goodmeasures != 0 } {
            return [expr $sum / $goodmeasures]
        } else {
            return [measures_flatten "$measure_list"]
        }
    }

    proc arithmetic_mean_cautious {measure_list} {
        global not_a_number_string

        set sum 0
        set goodmeasures 0

        foreach measure $measure_list {
            if { [is_valid_measure $measure] } {
                incr goodmeasures
                set sum [expr $sum + $measure]
            } else {
                return $not_a_number_string
            }
        }

        if { $goodmeasures != 0 } {
            return [expr $sum / $goodmeasures]
        } else {
            return [measures_flatten "$measure_list"]
        }
    }

    proc standard_deviation {measure_list} {
        set sum 0
        set goodmeasures 0
        set avg [arithmetic_mean $measure_list]

        foreach measure $measure_list {
            if { [is_valid_measure $measure] } {
                incr goodmeasures
                set sum [expr $sum + pow(($measure - $avg),2)]
            }
        }

        if { $goodmeasures != 0 } {
            return [expr sqrt($sum / $goodmeasures)]
        } else {
            return [measures_flatten "$measure_list"]
        }
    }

    proc plus {measure_list1 measure_list2} {
        set measure_list [list]

        foreach measure1 $measure_list1 measure2 $measure_list2 {
            if { [is_valid_measure $measure1] && [is_valid_measure $measure2] } {
                lappend measure_list [expr $measure1 + $measure2]
            } else {
                lappend measure_list [measures_flatten [list "$measure1" "$measure2"]]
            }
        }

        return $measure_list
    }

    proc percent {measure_list1 measure_list2} {
        set measure_list [list]

        foreach measure1 $measure_list1 measure2 $measure_list2 {
            if { [is_valid_measure $measure1] && [is_valid_measure $measure2] } {
                lappend measure_list [expr 100.0 * $measure1 / $measure2]
            } else {
                lappend measure_list [measures_flatten [list "$measure1" "$measure2"]]
            }
        }

        return $measure_list
    }

    proc measure {type} {
        upvar 1 executable_index executable_index\
            tag_index tag_index\
            measures measures\
            numberRuns numberRuns

	global no_measure_string

        set measure_list [list]

        for {set run_index 0} {$run_index < $numberRuns} {incr run_index} {
            if {[array names measures\
                 [list $executable_index $tag_index $run_index $type]]\
                != [list]} {
                lappend measure_list\
                    $measures([list $executable_index $tag_index\
                                   $run_index $type])
            } else {
                lappend measure_list $no_measure_string
            }
        }

        return $measure_list
    }

    proc measure_const {type} {
        upvar 1 executable_index executable_index\
            tag_index tag_index\
            measures measures\
            numberRuns numberRuns

            global errorchannel no_measure_string not_const_string

        set measure $no_measure_string

        for {set run_index 0} {$run_index < $numberRuns} {incr run_index} {
            if {[array names measures\
                 [list $executable_index $tag_index $run_index $type]]\
                != [list]} {
                set this_measure\
                    $measures([list $executable_index $tag_index\
                                   $run_index $type])
            } else {
                set this_measure $no_measure_string
            }
            if { $this_measure != $no_measure_string } {
                if { $measure != $no_measure_string } {
                    if { $measure != $this_measure } {
                        puts $errorchannel "$type used as constant but has varying values ($measure, $this_measure)."
                        set measure $not_const_string
                        break
                    }
                } else {
                    set measure $this_measure
                }
            }
        }

        return $measure
    }

    foreach outputscheme [array names outputschemata *] {
        set outputschemespec $outputschemata($outputscheme)
        set origformatstring [lindex $outputschemespec 0]
        set calcspecs [lindex $outputschemespec 1]

        set formatinfo [parse_formatstring $origformatstring]
        set formatstring [lindex $formatinfo 0]
        set speclist [lindex $formatinfo 1]

        if {[llength $speclist] != [llength $calcspecs]} {
            puts $errorchannel "Error: Format specifiers and arguments mismatch."
            puts $errorchannel "specifiers: [llength $speclist] ($speclist), arguments: [llength $calcspecs] ($calcspecs)"
        }

        # Compute average and standard deviation of the runs of a pair
        # executable-testcase.
        for {set executable_index 0}\
            {$executable_index < [array size executables]}\
            {incr executable_index} {
            for {set tag_index 1}\
                {$tag_index <= [array size tags]}\
                {incr tag_index} {
                set i 0
                set vals ""
                foreach spec $speclist calcspec $calcspecs {
                    set val$i [subst $calcspec]
                    if {[is_valid_measure "[set val$i]"]} {
                        set [lindex $spec 0] [lindex $spec 1]
                    } else {
                        set [lindex $spec 0] [lindex $spec 2]
                    }
                    set vals "$vals \$val$i"
                    incr i
                }
   
                if { [catch {eval format {[subst $formatstring]} $vals} output([list $executable_index $tag_index $outputscheme])] != 0 } {
                    set output([list $executable_index $tag_index $outputscheme]) [subst $vals]
                }

#                set output([list $executable_index $tag_index $outputscheme])\
#                    [eval format {[subst $formatstring]} $vals]

                if { $trace } {
                    puts "Output for executable $executables($executable_index), problem $tags($tag_index) and output schema $outputscheme: [set output([list $executable_index $tag_index $outputscheme])]"
                }
            }
        }
    }
}


# print_table --
#
#   prints a given matrix (list of lists representing columns), interpreting
#   the first line as a headline (which is printed centered), and the other
#   lines as data. The data is split into fields separated by spaces, the
#   fields are aligned on their decimal points, if they are not numbers
#   they are treated as integers, which causes troubles for data containing
#   dots which are not decimal points.
#
# Arguments:
#   matrix     the table to print (list of columns, which are lists as well)
# Results:
#   Does not return anything.
#
proc print_table {matrix} {
    # First determine the maximum entry- and field-lengths.
    set max_field_length_list [list]
    set max_entry_length_list [list]

    set firstcolumn [lindex $matrix 0]
    set datamatrix [lrange $matrix 1 end]

    # Do not consider fields in the first column.
    set max_entry_length 0
    foreach entry $firstcolumn {
        # If the current entry-lengths is greater than the maximum,
        # set the maximum accordingly.
        if {[string length $entry] > $max_entry_length} {
            set max_entry_length [string length $entry]
        }
    }
    lappend max_field_length_list [list]
    lappend max_entry_length_list $max_entry_length

    # Consider fields for all other columns.
    foreach column $datamatrix {
        set max_field_lengths [list]
        set max_entry_length 0
        # Do not consider the first line for field lengths - it is not
        # aligned in fields. Nevertheless, it is considered for determining
        # the entry lengths. So the first entry must get special treatment.
        set initial 1
        foreach entry $column {
            # If the current entry-lengths is greater than the maximum,
            # set the maximum accordingly.
            if {[string length $entry] > $max_entry_length} {
                set max_entry_length [string length $entry]
            }

	    # If we are in the first line, skip the following field length
            # code.
            if {$initial == 1} {
                set initial 0
                continue
            }

            set entry_list [split $entry]

	    # initialise missing maxima
            if {[llength $entry_list] > [llength $max_field_lengths]} {
                for {set diff [expr [llength $entry_list] - [llength $max_field_lengths]]} {$diff > 0} {incr diff -1} {
                    lappend max_field_lengths {0 0}
                }
            }

	    # determine the new maxima
            set new_max_field_lengths [list]
            set new_max_entry_length -1
            foreach field $entry_list max_field_length $max_field_lengths {
                # determine precomma part
                if {[regexp {^[^.]*} $field precomma] == 0} {
                    puts $errorchannel "Internal Error: Always matching regular expression didn't match."
                }
                set precomma_length [string length $precomma]
                set postcomma_length [expr [string length $field]\
                                          - $precomma_length]
                if {$precomma_length > [lindex $max_field_length 0]} {
                    set new_max_pre $precomma_length
                } else {
                    set new_max_pre [lindex $max_field_length 0]
                }
                if {$postcomma_length > [lindex $max_field_length 1]} {
                    set new_max_post $postcomma_length
                } else {
                    set new_max_post [lindex $max_field_length 1]
                }
                lappend new_max_field_lengths [list $new_max_pre $new_max_post]

                incr new_max_entry_length [expr $new_max_pre + $new_max_post + 1]
            }
            set max_field_lengths $new_max_field_lengths

            if {$new_max_entry_length > $max_entry_length} {
                set max_entry_length $new_max_entry_length
            }
        }
	# add the maxima for the current column
        lappend max_field_length_list $max_field_lengths
        lappend max_entry_length_list $max_entry_length
    }

    # print the first line: center entries
    foreach max_field_lengths $max_field_length_list\
        max_entry_length $max_entry_length_list\
        column $matrix {

        set entry [lindex $column 0]
        set length [string length $entry]

        set spaces [expr $max_entry_length - $length]
        set prespaces [expr $spaces / 2]
        set postspaces [expr $spaces - $prespaces]

        puts -nonewline " "
        for {set i 0} {$i < $prespaces} {incr i} {
            puts -nonewline " "
        }
        puts -nonewline $entry
        for {set i 0} {$i < $postspaces} {incr i} {
            puts -nonewline " "
        }
        puts -nonewline " |"
    }
    puts ""

    # second line
    foreach max_entry_length $max_entry_length_list {
        puts -nonewline "-"
        for {set i 0} {$i < $max_entry_length} {incr i} {
            puts -nonewline "-"
        }
        puts -nonewline "-+"
    }
    puts ""

    # the rest of the lines
    set lines [llength $firstcolumn]

    for {set lineindex 1} {$lineindex < $lines} {incr lineindex} {
        set initial 1
        foreach max_field_lengths $max_field_length_list\
	    max_entry_length $max_entry_length_list\
	    column $matrix {

            set entry [lindex $column $lineindex]

            # The first column is not split into fields.
            if {$initial} {
                set initial 0
                set total_length [string length $entry]
                # first fill in spaces to match the maximum entry length
                for {set k 0} {$k <= [expr $max_entry_length\
                                          - $total_length]} {incr k} {
                    puts -nonewline " "
                }
                # print the entry
                puts -nonewline $entry
            } else {
                set entry_list [split $entry]
                set printedlength 0
                foreach field $entry_list max_field_length $max_field_lengths {
                    # determine precomma part
                    if {[regexp {^[^.]*} $field precomma] == 0} {
                        puts $errorchannel "Internal Error: Always matching regular expression didn't match."
                    }
                    set precomma_length [string length $precomma]
                    set postcomma_length [expr [string length $field]\
                                              - $precomma_length]
                    set max_precomma_length [lindex $max_field_length 0]
                    set max_postcomma_length [lindex $max_field_length 1]

                    puts -nonewline " "
                    incr printedlength

                    # first fill in spaces for the maximum pre comma length
                    for {set k 0} {$k < [expr $max_precomma_length\
                                             - $precomma_length]} {incr k} {
                        puts -nonewline " "
                        incr printedlength
                    }
                    # print the value
                    puts -nonewline $field
                    incr printedlength [string length $field]
                    # print spaces to match the maximum postcomma length
                    for {set k 0} {$k < [expr $max_postcomma_length\
                                             - $postcomma_length]} {incr k} {
                        puts -nonewline " "
                        incr printedlength
                    }
                }

                # if we did not print max_entry_length 
                if {$printedlength > 0\
                    && [expr $printedlength - 1] < $max_entry_length} {
                    for {set k 0} {$k < [expr $max_entry_length\
                                             - $printedlength + 1]} {incr k} {
                        puts -nonewline " "
                    }
                } else {
                    if {$printedlength == 0} {
                        for {set k 0} {$k < $max_entry_length} {incr k} {
                            puts -nonewline " "
                        }
                    }
                }
            }
            puts -nonewline " |"
        }
        puts ""
    }

    # last line
    foreach max_entry_length $max_entry_length_list {
        puts -nonewline "-"
        for {set i 0} {$i < $max_entry_length} {incr i} {
            puts -nonewline "-"
        }
        puts -nonewline "-+"
    }
    puts ""
}

# prettyprint_output --
#
#   prints the timings as text in a nice way
#
# Arguments:
#   tagsname           the name of the array containing the problem tags
#   descriptionsname   the name of the array containing the problem
#                      descriptions
#   executablesname    the name of the array containing the executables
#   outputschemataname the name of the array containing the outputschemata
#   outputname         the name of the array containing the output strings
#   numberRuns         number of runs to be performed for each pair of
#                      executable and problem
# Results:
#   Does not return anything.
#
proc prettyprint_output {tagsname descriptionsname executablesname\
                         outputschemataname outputname} {

    upvar 1 $tagsname tags\
        $descriptionsname descriptions\
        $executablesname executables\
        $outputschemataname outputschemata\
        $outputname output

    global trace

    #####
    # first print the executable names
    for {set i 0} {$i < [array size executables]} {incr i} {
        puts "\[$i\]: $executables($i)"
    }
    puts ""

    if {[llength [array names outputschemata *]] > 1} {

        #####
        # Multiple outputschemata:
        # |tags| tables:
        for {set tagindex 1} {$tagindex <= [array size tags]} {incr tagindex} {
            set outputmatrix [list]
       
            # First column: problemtag name, followed by the schemata
            set column [list "$tags($tagindex)"]
            foreach schema [array names outputschemata *] {
                lappend column $schema
            }
            lappend outputmatrix $column

            # Second to |executables|+1st column: [executable index] 
            # followed by |schemata| output values computed by
            # calculate_output.
            for {set exeindex 0} {$exeindex < [array size executables]} {incr exeindex} {
                set column [list "\[$exeindex\]"]
                foreach schema [array names outputschemata *] {
                    lappend column [set output([list $exeindex $tagindex $schema])]
                }
                lappend outputmatrix $column
            }
            print_table $outputmatrix
            puts ""
        }
    } else {
        if {[llength [array names outputschemata *]] == 0} {
            puts $errorchannel "Warning: No outputschema specified."
            return
        }

        set schema [lindex [array names outputschemata *] 0]
        set outputmatrix [list]

        #####
        # Single outputschema:
        # First column: empty string, followed by |tags| [problemtag]

        set column [list ""]
        for {set tagindex 1} {$tagindex <= [array size tags]} {incr tagindex} {
            lappend column "$tags($tagindex)"
        }

        lappend outputmatrix $column

        # Second to |executables|+1st column: [executable index] followed by
        # |tags| output values computed by calculate_output.

        for {set exeindex 0} {$exeindex < [array size executables]} {incr exeindex} {
            set column [list "\[$exeindex\]"]
            for {set tagindex 1} {$tagindex <= [array size tags]} {incr tagindex} {
                lappend column [set output([list $exeindex $tagindex $schema])]
            }
            lappend outputmatrix $column
        }

        #####
        # print the table
        print_table $outputmatrix
    }

    #####
    # now print the problem descriptions
    puts ""
    for {set i 1} {$i <= [array size tags]} {incr i} {
        if { $descriptions($i) != "" } {
            puts "$tags($i):"
            puts "$descriptions($i)"
        }
    }
}

# listprint_output --
#
#   prints the timings as a list
#
# Arguments:
#   tagsname           the name of the array containing the problem tags
#   descriptionsname   the name of the array containing the problem
#                      descriptions
#   executablesname    the name of the array containing the executables
#   outputschemataname the name of the array containing the outputschemata
#   outputname         the name of the array containing the output strings

# Results:
#   Does not return anything.
#
proc listprint_output {tagsname descriptionsname executablesname\
                         outputschemataname outputname} {

    upvar 1 $tagsname tags\
        $descriptionsname descriptions\
        $executablesname executables\
        $outputschemataname outputschemata\
        $outputname output

    global trace

    for {set tagindex 1} {$tagindex <= [array size tags]} {incr tagindex} {
        for {set exeindex 0} {$exeindex < [array size executables]} {incr exeindex} {
            foreach schema [array names outputschemata *] {
                puts -nonewline "$exeindex	$tags($tagindex)	"
                puts -nonewline "$schema	"
                puts [set output([list $exeindex $tagindex $schema])]
            }
        }
    }
}

# print_latex_table --
#
#   prints a given matrix (list of lists representing columns), interpreting
#   the first line as a headline (which is printed centered), and the other
#   lines as data as a LaTeX tabular.
#
# Arguments:
#   matrix     the table to print (list of columns, which are lists as well)
# Results:
#   Does not return anything.
#
proc print_latex_table {matrix} {

    set formatstring "|l|"

    for {set fieldindex 1} {$fieldindex < [llength $matrix]} {incr fieldindex} {
        set formatstring "${formatstring}r|"
    }

    puts "\\begin\{tabular\}\{$formatstring\}"
    puts "\\hline"
    
    # print the first line

    set firstentry 1
    foreach column $matrix {
        if {$firstentry} {
            set firstentry 0
        } else {
            puts -nonewline "&"
        }

        puts -nonewline "[lindex $column 0]"
    }
    puts "\\\\"
    puts "\\hline"

    # the rest of the lines
    set lines [llength [lindex $matrix 0]]

    for {set lineindex 1} {$lineindex < $lines} {incr lineindex} {
        set firstentry 1
        foreach column $matrix {
            if {$firstentry} {
                set firstentry 0
            } else {
                puts -nonewline "&"
            }

            puts -nonewline "[lindex $column $lineindex]"
        }
        puts "\\\\"
    }
    puts "\\hline"

    puts "\\end\{tabular\}"

    puts ""
}

# print_tab_table --
#
#   prints a given matrix (list of lists representing columns), interpreting
#   the first line as a headline (which is simply skipped), and the other
#   lines as data which are output by simply filling in TABs.
#
# Arguments:
#   matrix     the table to print (list of columns, which are lists as well)
# Results:
#   Does not return anything.
#
proc print_tab_table {matrix} {

    # do not print the first line

#    set firstentry 1
#    foreach column $matrix {
#        if {$firstentry} {
#            set firstentry 0
#        } else {
#            puts -nonewline "\t"
#        }
#
#        puts -nonewline "[lindex $column 0]"
#    }
#
#    puts ""

    # the rest of the lines
    set lines [llength [lindex $matrix 0]]

    for {set lineindex 1} {$lineindex < $lines} {incr lineindex} {
        set firstentry 1
        foreach column $matrix {
            if {$firstentry} {
                set firstentry 0
            } else {
                puts -nonewline "\t"
            }

            puts -nonewline "[lindex $column $lineindex]"
        }
        puts ""
    }
}

# matrixprint_output --
#
#   forms a matrix of the data and calls a procedure for printing it
#
# Arguments:
#   tagsname           the name of the array containing the problem tags
#   descriptionsname   the name of the array containing the problem
#                      descriptions
#   executablesname    the name of the array containing the executables
#   outputschemataname the name of the array containing the outputschemata
#   outputname         the name of the array containing the output strings
#   printproc          the name of the procedure for printing the matrix

# Results:
#   Does not return anything.
#
proc matrixprint_output {tagsname descriptionsname executablesname\
                         outputschemataname outputname printproc} {

    upvar 1 $tagsname tags\
        $descriptionsname descriptions\
        $executablesname executables\
        $outputschemataname outputschemata\
        $outputname output

    global trace

    if {[llength [array names outputschemata *]] > 1} {

        #####
        # Multiple outputschemata:
        # |tags| tables:
        for {set tagindex 1} {$tagindex <= [array size tags]} {incr tagindex} {
            set outputmatrix [list]
       
            # First column: problemtag name, followed by the schemata
            set column [list "$tags($tagindex)"]
            foreach schema [array names outputschemata *] {
                lappend column $schema
            }
            lappend outputmatrix $column

            # Second to |executables|+1st column: [executable index] 
            # followed by |schemata| output values computed by
            # calculate_output.
            for {set exeindex 0} {$exeindex < [array size executables]} {incr exeindex} {
                set column [list "\[$exeindex\]"]
                foreach schema [array names outputschemata *] {
                    lappend column [set output([list $exeindex $tagindex $schema])]
                }
                lappend outputmatrix $column
            }
            $printproc $outputmatrix
            puts ""
        }
    } else {
        if {[llength [array names outputschemata *]] == 0} {
            puts $errorchannel "Warning: No outputschema specified."
            return
        }

        set schema [lindex [array names outputschemata *] 0]
        set outputmatrix [list]

        #####
        # Single outputschema:
        # First column: empty string, followed by |tags| [problemtag]

        set column [list ""]
        for {set tagindex 1} {$tagindex <= [array size tags]} {incr tagindex} {
            lappend column "$tags($tagindex)"
        }

        lappend outputmatrix $column

        # Second to |executables|+1st column: [executable index] followed by
        # |tags| output values computed by calculate_output.

        for {set exeindex 0} {$exeindex < [array size executables]} {incr exeindex} {
            set column [list "\[$exeindex\]"]
            for {set tagindex 1} {$tagindex <= [array size tags]} {incr tagindex} {
                lappend column [set output([list $exeindex $tagindex $schema])]
            }
            lappend outputmatrix $column
        }

        #####
        # print the table
        $printproc $outputmatrix
    }

}

###############################################################################
###############################################################################
# end of functions
###############################################################################
###############################################################################

# There should be at least 5 arguments.

if {$argc < 5} then {
    puts stderr {Usage: $argv0 (-e|--extractionrulefile) <filename>}
    puts stderr {              (-p|--problemfile) <filename>}
    puts stderr {              (-o|--outputschemafile) <filename>}
    puts stderr {              -n=<N>}
    puts stderr {              [--stopafterfirstfailure]}
    puts stderr {              [(-i|--intermediatefile) <filename>]}
    puts stderr {              [--postfix <postfix> | --postfix=<postfix>]}
    puts stderr {              [--prefix <prefix> | --prefix=<prefix>]}
    puts stderr {              [--backend=(table|list|latex|tabtable)]}
    puts stderr {              [--errorfile <filename>]}
    puts stderr {              [--] <executable> [...]}
    puts ""
    puts stderr {Or:    $argv0 --postprocess}
    puts stderr {              (-i|--intermediatefile) <filename>}
    puts stderr {              (-o|--outputschemafile) <filename>}
    puts stderr {              [--backend=(table|list|latex|tabtable)]}
    exit 1
}



##############################################################################
# parse command-line

set options(postprocessing) 0
set options(backend) "table"
set options(stopafterfirstfailure) false
set options(intermediateformat) 2.0

set argindex 0
while {$argindex < $argc} {
    set arg [lindex $argv $argindex]
    switch -regexp -- $arg {
        "^--prefix$" {
            incr argindex
            if {[info exists commandprefix]} {
                set commandprefix "$commandprefix [lindex $argv $argindex]"
            } else {
                set commandprefix [lindex $argv $argindex]
            }
        }
        "^--prefix=.*$" {
            if {[info exists commandprefix]} {
                set commandprefix "$commandprefix [string range $arg 9 end]"
            } else {
                set commandprefix [string range $arg 9 end]
            }
        }
        "^--postfix$" {
            incr argindex
            if {[info exists commandpostfix]} {
                set commandpostfix "$commandpostfix [lindex $argv $argindex]"
            } else {
                set commandpostfix [lindex $argv $argindex]
            }
        }
        "^--postfix=.*$" {
            if {[info exists commandpostfix]} {
                set commandpostfix "$commandpostfix [string range $arg 10 end]"
            } else {
                set commandpostfix [string range $arg 10 end]
            }
        }
        "^-e$" -
        "^--extractionrulefile$" {
            if {[info exists rulesfilename]} {
                puts stderr "Error: Extractionrule file specified twice."
                exit 1
            }
            incr argindex
            set rulesfilename [lindex $argv $argindex]
        }
        "^-p$" -
        "^--problemfile$" {
            if {[info exists problemsfilename]} {
                puts stderr "Error: Problem file specified twice."
                exit 1
            }
            incr argindex
            set problemsfilename [lindex $argv $argindex]
        }
        "^-o$" -
        "^--outputschemafile$" {
            if {[info exists outputschematafilename]} {
                puts stderr "Error: Outputschema file specified twice."
                exit 1
            }
            incr argindex
            set outputschematafilename [lindex $argv $argindex]
        }
        "^-i$" -
        "^--intermediatefile$" {
            if {[info exists intermediatefilename]} {
                puts stderr "Error: Intermediate file specified twice."
                exit 1
            }
            incr argindex
            set intermediatefilename [lindex $argv $argindex]
        }
        "-n=[0-9]*$" {
            if {[info exists numberRuns]} {
                puts stderr "Error: Number of runs specified twice."
                exit 1
            }
            set numberRuns [string range $arg 3 end]
        }
        "^--postprocess$" {
            set options(postprocessing) 1
        }
        "^--backend=.*$" {
            set newbackend [string range $arg 10 end]
            if {[lsearch -exact [list table list latex tabtable] $newbackend] < 0} {
                puts stderr "Error: $newbackend: unknown backend."
                exit 1
            }
            set options(backend) $newbackend
        }
        "^--errorfile$" {
            if {[info exists errorfilename]} {
                puts stderr "Error: Errorfile specified twice."
                exit 1
            }
            incr argindex
            set errorfilename [lindex $argv $argindex]
        }
        "^--stopafterfirstfailure$" {
            set options(stopafterfirstfailure) true
        }
        "^--intermediateformat"  {
            incr argindex
            set options(intermediateformat) [lindex $argv $argindex]
        }
        "^--intermediateformat=.*$" {
            set options(intermediateformat) [string range $arg 21 end]
        }
        "^--$" {
            incr argindex
            break
        }
        default {
            break
        }
    }
    incr argindex
}



if { ! [info exists outputschematafilename] } {
    puts stderr "Error: Outputschemafile not specified."
    exit 1
}

if { $options(postprocessing) == 0} {
    if { ! [info exists rulesfilename] } {
        puts stderr "Error: Extractionrulefile not specified."
        exit 1
    }
    if { ! [info exists problemsfilename] } {
        puts stderr "Error: Problemfile not specified."
        exit 1
    }
    if { ! [info exists numberRuns] } {
        puts stderr "Error: Number of runs not specified."
        exit 1
    }

    set firstexeindex $argindex
    for {} {$argindex < $argc} {incr argindex} {
        set exestring ""
        if {[info exists commandprefix] } {
            set exestring "$commandprefix "
        }
        set exestring "${exestring}[lindex $argv $argindex]"
        if {[info exists commandpostfix] } {
            set exestring "${exestring} $commandpostfix"
        }
        set executables([expr $argindex - $firstexeindex]) $exestring
    }
    if { ! [info exists intermediatefilename]} {
        set intermediatefilename ""
        set intermediatechannel ""
    }
} else {
    if { [info exists rulesfilename] } {
        puts stderr "Error: Extractionrulefile specified with postprocessing."
        exit 1
    }
    if { [info exists problemsfilename] } {
        puts stderr "Error: Problemfile specified with postprocessing."
        exit 1
    }
    if { [info exists numberRuns] } {
        puts stderr "Error: Number of runs specified with postprocessing."
        exit 1
    }
    if {$argindex < $argc} {
        puts stderr "Error: Trailing arguments with postprocessing."
        exit 1
    }
    if { ! [info exists intermediatefilename]} {
        puts stderr "Error: Intermediatefile not specified with postprocessing."
        exit 1
    }
}


##############################################################################
# test arguments for validity

if {! [file readable $outputschematafilename]} {
    puts stderr "$outputschematafilename is not a readable file."
    exit 2
}

if { [info exists errorfilename] && [file exists $errorfilename]\
         && ! [file writable $errorfilename] } {
    puts stderr "$errorfilename exists and is not writeable."
    exit 2
}

if { $options(postprocessing) == 0} {
    if {! [file readable $rulesfilename]} {
        puts stderr "$rulesfilename is not a readable file."
        exit 2
    }

    if {! [file readable $problemsfilename]} {
        puts stderr "$problemsfilename is not a readable file."
        exit 2
    }

    if { $intermediatefilename != "" && [file exists $intermediatefilename]\
            && ! [file writable $intermediatefilename] } {
        puts stderr "$intermediatefilename exists and is not writeable."
        exit 2
    }

    if {[scan $numberRuns "%d%s" dummy1 dummy2] != 1} {
        puts stderr "$numberRuns is not an integer."
        exit 2
    }

    for {set i 0} {$i < [array size executables]} {incr i} {
        set firstspace [string first " " $executables($i)]
        if { $firstspace <= 0 } {
            # No space -> only the executable name.
            set exepart $executables($i)
        } else {
            # There is a space -> the executable name is the string up to
            # the first space.
            set exepart [string range $executables($i) 0 [expr $firstspace - 1]]
        }
        if {! [file executable $exepart]} {
            puts stderr "$exepart is not an executable."
            exit 2
        }
    }
} else {
    if { ! [file readable $intermediatefilename] } {
        puts stderr "$intermediatefilename is not readable."
        exit 2
    }
}


#######################################################################
# open the file for the errors, defaults to stderr
if {[info exists errorfilename]} {
    if {[catch {set errorchannel [open $errorfilename w]}\
                errormsg] != 0} {
        puts stderr "Error while opening $errorfilename for writing: $errormsg"
        exit 2
    }
} else {
    set errorchannel stderr
}


if { $options(postprocessing) == 0} {
    #######################################################################
    # open the file for the intermediate values
    if {$intermediatefilename != ""\
            && [catch {set intermediatechannel [open $intermediatefilename w]}\
                    errormsg] != 0} {
        puts stderr "Error while opening $intermediatefilename for writing: $errormsg"
        exit 2
    }

    if {$intermediatechannel != ""} {
        puts $intermediatechannel "@formatversion $options(intermediateformat)"
        puts $intermediatechannel "@executables begin"
        for {set i 0} {$i < [array size executables]} {incr i} {
            puts $intermediatechannel "$i $executables($i)"
        }
        puts $intermediatechannel "@executables end"
        flush $intermediatechannel
    }

    #######################################################################
    # parse the file containing the rules for extracting the timings
    if {[set exitcode [parse_rulefile $rulesfilename types reexpand conv dummies]] > 0} {
        exit $exitcode
    }

    #######################################################################
    # parse the file containing the problem definitions
    if {[set exitcode [parse_problemfile $problemsfilename tags descriptions problemoptions files $intermediatechannel]] > 0} {
        exit $exitcode
    }

    if {$intermediatechannel != "" && $options(intermediateformat) == 2.0 } {
        puts $intermediatechannel "@typecount [llength $types]"
        flush $intermediatechannel
    }

    #######################################################################
    # run the problem instances
    perform_test $outfile executables tags problemoptions files types\
        measures $numberRuns $intermediatechannel options
} else {
    #######################################################################
    # parse the file for the intermediate values
    if {[set exitcode [parse_intermediatefile $intermediatefilename tags descriptions executables measures numberRuns]] > 0} {
        exit $exitcode
    }    
}

##############################################################################
# parse the file containing the output schema definitions

if {[set exitcode [parse_outputschematafile $outputschematafilename outputschemata]] > 0} {
    exit $exitcode
}

##############################################################################
# calculate data for output
calculate_output tags executables measures outputschemata output $numberRuns

##############################################################################
# print the output

switch -- $options(backend) {
    "table" {
        prettyprint_output tags descriptions executables outputschemata output
    }
    "list" {
        listprint_output tags descriptions executables outputschemata output
    }
    "latex" {
        matrixprint_output tags descriptions executables outputschemata output print_latex_table
    }
    "tabtable" {
        matrixprint_output tags descriptions executables outputschemata output print_tab_table
    }
}

##############################################################################
# close the file for the intermediate values
if {$options(postprocessing) == 0 && $intermediatechannel != ""} {
    if {[catch "close $intermediatechannel" errormsg] != 0} {
        puts stderr "Error while closing $intermediatefilename: $errormsg"
        exit 2
    }
}
