#!/usr/bin/perl
# 
# C - a pseudo-interpreter of the C programming language
# http://labs.cybozu.co.jp/blog/kazuhoatwork/
# 
# Copyright (C) 2006 Cybozu Labs, Inc.
# 
# 
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
# 
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
# 
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
# MA 02110-1301, USA.
# 
# 

use strict;
use warnings;
use File::Temp qw(tempdir);


our $VERSION = 0.03;


sub usage {
    print <<"EOF";
C - a pseudo interpreter of the C programming language (version $VERSION)

http://labs.cybozu.co.jp/blog/kazuhoatwork/

    Usage:   $0 [options] [source_file] [argv]

  Example:   C -cWall -e 'printf("hello world\\n")'

  Options:   -c<gcc_option>    pass a compiler option to gcc
             -i<include_file>  add an include file
             -d                use debugger
             -e expression     executes the expression
	     -k                keep temporary files
	     -l<gcc_option>    pass a linker option to gcc
	     -m                use main function
             -p                use C++ (implies -m)
             -h, --help        displays this help message

EOF
    ;
    exit 0;
}

my $src_text;
my $work_dir = tempdir();
my $out_file = "$work_dir/a.out";
my $src_file = "$out_file.c";
my $includes = "#include <stdio.h>\n#include <stdlib.h>\n";
my $use_main = 0;
my $use_plusplus = 0;
my $use_debugger = 0;
my $keep_files = 0;
my @copts;
my @lopts;
my @line_pragma = ('', '');


# cleanup
sub cleanup {
    return if $keep_files;
    unlink($src_file);
    unlink($out_file);
    rmdir($work_dir);
}

# show error if used in file
sub assert_cmdline {
    my ($opt, $file, $line) = @_;
    die "$file:$line: $opt cannot be used in file\n" if defined($file);
}

# parse args
sub parse_args {
    my ($args, $file, $line) = @_;
    while (@$args && $args->[0] =~ /^-/) {
	my $arg = shift(@$args);
	if ($arg =~ /^-c/) {
	    push(@copts, "-$'");
	} elsif ($arg eq '-d') {
	    push(@copts, '-g');
	    $use_debugger = 1;
	} elsif ($arg eq '-e') {
	    assert_cmdline($arg, $file, $line);
	    if ($#$args == -1) {
		print STDERR "$file:$line: -e cannot be used in file\n";
		exit(255);
	    }
	    $src_text = shift(@$args);
	} elsif ($arg =~ /^-i/) {
	    assert_cmdline($arg, $file, $line);
	    $includes .= "#include \"$'\"\n";
	} elsif ($arg eq '-k') {
	    $keep_files = 1;
	} elsif ($arg =~ /^-l/) {
	    push(@lopts, "-$'");
	} elsif ($arg eq '-m') {
	    $use_main = 1;
	} elsif ($arg eq '-p') {
	    $includes .= "#include <iostream>\n";
	    $use_main = 1;
	    $use_plusplus = 1;
	} elsif ($arg eq '-h' || $arg eq '--help') {
	    assert_cmdline($arg, $file, $line);
	    usage();
	} else {
	    print STDERR "unknown option: $arg\n";
	    exit(255);
	}
    }
}

parse_args(\@ARGV);

if (! defined($src_text)) {
    my $file;
    my $fh;
    if (@ARGV) {
	$file = shift(@ARGV);
	open($fh, '<', $file) or die "cannot open file: $file";
    } else {
	$fh = \*STDIN;
    }
    my $lineno = 0;
    while (my $l = <$fh>) {
	$lineno++;
	my $comment_out = 1;
	if ($l =~ /^\#!/) {
	} elsif ($l =~ /^\#option\s+/) {
	    my @args = split(/\s+/, $');
	    parse_args(\@args, defined($file) ? $file : 'stdin', $lineno);
	} else {
	    $comment_out = 0;
	}
	$l = "// $l" if $comment_out;
	$src_text .= $l;
    }
    if (defined($file)) {
	close($fh);
	my $at = (($includes . $src_text) =~ tr/\n/\n/) + 6;
	@line_pragma = ("# 1 \"$file\" 1",
			"# $at \"$src_file\" 2");
    }
} else {
    if ($use_plusplus) {
	$src_text = "using namespace std;\n".$src_text;
    }
}

# build source
if ($use_main) {
    $src_text = "$line_pragma[0]\n$src_text\n$line_pragma[1]\n";
} else {
    $src_text =<<"EOF";
int main(int argc, char** argv)
{
$line_pragma[0]
    $src_text;
$line_pragma[1]
    return 0;
}
EOF
    ;
}

# save source file
open(my $fh, '>', $src_file) or
    die "cannot create source file: $src_file : $!";
print $fh $includes.$src_text;
close($fh);

# compile
if ((my $err = system($use_plusplus ? 'g++' : 'gcc', @copts, '-o', $out_file,
		      $src_file, @lopts))
    != 0) {
    cleanup();
    die "failed to execute gcc: $!" if $err == -1;
    exit($err >> 8);
}

# execute
my @args = ($out_file, @ARGV);
unshift(@args, "gdb") if $use_debugger;
system(@args);
cleanup();
die "failed to execute the generated binary : $!" if $? == -1;
exit($? >> 8) if $? != 0;

__END__

=head1 NAME

C - a pseudo-interpreter of the C programming language

=head1 SYNOPSIS

/usr/bin/C [options] [source_file] [arguments...]

C hello.C                                # executes hello.C

C -cWall -e 'printf("hello world\n")'    # one-liner

C -m uuencode.c encoded.txt < data.bin   # run an ordinally C source

C -cWall -cO2 hello.C                    # gcc -Wall -O2

C -d hello.C                             # debug hello.C
    
=head1 DESCRIPTION

C (pronounced large-C) is a pseudo-interpreter of the C programming language.

Without the need of manual compilation, developers can rapidly create scripts or write one-liners using the C programming language that runs at native code speed.

=head1 OPTIONS

-c<gcc_option>    pass a compiler option to GCC

-d                use debugger

-e expression     executes the expression

-i<include_file>  add an include file

-k                keep temporary files

-l<gcc_option>    pass a linker option to GCC

-m                use main function

-p                use C++ (implies -m)

-h, --help        displays this help message

=head1 NOTES

Several options can be defined within the source code using the ``#option'' directive.

Example:

    #!/usr/bin/C
    #option -cWall -cO2

    printf("hello world\n");

=head1 AUTHOR

Kazuho Oku E<lt> kazuho ___at___ labs.cybozu.co.jpE<gt>

=head1 COPYRIGHT

Coypright(C) 2006 Cybozu Labs, Inc.

=cut
   
