#!/usr/bin/perl

# $Id$

# quarl 2006-07-06
#    builtin-maker parses a bunch of .c files full of definitions, and outputs
#    two files:
#        1) definitions of builtins which just call the non-builtin version
#        2) declarations of builtins

#    Input .c files look like this:
#        FAKEBUILTIN
#        char $_1_2 * strcpy (char $_1_2 * dest, const char $_1 * src) {}

#    Output definitions look like this:
#        char * strcpy (char * dest, const char * src);
#        char * __builtin_strcpy(char * dest, const char * src) {
#            return strcpy(dest, src);
#        }

#    Output declarations look like this:
#        char * __builtin_strcpy (char * dest, const char * src);

use strict;
use warnings;
use FileHandle;

sub ReadLines {
    my ($filename) = @_;

    my $f = new FileHandle($filename) or die;

    my $lines = [];
    local $_;
    while (<$f>) {
        chomp;
        push(@$lines, $_);
    }
    return $lines;
}

sub ParseLine {
    my ($line, $definitions_file, $declarations_file) = @_;

    local $_ = $line;
    # strip funky qualifiers
    s/ *[\$]([0-9_]+|tainted|untainted) */ /g;

    # split into return type, function name, {parameter type, parameter name}

    if (!/^([^\(]+[ *])([a-zA-Z0-9_]+)\s*\((.*)\)$/) {
        die "$0: couldn't parse line as function declaration: $line\n";
    }

    my $return_type = $1;
    my $function_name = $2;
    my $parameter_list = $3;

    $return_type =~ s/ +$//;

    # parse parameter list
    my $parameters = [];
    my $argument_list = [];
    my $vararg = 0;
    my $vparameter_list = [];
    for my $parameter (split(/ *, */, ($parameter_list eq 'void' ? '' : $parameter_list))) {
        if ($parameter eq '...') {
            $vararg = 1;
            # push(@$parameters, '...');
            # push(@$argument_list, 'args');
        } else {
            push(@$vparameter_list, $parameter);
            die if $vararg; # should have been last parameter
            if ($parameter !~ /^(.*[ *])([a-zA-Z0-9_]+)(\[[0-9]*\])*$/) {
                die "$0: couldn't parse parameter '$parameter' in line: $line\n";
            }
            push(@$parameters, [$1.($3||''), $2]);
            push(@$argument_list, $2);
        }
    }
    $vparameter_list = join(', ', @$vparameter_list);
    $argument_list = join(', ', @$argument_list);

    return { return_type     => $return_type,
             function_name   => $function_name,
             parameters      => $parameters,
             parameter_list  => $parameter_list,
             vparameter_list => $vparameter_list,
             argument_list   => $argument_list,
             vararg          => $vararg };
}

sub OutputBuiltinDefinition
{
    my ($p, $output_file) = @_;

    # first declare the non-builtin, then provide a definition for the builtin
    # that calls the non-builtin.

    my $ret1 = ($p->{return_type} eq 'void') ? '' : "$p->{return_type} ret = ";
    my $ret2 = ($p->{return_type} eq 'void') ? '' : "return ret;";

    if ($p->{vararg}) {
        my $lastparam = $p->{parameters}->[scalar(@{$p->{parameters}})-1]->[1];
        die "no last param" unless $lastparam;

        print $output_file <<END ;
$p->{return_type} v$p->{function_name} ($p->{vparameter_list}, __builtin_va_list args);
$p->{return_type} __builtin_$p->{function_name}($p->{vparameter_list}, ...) {
    __builtin_va_list args;
    __builtin_va_start(args, $lastparam);
    ${ret1}v$p->{function_name}($p->{argument_list}, args);
    __builtin_va_end(args);
    $ret2
}

END

   } else {
       my $ret = ($p->{return_type} eq 'void') ? '' : 'return ';
       print $output_file <<END ;
$p->{return_type} $p->{function_name} ($p->{parameter_list});
$p->{return_type} __builtin_$p->{function_name}($p->{parameter_list}) {
    $ret$p->{function_name}($p->{argument_list});
}

END
    }
}

sub OutputBuiltinDeclaration {
    my ($p, $output_file) = @_;

    # declare the builtin

    print $output_file <<END ;
$p->{return_type} __builtin_$p->{function_name} ($p->{parameter_list});
END
}

sub ProcessLine {
    my ($line, $definitions_file, $declarations_file) = @_;
    my $parsed = ParseLine($line, $definitions_file, $declarations_file);
    OutputBuiltinDefinition($parsed, $definitions_file);
    OutputBuiltinDeclaration($parsed, $declarations_file);
}

if (scalar(@ARGV) != 4) {
    die "syntax: $0 input_Manifest input-header.h output_definitions.c output_declarations.h";
}

my ($manifest_filename, $header_filename, $definitions_filename, $declarations_filename) = @ARGV;

my $src_filenames = ReadLines($manifest_filename);

my $definitions_file = new FileHandle(">$definitions_filename") or die;

my $declarations_file = new FileHandle(">$declarations_filename") or die;

print $definitions_file "/*** $definitions_filename - generated by builtin-maker */\n";
print $declarations_file "/*** $declarations_filename - generated by builtin-maker */\n";

# my $header_file = new FileHandle($header_filename) or die;
# print $definitions_file "/** $header_filename */\n";
# print $declarations_file "/** $header_filename */\n";
# while (<$header_file>) {
#     print $definitions_file $_;
#     print $declarations_file $_;
# }

print $definitions_file "#include \"$header_filename\"\n";
print $declarations_file "#include \"$header_filename\"\n";

for my $src_filename (@$src_filenames) {
    my $src_file = new FileHandle($src_filename) or die;
    print $definitions_file "\n/** $src_filename */\n";
    print $declarations_file "\n/** $src_filename */\n";
    local $_;
    while (<$src_file>) {
        # remove comments (only works for one-line comments right now)
        s,//.*,,; s,/\*.*?\*/,,;
        # 1) search for FAKEBUILTIN
        if (/FAKEBUILTIN/) {
            # 2) get the next line up to but not including "{"
            $_ = <$src_file>; chomp;
            s,//.*,,; s,/\*.*?\*/,,; # comments
            s/{.*//;                 # remove "{ ..."
            s/\s+$//; s/^\s+//;      # trim spaces
            ProcessLine($_, $definitions_file, $declarations_file);
        }
    }
}
