# ACTIVESTATE TEAPOT-PKG BEGIN TM -*- tcl -*- # -- Tcl Module # @@ Meta Begin # Package stubs::gen 1 # Meta as::build::date 2015-03-24 # Meta platform tcl # Meta require {Tcl 8.4} # Meta require stubs::container # @@ Meta End # ACTIVESTATE TEAPOT-PKG BEGIN REQUIREMENTS package require Tcl 8.4 package require stubs::container # ACTIVESTATE TEAPOT-PKG END REQUIREMENTS # ACTIVESTATE TEAPOT-PKG BEGIN DECLARE package provide stubs::gen 1 # ACTIVESTATE TEAPOT-PKG END DECLARE # ACTIVESTATE TEAPOT-PKG END TM # -*- tcl -*- # STUBS handling -- Code generation framework. # # (c) 2011 Andreas Kupries http://wiki.tcl.tk/andreas%20kupries # A stubs table is represented by a dictionary value. # A gen is a variable holding a stubs table value. # # ## ### ##### ######## ############# ## Requisites package require Tcl 8.4 package require stubs::container namespace eval ::stubs::gen::c { namespace import ::stubs::container::* } # # ## ### ##### ######## ############# ## Implementation. proc ::stubs::gen::warn {cmdprefix} { variable warnCmd $cmdprefix return } proc ::stubs::gen::uncap {text} { return [string tolower [string index $text 0]][string range $text 1 end] } proc ::stubs::gen::cap {text} { return [string toupper [string index $text 0]][string range $text 1 end] } proc ::stubs::gen::forall {table name emitCmd onAll {skipString {}}} { if {$skipString eq {}} { #checker exclude warnArgWrite set skipString "/* Slot @@ is reserved */\n" } set platforms [c::platforms $table $name] if {[lsearch -exact $platforms "generic"] >= 0} { # Emit integrated stubs block set lastNum [MAX [c::lastof $table $name]] for {set i 0} {$i <= $lastNum} {incr i} { set slots [c::slotplatforms $table $name $i] set emit 0 if {[lsearch -exact $slots "generic"] >= 0} { if {[llength $slots] > 1} { WARN {conflicting generic and platform entries: $name $i} } append text [CALL generic $i] set emit 1 } elseif {[llength $slots] > 0} { array set slot {unix 0 x11 0 win 0 macosx 0 aqua 0} foreach s $slots { set slot($s) 1 } # "aqua", "macosx" and "x11" are special cases: # "macosx" implies "unix", "aqua" implies "macosx" and "x11" # implies "unix", so we need to be careful not to emit # duplicate stubs entries: if {($slot(unix) && $slot(macosx)) || (($slot(unix) || $slot(macosx)) && ($slot(x11) || $slot(aqua)))} { WARN {conflicting platform entries: $name $i} } ## unix ## set temp {} set plat unix if {!$slot(aqua) && !$slot(x11)} { if {$slot($plat)} { append temp [CALL $plat $i] } elseif {$onAll} { append temp [SKIP] } } if {$temp ne ""} { append text [AddPlatformGuard $plat $temp] set emit 1 } ## x11 ## set temp {} set plat x11 if {!$slot(unix) && !$slot(macosx)} { if {$slot($plat)} { append temp [CALL $plat $i] } elseif {$onAll} { append temp [SKIP] } } if {$temp ne ""} { append text [AddPlatformGuard $plat $temp] set emit 1 } ## win ## set temp {} set plat win if {$slot($plat)} { append temp [CALL $plat $i] } elseif {$onAll} { append temp [SKIP] } if {$temp ne ""} { append text [AddPlatformGuard $plat $temp] set emit 1 } ## macosx ## set temp {} set plat macosx if {!$slot(aqua) && !$slot(x11)} { if {$slot($plat)} { append temp [CALL $plat $i] } elseif {$slot(unix)} { append temp [CALL unix $i] } elseif {$onAll} { append temp [SKIP] } } if {$temp ne ""} { append text [AddPlatformGuard $plat $temp] set emit 1 } ## aqua ## set temp {} set plat aqua if {!$slot(unix) && !$slot(macosx)} { if {[string range $skipString 0 1] ne "/*"} { # The code previously had a bug here causing # it to erroneously generate both a unix entry # and an aqua entry for a given stubs table # slot. To preserve backwards compatibility, # generate a dummy stubs entry before every # aqua entry (note that this breaks the # correspondence between emitted entry number # and actual position of the entry in the # stubs table, e.g. TkIntStubs entry 113 for # aqua is in fact at position 114 in the # table, entry 114 at position 116 etc). append temp [SKIP] CHOP temp append temp " /*\ Dummy entry for stubs table backwards\ compatibility */\n" } if {$slot($plat)} { append temp [CALL $plat $i] } elseif {$onAll} { append temp [SKIP] } } if {$temp ne ""} { append text [AddPlatformGuard $plat $temp] set emit 1 } } if {!$emit} { append text [SKIP] } } } else { # Emit separate stubs blocks per platform array set block {unix 0 x11 0 win 0 macosx 0 aqua 0} foreach s $platforms { set block($s) 1 } ## unix ## if {$block(unix) && !$block(x11)} { set temp {} set plat unix # (1) put into helper method set lastNum [c::lastof $table $name $plat] for {set i 0} {$i <= $lastNum} {incr i} { if {[c::slot? $table $name $plat $i]} { append temp [CALL $plat $i] } else { append temp [SKIP] } } append text [AddPlatformGuard $plat $temp] } ## win ## if {$block(win)} { set temp {} set plat win # (1) put into helper method set lastNum [c::lastof $table $name $plat] for {set i 0} {$i <= $lastNum} {incr i} { if {[c::slot? $table $name $plat $i]} { append temp [CALL $plat $i] } else { append temp [SKIP] } } append text [AddPlatformGuard $plat $temp] } ## macosx ## if {$block(macosx) && !$block(aqua) && !$block(x11)} { set temp {} set lastNum [MAX [list \ [c::lastof $table $name unix] \ [c::lastof $table $name macosx]]] for {set i 0} {$i <= $lastNum} {incr i} { set emit 0 foreach plat {unix macosx} { if {[c::slot? $table $name $plat $i]} { append temp [CALL $plat $i] set emit 1 break } } if {!$emit} { append temp [SKIP] } } append text [AddPlatformGuard macosx $temp] } ## aqua ## if {$block(aqua)} { set temp {} set lastNum [MAX [list \ [c::lastof $table $name unix] \ [c::lastof $table $name macosx] \ [c::lastof $table $name aqua]]] for {set i 0} {$i <= $lastNum} {incr i} { set emit 0 foreach plat {unix macosx aqua} { if {[c::slot? $table $name $plat $i]} { append temp [CALL $plat $i] set emit 1 break } } if {!$emit} { append temp [SKIP] } } append text [AddPlatformGuard aqua $temp] } ## x11 ## if {$block(x11)} { set temp {} set lastNum [MAX [list \ [c::lastof $table $name unix] \ [c::lastof $table $name macosx] \ [c::lastof $table $name x11]]] for {set i 0} {$i <= $lastNum} {incr i} { set emit 0 foreach plat {unix macosx x11} { if {[c::slot? $table $name $plat $i]} { if {$plat ne "macosx"} { append temp [CALL $plat $i] } else { append temp [AddPlatformGuard $plat \ [CALL $plat $i] \ [SKIP]] } set emit 1 break } } if {!$emit} { append temp [SKIP] } } append text [AddPlatformGuard x11 $temp] } } return $text } proc ::stubs::gen::rewrite {path newcode} { if {![file exists $path]} { return -code error "Cannot find file: $path" } set in [open ${path} r] set out [open ${path}.new w] # Hardwired use of unix line-endings in the output. fconfigure $out -translation lf # Copy the file header before the code section. while {![eof $in]} { set line [gets $in] if {[string match "*!BEGIN!*" $line]} break puts $out $line } puts $out "/* !BEGIN!: Do not edit below this line. */" # Insert the new code. puts $out $newcode # Skip over the input until the end of the code section. while {![eof $in]} { set line [gets $in] if {[string match "*!END!*" $line]} break } # Copy the trailer after the code section. This can be done fast, # as searching is not required anymore. puts $out "/* !END!: Do not edit above this line. */" puts -nonewline $out [read $in] # Close and commit to the changes (atomic rename). close $in close $out file rename -force -- ${path}.new ${path} return } # # ## ### ##### ## Internal helpers. proc ::stubs::gen::CALL {platform index} { upvar 1 table table name name emitCmd emitCmd set decl [c::slot $table $name $platform $index] return [uplevel \#0 [linsert $emitCmd end $name $decl $index]] } proc ::stubs::gen::WARN {text} { variable warnCmd if {$warnCmd eq {}} return return [uplevel \#0 [linsert $warnCmd end [uplevel 1 [list ::subst $text]]]] } proc ::stubs::gen::SKIP {} { upvar 1 skipString skipString i i #puts stderr SKIP/$i/[string map [list {$i} $i] $skipString] return [string map [list @@ $i] $skipString] } proc ::stubs::gen::CHOP {textvar} { upvar 1 $textvar text set text [string range $text 0 end-1] return } proc ::stubs::gen::AddPlatformGuard {platform iftext {elsetext {}}} { variable guard_begin variable guard_else variable guard_end set prefix [expr {![info exists guard_begin($platform)] ? "" : $guard_begin($platform)}] set middle [expr {![info exists guard_else($platform)] ? "" : $guard_else($platform)}] set suffix [expr {![info exists guard_end($platform)] ? "" : $guard_end($platform)}] return $prefix$iftext[expr {($elsetext eq "") ? "" : "$middle$elsetext"}]$suffix } if {[package vsatisfies [package present Tcl] 8.5]} { #checker exclude warnRedefine proc ::stubs::gen::MAX {list} { return [tcl::mathfunc::max {*}$list] } } else { #checker exclude warnRedefine proc ::stubs::gen::MAX {list} { set max {} foreach a $list { if {($max ne {}) && ($max >= $a)} continue set max $a } return $a } } # # ## ### ##### namespace eval ::stubs::gen { #checker -scope block exclude warnShadowVar variable guard_begin variable guard_else variable guard_end array set guard_begin { win "#ifdef __WIN32__ /* WIN */\n" unix "#if !defined(__WIN32__) && !defined(MAC_OSX_TCL) /* UNIX */\n" macosx "#ifdef MAC_OSX_TCL /* MACOSX */\n" aqua "#ifdef MAC_OSX_TK /* AQUA */\n" x11 "#if !(defined(__WIN32__) || defined(MAC_OSX_TK)) /* X11 */\n" } array set guard_else { win "#else /* WIN */\n" unix "#else /* UNIX */\n" macosx "#else /* MACOSX */\n" aqua "#else /* AQUA */\n" x11 "#else /* X11 */\n" } array set guard_end { win "#endif /* WIN */\n" unix "#endif /* UNIX */\n" macosx "#endif /* MACOSX */\n" aqua "#endif /* AQUA */\n" x11 "#endif /* X11 */\n" } # Default command to report conflict and other warnings. variable warnCmd {puts stderr} namespace export forall rewrite warn cap uncap } # # ## ### ##### package provide stubs::gen 1 return