# -*- tcl -*- # Code common to the various control files. # # Copyright (c) 2009-2014 by Andreas Kupries # All rights reserved. # # RCS: @(#) $Id: common,v 1.1 2010/03/26 05:07:24 andreas_kupries Exp $ # ------------------------------------------------------------------------- # Similar to TestFiles in devtools/testutilities.tcl, but not # identical. Here we do not expect source'able test suites, but data # files, organized in sections under a main directory. proc TestFilesProcess {maindir section inset outset -> nv lv iv dv ev script {optionalok 0}} { upvar 1 $nv n $lv label $dv data $ev expected $iv inputfile set pattern $maindir/$section/$inset/* set files [TestFilesGlob $pattern] if {![llength $files]} { if {$optionalok} return return -code error "No files matching \"$pattern\"" } foreach src $files { if {[string match *README* $src]} continue if {[file isdirectory $src]} continue set srcname [file tail $src] set exp [localPath $maindir]/$section/$outset/$srcname set data [fileutil::cat -translation binary -encoding utf-8 $src] set expected [string trim [fileutil::cat -translation binary -encoding utf-8 $exp]] set expected [string map [list \ @sak @sak \ @line @line \ {@ %d} {@ %d} \ {@ %p} {@ %p} \ @ $::tcltest::testsDirectory] $expected] regexp -- {^([0-9]+)} $srcname -> n regsub -all -- {^[0-9]+} $srcname {} label scan $n %d n set label [string trim [string map {_ { }} $label]] set inputfile $src uplevel 1 $script } return } proc TestFilesProcessIn {maindir section inset -> nv lv iv dv script} { upvar 1 $nv n $lv label $dv data $iv inputfile set pattern $maindir/$section/$inset/* set files [TestFilesGlob $pattern] if {![llength $files]} { return -code error "No files matching \"$pattern\"" } foreach src $files { if {[string match *README* $src]} continue if {[file isdirectory $src]} continue set srcname [file tail $src] set data [fileutil::cat -translation binary -encoding utf-8 $src] regexp -- {^([0-9]+)} $srcname -> n regsub -all -- {^[0-9]+} $srcname {} label scan $n %d n set label [string trim [string map {_ { }} $label]] set inputfile $src uplevel 1 $script } return } # ------------------------------------------------------------------------- proc setup_plugins {} { global env array_unset env LANG* array_unset env LC_* set env(LANG) C ; # Usually default if nothing is set, OS X requires this. set paths [join [list \ [tcllibPath grammar_peg] \ [tcllibPath struct] \ [tcllibPath json] \ [tcllibPath textutil] \ ] \ [expr {$::tcl_platform(platform) eq "windows" ? ";" : ":"}]] # Initialize the paths an import plugin manager should use when # searching for an import plugin used by the code under test, and # also provide the paths enabling the import plugins to find their # supporting packages as well. set env(GRAMMAR_PEG_IMPORT_PLUGINS) $paths # Initialize the paths an export plugin manager should use when # searching for an export plugin used by the code under test, and # also provide the paths enabling the export plugins to find their # supporting packages as well. set env(GRAMMAR_PEG_EXPORT_PLUGINS) $paths return } # ------------------------------------------------------------------------- proc stripcomments {text} { set pattern {[[:space:]]*\[comment[[:space:]][[:space:]]*\{[^\}]*\}[[:space:]]*\][[:space:]]*} regsub -all -- $pattern $text {} text return $text } proc striphtmlcomments {text {n {}}} { set pattern {} if {$n eq {}} { regsub -all -- $pattern $text {} text } else { while {$n} { regsub -- $pattern $text {} text incr n -1 } } return $text } proc stripmanmacros {text} { return [string map [list \n[pt::nroff::man_macros::contents] {}] $text] } proc stripnroffcomments {text {n {}}} { # return $text set pattern "'\\\\\"\[^\n\]*\n" if {$n eq {}} { regsub -all -- $pattern $text {} text } else { while {$n} { regsub -- $pattern $text {} text incr n -1 } } return $text } # ------------------------------------------------------------------------- # Validate a serialization against the tree it # was generated from. proc validate_serial {t serial {rootname {}}} { if {$rootname == {}} { set rootname [$t rootname] } # List length is multiple of 3 if {[llength $serial] % 3} { return serial/wrong#elements } # Scan through list and built a number helper # structures (arrays). array set a {} array set p {} array set ch {} foreach {node parent attr} $serial { # Node has to exist in tree if {![$t exists $node]} { return node/$node/unknown } if {![info exists ch($node)]} {set ch($node) {}} # Parent reference has to be empty or # integer, == 0 %3, >=0, < length serial if {$parent != {}} { if {![string is integer -strict $parent]} { return node/$node/parent/no-integer/$parent } if {$parent % 3} { return node/$node/parent/not-triple/$parent } if {$parent < 0} { return node/$node/parent/out-of-bounds/$parent } if {$parent >= [llength $serial]} { return node/$node/parent/out-of-bounds/$parent } # Resolve parent index into node name, has to match set parentnode [lindex $serial $parent] if {![$t exists $parentnode]} { return node/$node/parent/unknown/$parent/$parentnode } if {![string equal [$t parent $node] $parentnode]} { return node/$node/parent/mismatch/$parent/$parentnode/[$t parent $node] } lappend ch($parentnode) $node } else { set p($node) {} } # Attr list has to be of even length. if {[llength $attr] % 2} { return attr/$node/wrong#elements } # Attr have to exist and match in all respects if {![string equal \ [dictsort $attr] \ [dictsort [$t getall $node]]]} { return attr/$node/mismatch } } # Second pass, check that the children information is encoded # correctly. Reconstructed data has to match originals. foreach {node parent attr} $serial { if {![string equal $ch($node) [$t children $node]]} { return node/$node/children/mismatch } } # Reverse check # - List of nodes from the 'rootname' and check # that it and all its children are present # in the structure. set ::FOO {} $t walk $rootname n {walker $n} foreach n $::FOO { if {![info exists ch($n)]} { return node/$n/mismatch/reachable/missing } } if {[llength $::FOO] != [llength $serial]/3} { return structure/mismatch/#nodes/multiples } if {[llength $::FOO] != [array size ch]} { return structure/mismatch/#nodes/multiples/ii } return ok } # Callbacks for tree walking. # Remember the node in a global variable. proc walker {node} { lappend ::FOO $node } proc match_tree {ta tb} { match_node $ta [$ta rootname] $tb [$tb rootname] return } proc match_node {ta a tb b} { if {[dictsort [$ta getall $a]] ne [dictsort [$tb getall $b]]} { return -code error "$ta/$a at $tb/$b, attribute mismatch (([dictsort [$ta getall $a]]) ne ([dictsort [$tb getall $b]]))" } if {[llength [$ta children $a]] != [llength [$tb children $b]]} { return -code error "$ta/$a at $tb/$b, children mismatch" } foreach ca [$ta children $a] cb [$tb children $b] { match_node $ta $ca $tb $cb } return } # ------------------------------------------------------------------------- ## Dynamically create a parser for a PE grammar stored in a string. ## Different types: ## - critcl -- Run through critcl tool for compilation at test time. ## - oo ## - container -- interpreter loaded from a container ## - snit proc make-parser {format glabel grammar} { global pcounter if {![info exist pcounter]} { set pcounter 0 } set debug 0 set keep 0 # should be preloaded by test suite. if {[catch { package present pt::pgen }]} { error "pt::pgen package required and not loaded. Please fix your testsuite." } # Options per format. # container : -name # critcl : -class -name # oo : -class -name # snit : -class -name set gc GC[incr pcounter] lappend cmd pt::pgen peg $grammar $format -name G if {$format ne "container"} { lappend cmd -class $gc } try { set code [eval $cmd] } trap {PT RDE SYNTAX} {e o} { error [pt::util error2readable $e $grammar] } # debugging generator output if {$debug} { set k [expr {$keep ? [open $gc$format w] : "stdout"}] puts $k "#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% $format ($glabel)" puts $k $code puts $k "#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% $format ($glabel)" if {$keep} { close $k } } # Now do format-specific post-processing of the generated code to # get a proper parser object. switch -exact -- $format { container { # should be preloaded by test suite. if {[catch { package present pt::peg::interp }]} { error "pt::peg::interp package required and not loaded. Please fix your testsuite." } # Instantiate container class (transient). eval $code # Instantiate container (transient). set c [G %AUTO%] # Instantiate PEG interpreter, and configure with grammar in container. set p [pt::peg::interp %AUTO%] $p use $c # Clean up the transient pieces (container class and instance). $c destroy G destroy } snit { # Instantiate the parser class. # ATTENTION: We chop the last 2 lines of the code first, # unwanted "package provide" and "return" commands. eval [join [lrange [split $code \n] 0 end-2] \n] # Instantiate a parser based on the class. set p [$gc %AUTO%] # Note: Cannot destroy class now, would destroy instance as well. } oo { # Instantiate the parser class. # ATTENTION: We chop the last 2 lines of the code first, # unwanted "package provide" and "return" commands. eval [join [lrange [split $code \n] 0 end-2] \n] # Instantiate a parser based on the class. set p [$gc new] # Note: Cannot destroy class now, would destroy instance as well. } critcl { # Instantiate the parser class. # ATTENTION: We chop the last line of the code first, # unwanted "return" command. # # ATTENTION: We muck with [info script] to distinguish the # multiple parsers going through this file and procedure # from each other. Without doing this they would all map # to the same file and critcl bailing on us for code # redefinition after a compile & link for that file. set here [info script] info script $gc eval [join [lrange [split $code \n] 0 end-1] \n] # Above invoked critcl's collection of the C fragments. # We have made sure (in "pt_pgen.test" and # "tests/pt_pgen.tests") that the critcl package is # available (we use it in run&compile mode). # Hidden in the execution of the command instantiating the # parser is the compilation, link and load of the C # pieces, via $auto_index() and [unknown]. set p [${gc}::${gc}_critcl] info script $here } } # Provide parser instance. if {$debug} { puts "P = ($p)" puts "%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% $format ($glabel)" puts "** [join [info loaded] "\n** "]" puts "%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% $format ($glabel)" puts %% puts %%% } return $p } # ------------------------------------------------------------------------- return