#!/usr/bin/perl -w
# a simple system for generating C parse info
# this can be used to write generic C structer load/save routines
# Copyright 2002 Andrew Tridgell <genstruct@tridgell.net>
# released under the GNU General Public License v2 or later

#use strict;

my(%enum_done) = ();
my(%struct_done) = ();

###################################################
# general handler
sub handle_general($$$$$$$$)
{
	my($name) = shift;
	my($ptr_count) = shift;
	my($size) = shift;
	my($element) = shift;
	my($flags) = shift;
	my($dump_fn) = shift;
	my($parse_fn) = shift;
	my($tflags) = shift;
	my($array_len) = 0;
	my($dynamic_len) = "NULL";

	# handle arrays, currently treat multidimensional arrays as 1 dimensional
	while ($element =~ /(.*)\[(.*?)\]$/) {
		$element = $1;
		if ($array_len == 0) {
			$array_len = $2;
		} else {
			$array_len = "$2 * $array_len";
		}
	}

	if ($flags =~ /_LEN\((\w*?)\)/) {
		$dynamic_len = "\"$1\"";
	}

	if ($flags =~ /_NULLTERM/) {
		$tflags = "FLAG_NULLTERM";
	}

	print OFILE "{\"$element\", $ptr_count, $size, offsetof(struct $name, $element), $array_len, $dynamic_len, $tflags, $dump_fn, $parse_fn},\n";
}


####################################################
# parse one element
sub parse_one($$$$)
{
	my($name) = shift;
	my($type) = shift;
	my($element) = shift;
	my($flags) = shift;
	my($ptr_count) = 0;
	my($size) = "sizeof($type)";
	my($tflags) = "0";
	
	# enums get the FLAG_ALWAYS flag
	if ($type =~ /^enum /) {
		$tflags = "FLAG_ALWAYS";
	}


	# make the pointer part of the base type 
	while ($element =~ /^\*(.*)/) {
		$ptr_count++;
		$element = $1;
	}

	# convert spaces to _
	$type =~ s/ /_/g;

	my($dump_fn) = "gen_dump_$type";
	my($parse_fn) = "gen_parse_$type";

	handle_general($name, $ptr_count, $size, $element, $flags, $dump_fn, $parse_fn, $tflags);
}

####################################################
# parse one element
sub parse_element($$$)
{
	my($name) = shift;
	my($element) = shift;
	my($flags) = shift;
	my($type);
	my($data);

	# pull the base type
	if ($element =~ /^struct (\S*) (.*)/) {
		$type = "struct $1";
		$data = $2;
	} elsif ($element =~ /^enum (\S*) (.*)/) {
		$type = "enum $1";
		$data = $2;
	} elsif ($element =~ /^unsigned (\S*) (.*)/) {
		$type = "unsigned $1";
		$data = $2;
	} elsif ($element =~ /^const (\S*) (.*)/) {
		$type = $1;
		$data = $2;
	} elsif ($element =~ /^(\S*) (.*)/) {
		$type = $1;
		$data = $2;
	} else {
		die "Can't parse element '$element'";
	}

	# handle comma separated lists 
	while ($data =~ /(\S*),[\s]?(.*)/) {
		parse_one($name, $type, $1, $flags);
		$data = $2;
	}
	parse_one($name, $type, $data, $flags);
}


my($first_struct) = 1;

####################################################
# parse the elements of one structure
sub parse_elements($$)
{
	my($name) = shift;
	my($elements) = shift;

	if ($first_struct) {
		$first_struct = 0;
		print "Parsing structs: $name";
	} else {
		print ", $name";
	}

	print OFILE "int gen_dump_struct_$name(struct parse_string *, const char *, unsigned);\n";
	print OFILE "int gen_parse_struct_$name(char *, const char *);\n";

	print OFILE "static const struct parse_struct pinfo_" . $name . "[] = {\n";

	while ($elements =~ /^.*?([a-z].*?);\s*?(\S*?)\s*?$(.*)/msi) {
		my($element) = $1;
		my($flags) = $2;
		$elements = $3;
		parse_element($name, $element, $flags);
	}

	print OFILE "{NULL, 0, 0, 0, 0, NULL, 0, NULL, NULL}};\n";

	print OFILE "
int gen_dump_struct_$name(struct parse_string *p, const char *ptr, unsigned indent) {
	return gen_dump_struct(pinfo_$name, p, ptr, indent);
}
int gen_parse_struct_$name(char *ptr, const char *str) {
	return gen_parse_struct(pinfo_$name, ptr, str);
}

";
}

