#! /usr/bin/perl -w
#
# Build the server/trace.c and server/request.h files
# from the contents of include/server.h.
#
# Copyright (C) 1998 Alexandre Julliard
#

%formats =
(
    "int"           => "%d",
    "char"          => "%c",
    "unsigned char" => "%02x",
    "unsigned int"  => "%08x",
    "void*"         => "%p",
    "time_t"        => "%ld",
    "path_t"        => "&dump_path_t",
);

my @requests = ();
my %replies = ();

open(SERVER,"include/server.h") or die "Can't open include/server.h";

### Parse server.h to find request/reply structure definitions

my @trace_lines = ();
my $protocol = 0;  # server protocol version

while (<SERVER>)
{
    if (/^struct +(\w+)_request/) { &DO_REQUEST($1); }
    if (/^\#define SERVER_PROTOCOL_VERSION (\d+)/) { $protocol = $1 + 1; }
}

### Output the dumping function tables

push @trace_lines, "static const dump_func req_dumpers[REQ_NB_REQUESTS] = {\n";
foreach $req (@requests)
{
    push @trace_lines, "    (dump_func)dump_${req}_request,\n";
}
push @trace_lines, "};\n\n";

push @trace_lines, "static const dump_func reply_dumpers[REQ_NB_REQUESTS] = {\n";
foreach $req (@requests)
{
    push @trace_lines, "    (dump_func)", $replies{$req} ? "dump_${req}_reply,\n" : "0,\n";
}
push @trace_lines, "};\n\n";

push @trace_lines, "static const char * const req_names[REQ_NB_REQUESTS] = {\n";
foreach $req (@requests)
{
    push @trace_lines, "    \"$req\",\n";
}
push @trace_lines, "};\n";

REPLACE_IN_FILE( "server/trace.c", @trace_lines );

### Replace the request list in server.h by the new values

my @server_lines = ();

push @server_lines, "enum request\n{\n";
foreach $req (@requests) { push @server_lines, "    REQ_\U$req,\n"; }
push @server_lines, "    REQ_NB_REQUESTS\n};\n\n";
push @server_lines, "union generic_request\n{\n";
push @server_lines, "    struct request_max_size max_size;\n";
push @server_lines, "    struct request_header header;\n";
foreach $req (@requests) { push @server_lines, "    struct ${req}_request $req;\n"; }
push @server_lines, "};\n\n";
push @server_lines, "#define SERVER_PROTOCOL_VERSION $protocol\n";

REPLACE_IN_FILE( "include/server.h", @server_lines );

### Output the request handlers list

my @request_lines = ();

foreach $req (@requests) { push @request_lines, "DECL_HANDLER($req);\n"; }
push @request_lines, "\n#ifdef WANT_REQUEST_HANDLERS\n\n";
push @request_lines, "typedef void (*req_handler)( void *req );\n";
push @request_lines, "static const req_handler req_handlers[REQ_NB_REQUESTS] =\n{\n";
foreach $req (@requests)
{
    push @request_lines, "    (req_handler)req_$req,\n";
}
push @request_lines, "};\n#endif  /* WANT_REQUEST_HANDLERS */\n";

REPLACE_IN_FILE( "server/request.h", @request_lines );

### Handle a request structure definition

sub DO_REQUEST
{
    my $name = shift;
    my @in_struct = ();
    my @out_struct = ();
    my $got_header = 0;
    while (<SERVER>)
    {
	my ($dir, $type, $var);
	last if /^};$/;
        next if /^{$/;
	s!/\*.*\*/!!g;
	next if /^\s*$/;
        if (/REQUEST_HEADER/)
        {
            die "Duplicated header" if $got_header;
            die "Header must be first" if ($#in_struct != -1 || $#out_struct != -1);
            $got_header++;
            next;
        }
        if (/^\s*(IN|OUT)\s*VARARG\((\w+),(\w+)\)/)
        {
            $dir = $1;
            $var = $2;
            $type = "&dump_varargs_" . $3;
        }
	elsif (/^\s*(IN|OUT)\s*(\w+\**(\s+\w+\**)*)\s+(\w+)(\[[1]\])?;/)
        {
            $dir = $1;
            $type = $2 . ($5 || "");
            $var = $4;
            die "Unrecognized type $type" unless (defined($formats{$type}) || $5);
        }
        else
        {
            die "Unrecognized syntax $_";
        }
	if ($dir =~ /IN/) { push @in_struct, $type, $var; }
	if ($dir =~ /OUT/) { push @out_struct, $type, $var; }
    }
    die "Missing header" unless $got_header;
    push @requests, $name;
    &DO_DUMP_FUNC( $name, "request", @in_struct);
    if ($#out_struct >= 0)
    {
	$replies{$name} = 1;
	&DO_DUMP_FUNC( $name, "reply", @out_struct);
    }
}

### Generate a dumping function

sub DO_DUMP_FUNC
{
    my $name = shift;
    my $req = shift;
    push @trace_lines, "static void dump_${name}_$req( const struct ${name}_request *req )\n{\n";
    while ($#_ >= 0)
    {
	my $type = shift;
	my $var = shift;
	if (defined($formats{$type}))
	{
            if ($formats{$type} =~ /^&(.*)/)
            {
                my $func = $1;
                push @trace_lines, "    fprintf( stderr, \" $var=\" );\n";
                if ($type =~ /[1]/) { push @trace_lines, "    $func( req, req->$var );\n"; }
                else { push @trace_lines, "    $func( req, &req->$var );\n"; }
                push @trace_lines, "    fprintf( stderr, \",\" );\n" if ($#_ > 0);
            }
            else
            {
                push @trace_lines, "    fprintf( stderr, \" $var=$formats{$type}";
                push @trace_lines, "," if ($#_ > 0);
                push @trace_lines, "\", ";
                push @trace_lines, "req->$var );\n";
            }
	}
	else  # must be some varargs format
	{
            if ($type =~ /^&(.*)/)
            {
                my $func = $1;
                push @trace_lines, "    fprintf( stderr, \" $var=\" );\n";
                push @trace_lines, "    cur_pos += $func( req );\n";
                push @trace_lines, "    fputc( ',', stderr );\n" if ($#_ > 0);
            }
            else
            {
                push @trace_lines, "    fprintf( stderr, \" $var=\" );\n";
                push @trace_lines, "    dump_varargs_${name}_${req}( req );\n";
            }
        }
    }
    push @trace_lines, "}\n\n";
}

### Replace the contents of a file between ### make_requests ### marks

sub REPLACE_IN_FILE
{
    my $name = shift;
    my @data = @_;
    my @lines = ();
    open(FILE,$name) or die "Can't open $name";
    while (<FILE>)
    {
	push @lines, $_;
	last if /\#\#\# make_requests begin \#\#\#/;
    }
    push @lines, "\n", @data;
    while (<FILE>)
    {
	if (/\#\#\# make_requests end \#\#\#/) { push @lines, "\n", $_; last; }
    }
    push @lines, <FILE>;
    open(FILE,">$name") or die "Can't modify $name";
    print FILE @lines;
    close(FILE);
}