['''xk2600'''] hope others find this useful... I needed the ability to process c-stype preprocessing directives. The added advantage (or disadvantage depending on use) is since TCL utilizes # for comments, if you write directives using this style, they are simply ignored by TCL when this facility is disabled. Usage: '''preprocessor''' ''eval body'' ====== # preprocessor.tcl -- # # This file provides preprocessing directives and a limited macro facility. # # Copyright (c) 2016-2017, Christopher M. Stephan # Free to use, no warranty implied, use at your own risk. namespace eval ::preprocessor { namespace export eval namespace ensemble create -command ::preprocessor -subcommands eval variable DEFINES array set DEFINES {} proc eval {body} { variable DEFINES set result {} set defname {} set defval {} set breadcrumb {} set linenum 0 set includecontent true set exprFilter {[^ @_A-Za-z0-9=<>()&|/%*^+-]*} set defFilter {@[_A-Za-z0-9]+} foreach line [split $body "\n"] { incr linenum puts "#### line $linenum : $line (bc:$breadcrumb)" switch -exact -- [lindex $line 0] { \#DEFINE { # process line set line [lassign $line instruction defname defval] # validate arguments if {[string length $line]>0} { error [format {preprocessor-define: too many arguments in %s on line %s: "%s"} $instruction $linenum $line] } if {[string equal $defval {}]} { error [format {preprocessor-define: define value not specified for "%s" on line %s:%s "%s"} $instruction $linenum "\n " $line] } set DEFINES(@$defname) $defval unset defname unset defval } \#IFNDEF - \#IFDEF { #### PROCESS FOR @IFDEF AND @IFNDEF # single function checks for undefined and defined variables # by using setting invert variable to ! if this is IFNDEF # make sure we're not already processing an IF or IFDEF if {![string equal $breadcrumb {}]} { set errmsg {preprocessor-ifdef: recursive preprocessor scripts unsupported, currently processing:} foreach stack_frame $breadcrumb { append errmsg "\n --> $stack_frame" } error $errmsg } # process line... set line [lassign $line instruction defname] if {[string length $line]>0} { error [format {preprocessor-ifdef: too many arguments in %s on line %s: "%s"} $instruction $linenum $line] } # set breadcrumb lappend breadcrumb "${instruction}(${defname}) linenum:$linenum" # deal with inversion... (NDEF) set INVERT [expr {[string equal $instruction IFNDEF] ? {!} : {}}] if {[expr ${INVERT} [info exist DEFINES($defname)]]} { # passes condition... all lines unil else or elif are kept. set includecontent true } else { # failes condition... all lines until else or elif are dropped. set includecontent false } unset instruction unset defname } \#IF { #### PROCESSES @IF # make sure we're not already processing an IF or IFDEF if {![string equal $breadcrumb {}]} { set errmsg {preprocessor-if: recursive preprocessor scripts unsupported, currently processing:} foreach stack_frame $breadcrumb { append errmsg "\n --> $stack_frame" } error $errmsg } # process line set line [lassign $line instruction expression] if {[string length $line]>0} { error [format {preprocessor-if: too many arguments in @%s on line %s: "%s"} $instruction $linenum $line] } # check expression character use for invalid characters if {[regsub -all -- $exprFilter $expression {} filteredExpression] > 0} { set errmsg {preproceossor: expression in \"%s\" on line $linenum contains illegal symbols.} lappend errmsg "\n submitted" lappend errmsg "\n [string map [list \n {} \r {}] [string trim $expression]]\n" lappend errmsg "\n passed through filter:" lappend errmsg "\n $filteredExpression\n" } set ppexpr {} # substitute @defines foreach {full_match expr var} [regexp -inline -all -- {([^@]*)(@[_A-Za-z0-9]+)} $expression] { append ppexpr ${expr}$DEFINES($var) } # attempt evaluation if {[catch {expr $ppexpr} res]} { error [format {preprocessor: expression evaluation failure: "%s" on line %s} $expression $linenum] } # set breadcrumb lappend breadcrumb "${instruction}(${expression}) linenum:$linenum" # handle result if {$res} { # passes condition... all lines unil else or elif are kept. set includecontent true } else { # failes condition... all lines until else or elif are dropped. set includecontent false } unset ppexpr unset instruction unset expression unset full_match unset expr unset var unset res } \#ELSE { #### PROCESSES @ELSE # make sure we're already processing an IF/IFDEF/IFNDEF if {[string equal $breadcrumb {}]} { error [format {preprocessor-else: ELSE before IF, IFDEF, or IFNDEF on line %s} $linenum] } # process line set line [lassign $line instruction expression] # invert current action as we're now operating on the ELSE set includecontent [expr {$includecontent ? false : true }] } \#ENDIF { #### PROCESSES @ENDIF # make sure we're already processing an IF/IFDEF/IFNDEF if {[string equal $breadcrumb {}]} { error [format {preprocessor-endif: ENDIF before IF, IFDEF, or IFNDEF on line %s} $linenum] } set breadcrumb {} set includecontent true } default { #### CONTROLLED CONTENT if {$includecontent} { append result $line\n } } } } return [uplevel $result] } } ====== <>Concept|Glossary|Dev. Tools