# -*- tcl -*- # (C) 2008 Andreas Kupries ## # ### package require sak::animate package require sak::feedback package require sak::color getpackage textutil::repeat textutil/repeat.tcl getpackage doctools doctools/doctools.tcl namespace eval ::sak::validate::syntax { namespace import ::textutil::repeat::blank namespace import ::sak::color::* namespace import ::sak::feedback::! namespace import ::sak::feedback::>> namespace import ::sak::feedback::+= namespace import ::sak::feedback::= namespace import ::sak::feedback::=| namespace import ::sak::feedback::log namespace import ::sak::feedback::summary rename summary sum } # ### proc ::sak::validate::syntax {modules mode stem tclv} { syntax::run $modules $mode $stem $tclv syntax::summary return } proc ::sak::validate::syntax::run {modules mode stem tclv} { sak::feedback::init $mode $stem sak::feedback::first log "\[ Syntax \] ======================================================" sak::feedback::first unc "\[ Syntax \] ======================================================" sak::feedback::first fail "\[ Syntax \] ======================================================" sak::feedback::first warn "\[ Syntax \] ======================================================" sak::feedback::first miss "\[ Syntax \] ======================================================" sak::feedback::first none "\[ Syntax \] ======================================================" # Preprocessing of module names to allow better formatting of the # progress output, i.e. vertically aligned columns # Per module we can distinguish the following levels of # syntactic completeness and validity. # Rule completeness # - No package has pcx rules # - Some, but not all packages have pcx rules # - All packages have pcx rules # # Validity. Not of the pcx rules, but of the files in the # packages. # - Package has errors and warnings # - Package has errors, but no warnings. # - Package has no errors, but warnings. # - Package has neither errors nor warnings. # Progress report per module: Modules and packages it is working on. # Summary at module level: # - Number of packages, number of packages with pcx rules # - Number of errors, number of warnings. # Full log: # - Lists packages without pcx rules. # - Lists packages with errors/warnings. # - Lists the exact errors/warnings per package, and location. # Global preparation: Pull information about all packages and the # modules they belong to. Setup Count $modules MapPackages InitCounters foreach m $modules { # Skip tcllibc shared library, not a module. if {[string equal $m tcllibc]} continue InitModuleCounters ! log "@@ Module $m" Head $m # Per module: Find all syntax definition (pcx) files inside # and process them. Further find all the Tcl files and process # them as well. We get errors, warnings, and determine the # package(s) they may belong to. # Per package: Have they pcx files claiming them? After that, # are pcx files left over (i.e. without a package)? ProcessAllPCX $m ProcessPackages $m ProcessUnclaimed ProcessTclSources $m $tclv ModuleSummary } Shutdown return } proc ::sak::validate::syntax::summary {} { Summary return } # ### proc ::sak::validate::syntax::ProcessAllPCX {m} { !claims foreach f [glob -nocomplain [file join [At $m] *.pcx]] { ProcessOnePCX $f } return } proc ::sak::validate::syntax::ProcessOnePCX {f} { =file $f if {[catch { Scan [get_input $f] } msg]} { +e $msg } else { +claim $msg } return } proc ::sak::validate::syntax::ProcessPackages {m} { !used if {![HasPackages $m]} return foreach p [ThePackages $m] { +pkg $p if {[claimants $p]} { +pcx $p } else { nopcx $p } } return } proc ::sak::validate::syntax::ProcessUnclaimed {} { variable claims if {![array size claims]} return foreach p [lsort -dict [array names claims]] { foreach fx $claims($p) { +u $fx } } return } proc ::sak::validate::syntax::ProcessTclSources {m tclv} { variable tclchecker if {![llength $tclchecker]} return foreach t [modtclfiles $m] { # Ignore TeX files. if {[string equal [file extension $t] .tex]} continue =file $t set cmd [Command $t $tclv] if {[catch {Close [Process [open |$cmd r+]]} msg]} { if {[string match {*child process exited abnormally*} $msg]} continue +e $msg } } return } ### proc ::sak::validate::syntax::Setup {} { variable ip [interp create] # Make it mostly empty (We keep the 'set' command). foreach n [interp eval $ip [list ::namespace children ::]] { if {[string equal $n ::tcl]} continue interp eval $ip [list namespace delete $n] } foreach c [interp eval $ip [list ::info commands]] { if {[string equal $c set]} continue if {[string equal $c if]} continue if {[string equal $c rename]} continue if {[string equal $c namespace]} continue interp eval $ip [list ::rename $c {}] } interp eval $ip [list ::namespace delete ::tcl] interp eval $ip [list ::rename namespace {}] interp eval $ip [list ::rename rename {}] foreach m { pcx::register unknown } { interp alias $ip $m {} ::sak::validate::syntax::PCX/[string map {:: _} $m] $ip } return } proc ::sak::validate::syntax::Shutdown {} { variable ip interp delete $ip return } proc ::sak::validate::syntax::Scan {data} { variable ip variable pcxpackage while {1} { if {[catch { $ip eval $data } msg]} { if {[string match {can't read "*": no such variable} $msg]} { regexp {can't read "(.*)": no such variable} $msg -> var log "@@ + variable \"$var\"" $ip eval [list set $var {}] continue } return -code error $msg } break } return $pcxpackage } proc ::sak::validate::syntax::PCX/pcx_register {ip pkg} { variable pcxpackage $pkg return } proc ::sak::validate::syntax::PCX/unknown {ip args} { return 0 } ### proc ::sak::validate::syntax::Process {pipe} { variable current set dst log while {1} { if {[eof $pipe]} break if {[gets $pipe line] < 0} break set tline [string trim $line] if {[string equal $tline ""]} continue if {[string match scanning:* $tline]} { log $line continue } if {[string match checking:* $tline]} { log $line continue } if {[regexp {^([^:]*):(\d+) \(([^)]*)\) (.*)$} $tline -> path at code detail]} { = "$current $at $code" set dst code,$code if {[IsError $code]} { +e $line } else { +w $line } } log $line $dst } return $pipe } proc ::sak::validate::syntax::IsError {code} { variable codetype variable codec if {[info exists codec($code)]} { return $codec($code) } foreach {p t} $codetype { if {![string match $p $code]} continue set codec($code) $t return $t } # We assume that codetype contains a default * pattern as the last # entry, capturing all unknown codes. +e INTERNAL exit } proc ::sak::validate::syntax::Command {t tclv} { # Unix. Construction of the pipe to run the tclchecker against a # single tcl file. set cmd [Driver $tclv] lappend cmd $t #lappend cmd >@ stdout 2>@ stderr #puts <<$cmd>> return $cmd } proc ::sak::validate::syntax::Close {pipe} { close $pipe return } proc ::sak::validate::syntax::Driver {tclv} { variable tclchecker set cmd $tclchecker if {$tclv ne {}} { lappend cmd -use Tcl-$tclv } # Make all syntax definition files we may have available to the # checker for higher accuracy of its output. foreach m [modules] { lappend cmd -pcx [At $m] } # Memoize proc ::sak::validate::syntax::Driver {tclv} [list return $cmd] return $cmd } ### proc ::sak::validate::syntax::=file {f} { variable current [file tail $f] = "$current ..." return } ### proc ::sak::validate::syntax::!claims {} { variable claims array unset claims * return } proc ::sak::validate::syntax::+claim {pkg} { variable current variable claims lappend claims($pkg) $current return } proc ::sak::validate::syntax::claimants {pkg} { variable claims expr { [info exists claims($pkg)] && [llength $claims($pkg)] } } ### proc ::sak::validate::syntax::!used {} { variable used array unset used * return } proc ::sak::validate::syntax::+use {pkg} { variable used variable claims foreach fx $claims($pkg) { set used($fx) . } unset claims($pkg) return } ### proc ::sak::validate::syntax::MapPackages {} { variable pkg array unset pkg * ! += Package foreach {pname pdata} [ipackages] { = "$pname ..." foreach {pver pmodule} $pdata break lappend pkg($pmodule) $pname } ! =| {Packages mapped ...} return } proc ::sak::validate::syntax::HasPackages {m} { variable pkg expr { [info exists pkg($m)] && [llength $pkg($m)] } } proc ::sak::validate::syntax::ThePackages {m} { variable pkg return [lsort -dict $pkg($m)] } ### proc ::sak::validate::syntax::+pkg {pkg} { variable mtotal ; incr mtotal variable total ; incr total return } proc ::sak::validate::syntax::+pcx {pkg} { variable mhavepcx ; incr mhavepcx variable havepcx ; incr havepcx = "$pkg Ok" +use $pkg return } proc ::sak::validate::syntax::nopcx {pkg} { = "$pkg Bad" log "@@ WARN No syntax definition: $pkg" return } ### proc ::sak::validate::syntax::+w {msg} { variable mwarnings ; incr mwarnings variable warnings ; incr warnings variable current foreach {a b c} [split $msg \n] break log "@@ WARN $current: [Trim $a] [Trim $b] [Trim $c]" return } proc ::sak::validate::syntax::+e {msg} { variable merrors ; incr merrors variable errors ; incr errors variable current log "@@ ERROR $current $msg" return } proc ::sak::validate::syntax::+u {f} { variable used if {[info exists used($f)]} return variable munclaimed ; incr munclaimed variable unclaimed ; incr unclaimed set used($f) . log "@@ WARN Unclaimed syntax definition file: $f" return } ### proc ::sak::validate::syntax::Count {modules} { variable maxml 0 ! foreach m [linsert $modules 0 Module] { = "M $m" set l [string length $m] if {$l > $maxml} {set maxml $l} } =| "Validate syntax (code, and API definitions) ..." return } proc ::sak::validate::syntax::Head {m} { variable maxml += ${m}[blank [expr {$maxml - [string length $m]}]] return } ### proc ::sak::validate::syntax::InitModuleCounters {} { variable mtotal 0 variable mhavepcx 0 variable munclaimed 0 variable merrors 0 variable mwarnings 0 return } proc ::sak::validate::syntax::ModuleSummary {} { variable mtotal variable mhavepcx variable munclaimed variable merrors variable mwarnings variable tclchecker set complete [F $mhavepcx]/[F $mtotal] set not "! [F [expr {$mtotal - $mhavepcx}]]" set err "E [F $merrors]" set warn "W [F $mwarnings]" set unc "U [F $munclaimed]" if {$munclaimed} { set unc [=cya $unc] >> unc } if {!$mhavepcx && $mtotal} { set complete [=red $complete] set not [=red $not] >> none } elseif {$mhavepcx < $mtotal} { set complete [=yel $complete] set not [=yel $not] >> miss } if {[llength $tclchecker]} { if {$merrors} { set err " [=red $err]" set warn " [=yel $warn]" >> fail } elseif {$mwarnings} { set err " $err" set warn " [=yel $warn]" >> warn } else { set err " $err" set warn " $warn" } } else { set err "" set warn "" } =| "~~ $complete $not $unc$err$warn" return } ### proc ::sak::validate::syntax::InitCounters {} { variable total 0 variable havepcx 0 variable unclaimed 0 variable errors 0 variable warnings 0 return } proc ::sak::validate::syntax::Summary {} { variable total variable havepcx variable unclaimed variable errors variable warnings variable tclchecker set tot [F $total] set doc [F $havepcx] set udc [F [expr {$total - $havepcx}]] set unc [F $unclaimed] set per [format %6.2f [expr {$havepcx*100./$total}]] set uper [format %6.2f [expr {($total - $havepcx)*100./$total}]] set err [F $errors] set wrn [F $warnings] if {$errors} { set err [=red $err] } if {$warnings} { set wrn [=yel $wrn] } if {$unclaimed} { set unc [=cya $unc] } if {!$havepcx && $total} { set doc [=red $doc] set udc [=red $udc] } elseif {$havepcx < $total} { set doc [=yel $doc] set udc [=yel $udc] } if {[llength $tclchecker]} { set sfx " ($tclchecker)" } else { set sfx " ([=cya {No tclchecker available}])" } sum "" sum "Syntax statistics$sfx" sum "#Packages: $tot" sum "#Syntax def: $doc (${per}%)" sum "#No syntax: $udc (${uper}%)" sum "#Unclaimed: $unc" if {[llength $tclchecker]} { sum "#Errors: $err" sum "#Warnings: $wrn" } return } ### proc ::sak::validate::syntax::F {n} { format %6d $n } proc ::sak::validate::syntax::Trim {text} { regsub {^[^:]*:} $text {} text return [string trim $text] } ### proc ::sak::validate::syntax::At {m} { global distribution return [file join $distribution modules $m] } # ### namespace eval ::sak::validate::syntax { # Max length of module names and patchlevel information. variable maxml 0 # Counters across all modules variable total 0 ; # Number of packages overall. variable havepcx 0 ; # Number of packages with syntax definition (pcx) files. variable unclaimed 0 ; # Number of PCX files not claimed by a specific package. variable errors 0 ; # Number of errors found in all code. variable warnings 0 ; # Number of warnings found in all code. # Same counters, per module. variable mtotal 0 variable mhavepcx 0 variable munclaimed 0 variable merrors 0 variable mwarnings 0 # Name of currently processed syntax definition or code file variable current "" # Map from packages to files claiming to define the syntax of their API. variable claims array set claims {} # Set of files taken by packages, as array variable used array set used {} # Map from modules to packages contained in them variable pkg array set pkg {} # Transient storage used while collecting packages per syntax definition. variable pcxpackage {} variable ip {} # Location of the tclchecker used to perform syntactic validation. variable tclchecker [auto_execok tclchecker] # Patterns for separation of errors from warnings variable codetype { warn* 0 nonPort* 0 pkgUnchecked 0 pkgVConflict 0 * 1 } variable codec ; array set codec {} } ## # ### package provide sak::validate::syntax 1.0