cserver (servertype) (methods) (args)
e.g.: cserver bitmap_sv {new {max} {..} ...} -cc acc -with {#define ONE 1}creates a "C server" (generates code, compiles, links, generates Tcl wrapper) named servertype, where methods is a sequence of cproc-like triples of methodname argl cbody (cbody is C code to be spliced in for that method). Special (optional) methods are:- new - "constructor", executed at cserver startup
- finally - "destructor", executed when cserver is closed
(servertype) (instancename) (args) e.g.: bitmap_sv foo 1000instantiates an existing C server (opens it as pipe server), where servertype is one used with the cserver command. The structure of args must match the one specified in the cserver's new method, or be empty if there was no explicit constructor.
(instancename) (method) (args) e.g.: foo set 123 1sends the message method with args to the cserver instance, and returns its result. Method can be any of those defined in the cserver command, plus the predefined
- "empty method" (send an empty string, and you get a "self-portrait", a list specifying the server type and the list of known methods), and
- close (guess what that does ;-).
proc cserver {name methods args} {
if [llength [info command $name]] {error "$name exists"}
array set a [list -cc gcc -ccflags {-s -Wall -W -ansi -pedantic} \
-dir $::env(TEMP) -with {}]
array set a $args ;# maybe override default settings
set mcode ""
set mnames [list {} close] ;# the default methods
set constructor "\{" ;# see note in genConstr for reason
set destructor "\}"
foreach {mname margs mbody} $methods {
switch -- $mname {
new {set constructor "[CParseArgv $name $margs]
\{$mbody"}
finally {set destructor "$mbody\}" ;# ignore margs}
default {
lappend mnames $mname
append mcode "[addMethod $mname $margs _line_]
{[escapeSpecials $mbody]\t\t}"
}
}
}
set cbody [CTemplate]
set with [escapeSpecials [CFunctions]$a(-with)]
foreach i {name with constructor mnames mcode destructor} {
regsub -all @$i $cbody [set $i] cbody
}
#regsub -all {\n[ \t]+#} $cbody "\n#" cbody ;# make cpp happy?
set nname [file nativename [file join $a(-dir) $name]]
set fp [open $nname.c w]
puts $fp $cbody
close $fp
eval exec $a(-cc) $a(-ccflags) [list $nname.c -o $nname]
makeTclWrapper $name $nname
}
proc escapeSpecials s {
regsub -all {\\} $s {\\\\} s
regsub -all {\&} $s {\\&} s
set s
}# This is the C source framework - specifics will replace @... words proc CTemplate {} {
return {/* @name.c - generated by cserver */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define EQ(_s1,_s2) !strcmp(_s1,_s2)
#define ERROR(_s) {printf("%s","error: "_s"\n"); fflush(stdout); continue;}
#define ERRORF(_s,_a) {printf("error: "_s"\n",_a); fflush(stdout); continue;}
#define FATAL(_s) {puts("error! "_s); return -1;}
#define FOREACH(_i,_s) for(strcpy(_line_,_s),_i=w_strtok(_line_," \t");\
_i;_i=w_strtok(NULL," \t"))
#define MAXLINE 256
#define MAXWORD 128
@with
int main(int argc, char *argv[]) {
char _line_[MAXLINE]="", _cmd_[MAXWORD], _guard_;
@constructor
puts(w_wordn("",1)); fflush(stdout);
(void)_guard_; (void)argc; (void)argv; (void)w_strtok;
while(1) {
fgets(_line_, sizeof(_line_), stdin);
if(feof(stdin)) break;
_line_[strlen(_line_)-1] = '\0';
sscanf(_line_, "%s ", _cmd_);
if(!strlen(_line_) || EQ(_cmd_,"{}")) {
printf("%s","@name {@mnames}"); /* self-portrait */
} else if(EQ(_cmd_,"close")) {
break;\
@mcode
} else ERRORF("%s? Use one of: @mnames", _cmd_);
puts(""); fflush(stdout);
}
@destructor
puts(""); return 0;
}
}}# Here are helpful C functions - wrapping braced strings, getting nth word proc CFunctions {} {return {
static char *w_wrap(char *s) {
int br = 0; char *cp;
for (cp = s; *cp; cp++) switch (*cp) {
case '{': if(!(br++) && *(cp+1)!='}') *cp=' '; break;
case '}': if(!(--br) && *(cp-1)!='{') *cp=' '; break;
case ' ': case '\t': if(br) *cp='\01'; break;
} return s;
}
static char *w_unwrap(char *s) {
char *cp;
if(s) for(cp = s; *cp; cp++) if(*cp=='\01') *cp=' ';
return s;
}
static char *w_strtok(char *s, char *sepa) {
if(s) w_wrap(s);
return(w_unwrap(strtok(s, sepa)));
}
static char *w_wordn(char *cp, int n) {
char *res = NULL; int br = 0;
if(cp && n>=1)
for(res=cp; n>1 && *cp; cp++) {
if(*cp=='{') br++; if(*cp=='}') br--;
res = (!br && *cp==' ' && *(cp+1)!=' ')? n--,cp+1 : "";
}
return res;
}
}}
proc CParseArgv {name argl} {
set j 0
set maxargs [set minargs [expr [llength $argl]+1]]
foreach i $argl {
incr j
if {$i=="args" && $j==[llength $argl]} {
incr minargs -1
set maxargs 127
append res "\n\t\tchar $i\[MAXLINE\]=\"\";
int _i_;
for(_i_=$j; _i_<argc; _i_++) {
strcat($i,argv\[_i_\]);
if(_i_<(argc-1)) strcat($i,\" \");
}"
} else {
foreach {argname default} $i break
append res "
char *$argname = (argc>$j)?argv\[$j\] : \"$default\";"
incr minargs -1
}
}
append res "\n\t if(argc<$minargs || argc>$maxargs)
FATAL(\"usage: $name $argl\");"
}# This generates C code for a general method (except con/destructors) proc addMethod {method margs var} {
set _ \n\t\t\t ;# indentation, for better looks
set mcode "\n\n\t\t\} else if (EQ(_cmd_,\"$method\")) \{"
set scan "$_ char _scan_\[MAXLINE\];"
append scan "$_ int _n_ = sscanf(w_wrap(strcpy(_scan_,$var)),\"%s"
set argnames [list {}] ;# to get the right # commas at empty list
set narg 1 ;# method name will be first argument
set maxargs [set minargs [expr [llength $margs]+1]]
foreach i $margs {
incr narg
if {$i=="args" && $narg==$maxargs} {
append mcode "$_ char *$i = w_wordn($var,$narg);"
incr minargs -1 ;# args might be empty...
set maxargs 127 ;#... or very long
} else {
foreach {argname default} $i break
append mcode "$_ char $argname\[MAXWORD\] = \"$default\";"
if {[llength $i]>1} {incr minargs -1}
lappend argnames [lindex $i 0]
append scan " %s"
}
}
if {$minargs>1} {
append mcode "$scan %c\",$_\t _cmd_[join $argnames ,], \\&_guard_);"
append mcode "$_ if(_n_<$minargs || _n_>$maxargs)
ERRORF(\"wrong # args %d, should be '$method $margs'\",_n_);$_ "
foreach i [lrange $argnames 1 end] {append mcode "w_unwrap($i); "}
}
set mcode
}# This produces a server proc, which in turn produces an instance proc proc makeTclWrapper {name nname} {
regsub -all @nname {
if [llength [info command $instname]] {error "$instname exists"}
set fp [open [concat |[list {@nname}] $args] r+]
fconfigure $fp -buffering line -translation lf
gets $fp line
if [regexp ^error $line] {error $line}
regsub -all @fp {
puts @fp $args
gets @fp line
if [regexp ^error $line] {error $line}
if {[lindex $args 0]=="close"} {
close @fp
rename [lindex [info level 0] 0] {} ;# suicide
}
set line
} $fp ibody
proc $instname {args} $ibody
set line
} $nname body
proc $name {instname args} $body
set name
}if 0 {For testing, here's an almost non-trivial example: a bitmap server which keeps a tightly-packed bit vector from 0 to the specified maximum, with a set method (without 2nd argument, it retrieves a bit's value). The yodel method was added only to test the args feature and brace wrapping. } catch {rename bitmap_sv ""; foo close} ;# good for repeated sourcing
cserver bitmap_sv {
new {{max 1024}} {
#define LONGBITS (sizeof(long)*8/sizeof(char))
int imax = atoi(max);
long *map = (long*)calloc((imax+LONGBITS-1)/LONGBITS,sizeof(long));
if(imax<=0) FATAL("max must be > 0");
if(!map) FATAL("no memory for map");
}
yodel {first args} {
char *i;
printf("holladihoo '%s','%s'!", first, args);
FOREACH(i,args) printf(" '%s'(%d)", i, strlen(i));
}
llength list {
int n = 0;
if(list && !EQ(list,"{}")) {
char *i;
FOREACH(i,list) n++;
}
printf("%d", n);
}
lindex {list index} {
int n = atoi(index);
char *i;
FOREACH(i,list) if (!(n--)) break;
if(!i) i="";
printf("%s", i);
}
set {bitno {val -1}} {
int ibit = atoi(bitno);
#define BIT (1<<(ibit%LONGBITS))
#define WORD map[ibit/LONGBITS]
if(ibit>imax || ibit<0)
ERRORF("out of bitmap bounds, must be in 0..%d", imax);
if (EQ(val, "1")) WORD |= BIT;
else if(EQ(val, "0")) WORD &= ~BIT;
else if(EQ(val,"-1")) sprintf(val,"%d", (0 != (WORD & BIT)));
else ERROR("value must be 0 or 1, or not set");
printf(val);
}
finally {} {free(map); printf("Thank you.");}
}
bitmap_sv foo 999
foo set 123 1
foo yodel must be [foo set 123]C code generators - Arts and crafts of Tcl-Tk programming - Category Foreign Interfaces

