# perl module for generating C code.
# arch-tag: d80bc4c9-0bf3-4786-878b-63905f0bcb29
 
package CGenerator;
use strict;

use Getopt::Std;
#use Data::Dumper;


sub new ($) { bless { Name => $_[0], cd => "", hd => "", enums => {}, cinc => [], hinc => [], cbody => [], hbody => [], structs => {}, funs => {}  } }
sub gen_comment () { printf "/* generated by %s at %s */\n", $0,  scalar(localtime); }

sub cquote ($) {
    ($_) = @_;
    s/^"/\\"/;
    s/([^\\])"/$1\\"/g;
    return "\"$_\"";
}

sub c {
    my $self = shift;
    $self->{cd} .= "$_[0]\n";
}

sub cb {
    my $self = shift;
    push @{$self->{cbody}},$_[0];
}

sub h {
    my $self = shift;
    $self->{hd} .= "$_[0]\n";
}

sub dd {
    my ($self, $decl, $value) = @_;
    $self->c(sprintf "%s%s;", $decl, $value ? " = $value" : "" );
    $self->h(sprintf "extern %s;", $decl) if $decl !~ /static/;
}

sub enum {
    my $self = shift;
    push @{$self->{enums}{$_[0]}},$_[1];
}

sub struct {
    my $self = shift;
    push @{$self->{structs}{$_[0]}},$_[1];
}

sub fun {
    my $self = shift;
    my $f = shift;
    push @{$self->{funs}{$f}{body}},@_;
}

sub fund {
    my $self = shift;
    my $f = shift;
    $self->{funs}{$f}{ret} = shift;
    $self->{funs}{$f}{proto} = [@_];
}

sub hinc {
    my ($self,$fn,$local) = @_;
    push @{$self->{hinc}}, $local ? "\"$fn\"" : "<$fn>";
}
sub cinc {
    my ($self,$fn,$local) = @_;
    push @{$self->{cinc}}, $local ? "\"$fn\"" : "<$fn>";
}

sub gen_headers {
    my $prev = ""; 
    my @hi = grep($_ ne $prev && ($prev = $_, 1), sort @_);
    foreach (@hi) { next if /^\"/; print "#include $_\n"; }
    print "\n";
    foreach (@hi) { next unless /^\"/; print "#include $_\n"; }
}

sub gen_body {
    my %saw;
    my @out = grep(!$saw{$_}++, @_);
    map { print "$_\n\n" } @out;
}

sub gen_c {
    my $self = shift;
    gen_comment();

    gen_headers(@{$self->{cinc}});

    print "#include \"$self->{Name}.h\"\n\n";

    print $self->{cd};

    gen_body(@{$self->{cbody}});

    foreach (keys %{$self->{funs}}) {
        my %v = %{$self->{funs}{$_}};
        print "$v{ret}\n$_" . "(" . ((join ", ", @{$v{proto}}) || "void") . ")\n{\n";
        foreach (@{$v{body}}) {
            print "\t$_\n";
        }
        print "}\n\n";
    }
}

sub gen_h  {
    my $self = shift;
    my $Name = uc $self->{Name};
    gen_comment();
    print "#ifndef ${Name}_H\n";
    print "#define ${Name}_H\n\n";
    
    gen_headers(@{$self->{hinc}});

    foreach (keys %{$self->{enums}}) {
        my @v = @{$self->{enums}{$_}};
        print "enum $_ { ", (join ", ",@v), " };\n";
    }
    
    foreach (keys %{$self->{structs}}) {
        my @v = @{$self->{structs}{$_}};
        print "struct $_ {\n";
        map { print "\t$_;\n"; } @v;
        print "};\n\n";
    }

    print $self->{hd};

    foreach (keys %{$self->{funs}}) {
        my %v = %{$self->{funs}{$_}};
        next if $v{ret} =~ /static/;
        print "$v{ret} $_" . "(" . ((join ", ", @{$v{proto}}) || "void") . ");\n";
    }

    print "\n#endif\n";
    
}

sub show {
    my $self = shift;
    my $o = shift;
    #Getopt::Std::getopts("ch", \%o);
    $self->gen_c if $o->{c}; 
    $self->gen_h if $o->{h}; 
}

1;