my($first_enum) = 1;

####################################################
# parse out the enum declarations
sub parse_enum_elements($$)
{
	my($name) = shift;
	my($elements) = shift;

	if ($first_enum) {
		$first_enum = 0;
		print "Parsing enums: $name";
	} else {
		print ", $name";
	}

	print OFILE "static const struct enum_struct einfo_" . $name . "[] = {\n";

	my(@enums) = split(/,/s, $elements);
	for (my($i)=0; $i <= $#{@enums}; $i++) {
		my($enum) = $enums[$i];
		if ($enum =~ /\s*(\w*)/) {
			my($e) = $1;
			print OFILE "{\"$e\", $e},\n";
		}
	}

	print OFILE "{NULL, 0}};\n";

	print OFILE "
int gen_dump_enum_$name(struct parse_string *p, const char *ptr, unsigned indent) {
	return gen_dump_enum(einfo_$name, p, ptr, indent);
}

int gen_parse_enum_$name(char *ptr, const char *str) {
	return gen_parse_enum(einfo_$name, ptr, str);
}

";
}

####################################################
# parse out the enum declarations
sub parse_enums($)
{
	my($data) = shift;

	while ($data =~ /^GENSTRUCT\s+enum\s+(\w*?)\s*{(.*?)}\s*;(.*)/ms) {
		my($name) = $1;
		my($elements) = $2;
		$data = $3;

		if (!defined($enum_done{$name})) {
			$enum_done{$name} = 1;
			parse_enum_elements($name, $elements);
		}
	}

	if (! $first_enum) {
		print "\n";
	}
}

####################################################
# parse all the structures
sub parse_structs($)
{
	my($data) = shift;

	# parse into structures 
	while ($data =~ /^GENSTRUCT\s+struct\s+(\w+?)\s*{\s*(.*?)\s*}\s*;(.*)/ms) {
		my($name) = $1;
		my($elements) = $2;
		$data = $3;
		if (!defined($struct_done{$name})) {
			$struct_done{$name} = 1;
			parse_elements($name, $elements);
		}
	}

	if (! $first_struct) {
		print "\n";
	} else {
		print "No GENSTRUCT structures found?\n";
	}
}


####################################################
# parse a header file, generating a dumper structure
sub parse_data($)
{
	my($data) = shift;

	# collapse spaces 
	$data =~ s/[\t ]+/ /sg;
	$data =~ s/\s*\n\s+/\n/sg;
	# strip debug lines
	$data =~ s/^\#.*?\n//smg;

	parse_enums($data);
	parse_structs($data);
}


#########################################
# display help text
sub ShowHelp()
{
    print "
generator for C structure dumpers
Copyright Andrew Tridgell <genstruct\@tridgell.net>

Sample usage:
   genstruct -o output.h gcc -E -O2 -g test.h

Options:
    --help                this help page
    -o OUTPUT             place output in OUTPUT
";
    exit(0);
}

########################################
# main program
if ($ARGV[0] ne "-o" || $#ARGV < 2) {
	ShowHelp();
}

shift;
my($opt_ofile)=shift;

open(OFILE, ">$opt_ofile") || die "can't open $opt_ofile";    

print OFILE "/* This is an automatically generated file - DO NOT EDIT! */\n\n";

parse_data(`@ARGV -DGENSTRUCT=GENSTRUCT`);
exit(0);
