#! /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 Getopt::Std;
use POSIX qw(tmpnam);

my $VERSION = 0.01;

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 -cO2 -e 'printf("hello world\\n")'

  Options:   -c<gcc_option>    pass an option to gcc
             -i<include_file>  add an include file
             -e expression     executes the expression
             -h, --help        displays this help message

EOF
    ;
    exit 0;
}

my $src_text;
my $out_file = tmpnam();
my $src_file = "$out_file.c";
my $includes = "#include <stdio.h>\n#include <stdlib.h>\n";
my @gcc_opts;

sub cleanup {
    unlink($src_file);
    unlink($out_file);
}

# parse args
while ($#ARGV <=> -1 && $ARGV[0] =~ /^-/) {
    my $arg = shift(@ARGV);
    if ($arg =~ /^-c/) {
	push(@gcc_opts, "-$'");
    } elsif ($arg eq '-e') {
	$src_text = $#ARGV <=> -1 ? shift(@ARGV).';' : undef;
    } elsif ($arg =~ /^-i/) {
	$includes .= "#include \"$'\"\n";
    } elsif ($arg eq '-h' || $arg eq '--help') {
	usage();
    } else {
	print STDERR "unknown option: $arg\n";
	exit(255);
    }
}
if (! defined($src_text)) {
    my $file;
    my $fd;
    if ($#ARGV <=> -1) {
	$file = shift(@ARGV);
	open($fd, $file) || die "cannot open file: $file\n";
    } else {
	$fd = \*STDIN;
    }
    while (my $l = <$fd>) {
	$src_text .= $l unless $l =~ /^#!/;
    }
    close($fd) if defined($file);
}

# build source
$src_text =<<"EOF";
$includes

int main(int argc, char** argv)
{
#line 1
    $src_text
    return 0;
}
EOF
;

# save source file
my $fd;
open($fd, "> $src_file") || die "cannot create source file: $src_file\n";
print $fd $src_text;
close($fd);

# compile
system('gcc', @gcc_opts, '-o', $out_file, $src_file);
cleanup() if ($? <=> 0);
die "failed to execute gcc\n" if $? == 1;
exit($? >> 8) if $? <=> 0;

# execute
system($out_file, @ARGV);
cleanup();
die "failed to execute the generated binary\n" if $? == 1;
exit($? >> 8) if $? <=> 0;
