# -*- tcl -*-
# ### ### ### ######### ######### #########

## This package provides a number of utility commands to
## transformations for common operations. It assumes a 'Normalized PE
## Grammar Tree' as input, possibly augmented with attributes coming
## from transformation not in conflict with the base definition.

# ### ### ### ######### ######### #########
## Requisites

package require page::util::quote

namespace eval ::page::util::peg {
    namespace export \
	    symbolOf symbolNodeOf \
	    updateUndefinedDueRemoval \
	    flatten peOf printTclExpr \
	    getWarnings printWarnings

    # Get the peg char de/encoder commands.
    # (unquote, quote'tcl).

    namespace import ::page::util::quote::*
}

# ### ### ### ######### ######### #########
## API

proc ::page::util::peg::symbolNodeOf {t n} {
    # Given an arbitrary root it determines the node (itself or an
    # ancestor) containing the name of the nonterminal symbol the node
    # belongs to, and returns its id. The result is either the root of
    # the tree (for the start expression), or a definition mode.

    while {![$t keyexists $n symbol]} {
	set n [$t parent $n]
    }
    return $n
}

proc ::page::util::peg::symbolOf {t n} {
    # As above, but returns the symbol name.

    return [$t get [symbolNodeOf $t $n] symbol]
}

proc ::page::util::peg::updateUndefinedDueRemoval {t} {
    # The removal of nodes may have caused symbols to lose one or more
    # users. Example: A used by B and C, B is reachable, C is not, so A
    # now loses a node in the expression for C calling it, or rather
    # not anymore.

    foreach {sym def} [$t get root definitions] {
	set res {}
	foreach u [$t get $def users] {
	    if {![$t exists $u]} continue
	    lappend res $u
	}
	$t set $def users $res
    }

    # Update the knowledge of undefined nonterminals. To be used when
    # a transformation can remove invokations of undefined symbols,
    # and is not able to generate such invokations.

    set res {}
    foreach {sym invokers} [$t get root undefined] {
	set sres {}
	foreach n $invokers {
	    if {![$t exists $n]} continue
	    lappend sres $n
	}
	if {[llength $sres]} {
	    lappend res $sym $sres
	}
    }
    $t set root undefined $res
    return
}

proc ::page::util::peg::flatten {q t} {
    # Flatten nested x-, or /-operators.
    # See peg_normalize.tcl, peg::normalize::ExprFlatten

    foreach op {x /} {
	# Locate all x operators, whose parents are x oerators as
	# well, then go back to the child operators and cut them out.

	$q query \
		tree          withatt op $op \
		parent unique withatt op $op \
		children      withatt op $op \
		over n {
	    $t cut $n
	}
    }
    return
}

proc ::page::util::peg::getWarnings {t} {
    # Look at the attributes for problems with the grammar and issue
    # warnings. They do not prevent us from writing the grammar, but
    # still represent problems with it the user should be made aware
    # of.

    array set msg {}
    array set undefined [$t get root undefined]
    foreach sym [array names undefined] {
	set msg($sym) {}
	foreach ref $undefined($sym) {
	    lappend msg($sym) "Undefined symbol used by the definition of '[symbolOf $t $ref]'."
	}
    }

    foreach {sym def} [$t get root definitions] {
	if {[llength [$t get $def users]] == 0} {
	    set msg($sym) [list "This symbol has been defined, but is not used."]
	}
    }

    return [array get msg]
}

proc ::page::util::peg::printWarnings {msg} {
    if {![llength $msg]} return

    set dict {}
    set max -1
    foreach {k v} $msg {
	set l [string length [list $k]]
	if {$l > $max} {set max $l}
	lappend dict [list $k $v $l]
    }

    foreach e [lsort -dict -index 0 $dict] {
	foreach {k msgs l} $e break

	set off [string repeat " " [expr {$max - $l}]]
	page_info "[list $k]$off : [lindex $msgs 0]"

	if {[llength $msgs] > 1} {
	    set indent [string repeat " " [string length [list $k]]]
	    foreach m [lrange $msgs 1 end] {
		puts stderr "  $indent$off : $m"
	    }
	}
    }
    return
}

proc ::page::util::peg::peOf {t eroot} {
    set op [$t get $eroot op]
    set pe [list $op]

    set ch [$t children $eroot]

    if {[llength $ch]} {
	foreach c $ch {
	    lappend pe [peOf $t $c]
	}
    } elseif {$op eq "n"} {
	lappend pe [$t get $eroot sym]
    } elseif {$op eq "t"} {
	lappend pe [unquote [$t get $eroot char]]
    } elseif {$op eq ".."} {
	lappend pe \
		[unquote [$t get $eroot begin]] \
		[unquote [$t get $eroot end]]

    }
    return $pe
}

proc ::page::util::peg::printTclExpr {pe} {
    list [PrintExprSub $pe]
}

# ### ### ### ######### ######### #########
## Internal

proc ::page::util::peg::PrintExprSub {pe} {
    set op   [lindex $pe 0]
    set args [lrange $pe 1 end]

    #puts stderr "PE [llength $args] $op | $args"

    if {$op eq "t"} {
	set a [lindex $args 0]
	return "$op [quote'tcl $a]"
    } elseif {$op eq ".."} {
	set a [lindex $args 0]
	set b [lindex $args 1]
	return "$op [quote'tcl $a] [quote'tcl $b]"
    } elseif {$op eq "n"} {
	return $pe
    } else {
	set res $op
	foreach a $args {
	    lappend res [PrintExprSub $a]
	}
	return $res
    }
}

# ### ### ### ######### ######### #########
## Ready

package provide page::util::peg 0.2
