#!/usr/bin/env perl
######
# runtime.pl
# Tom Prince 2004/4/15
#
# Generate the runtime functions used by the vm::stack machine.
#
#####
use strict;
use warnings;
use Getopt::Long;
my $opsymbolsFile;
my $runtimeBaseFile;
my $prefix;
my $srcTemplateDir;
my $headerOutDir;
my $srcOutDir;
GetOptions(
"opsym-file=s" => \$opsymbolsFile,
"runtime-base-file=s" => \$runtimeBaseFile,
"src-template-dir=s" => \$srcTemplateDir,
"prefix=s" => \$prefix,
"header-out-dir=s" => \$headerOutDir,
"src-out-dir=s" => \$srcOutDir
) || die("Argument error");
my $outHeaderFile = "$headerOutDir/$prefix.h";
my $outSrcFile = "$srcOutDir/$prefix.cc";
my $stack = "Stack";
my $errors = 0;
sub report_error {
my $filename = shift;
my $line = shift;
my $error = shift;
print STDERR "$filename:$line: $error\n";
$errors = 1;
}
sub assoc_error {
my $filename = shift;
my $line = shift;
my $type = shift;
report_error($filename, $line, "no asy type associated to '$type'");
}
sub clean_type {
for (@_) {
s/\s//g;
}
}
sub clean_params {
for (@_) {
s/\n//g;
}
}
my %type_map;
sub read_types {
my @types = split /\n/, shift;
my $filename = shift;
my $line = shift;
for (@types) {
++$line;
# Remove // comments.
s/\/\/.*//g;
# Skip blank lines.
next if /^\s*$/;
my ($type,$code) =
m|(\w*(?:\s*\*)?)
\s*=>\s*
(.*)
|x;
if (not $type) {
report_error($filename, $line, "bad type declaration");
}
clean_type($type);
$type_map{$type} = $code;
}
}
# Scrape the symbol names of the operators from opsymbols.h.
my %opsymbols = ();
open(my $opsyms, $opsymbolsFile) ||
die("Couldn't open $opsymbolsFile");
while (<$opsyms>) {
if (m/^OPSYMBOL\(\"(.*)\", ([A-Za-z_]+)\);/) {
$opsymbols{ $1 } = $2;
}
}
close($opsyms);
# Turn a name into a symbol.
sub symbolize {
my $name = shift;
if ($name =~ /^[A-Za-z0-9_]+$/) {
return "SYM($name)";
}
if ($opsymbols{ $name }) {
return $opsymbols{ $name };
}
if ($name =~ /operator (\w+)/ && $opsymbols{ $1 }) {
return $opsymbols{ $1 }
}
return "symbol::trans(\"" . $name . "\")"
}
sub asy_params {
my $params = shift;
my @params = split m/,\s*/, $params;
my $filename = shift;
my $line = shift;
for (@params) {
my ($explicit, $type, $name, $default) =
m|^\s*
(explicit)*\s*(\w*(?:\s*\*)?)
\s*
(\w*)(=*)|xs;
clean_type($type);
if (not $type_map{$type}) {
assoc_error($filename, $line, $type);
}
$_ = "formal(" . $type_map{$type} . ", " .
symbolize(lc($name)) . ", " .
($default ? "true" : "false") . ", " .
($explicit ? "true" : "false") . ")";
}
return @params;
}
sub c_params {
my @params = @_;
for (@params) {
my ($explicit, $type, $name, $default, $value) =
m|^\s*
(explicit)*\s*(\w*(?:\s*\*)?)
\s*
(\w*)(=*)([\w.+\-]*)|xs;
$_ = " $type $name=vm::pop" . ($type =~ /^item$/ ? "" : "<$type>") .
"($stack" . ($default ? "," . $value : "") . ");\n";
}
reverse @params;
}
$/ = "\f\n";
open STDIN, "<$srcTemplateDir/$prefix.in" or die "can't open input file $srcTemplateDir/$prefix.in";
open BASE, "<$runtimeBaseFile" or die "can't open $runtimeBaseFile";
open STDOUT, ">$outSrcFile" or die "can't open output file $outSrcFile";
binmode STDIN, ":unix:crlf";
binmode BASE, ":unix:crlf";
my $autogenerated=
"/***** Autogenerated from $prefix.in; changes will be overwritten *****/\n\n";
my $base_source_line = 1;
my $source_line = 1;
print $autogenerated;
print "#line $base_source_line \"$srcTemplateDir/runtimebase.in\"\n";
my $baseheader = ;
print $baseheader;
my $basesource_line += ($baseheader =~ tr/\n//);;
my $basesource_type_line = $basesource_line;
print "#line $source_line \"$srcTemplateDir/$prefix.in\"\n";
my $header = <>;
print $header;
$source_line += ($header =~ tr/\n//);;
my $source_type_line = $source_line;
my $basetypes = ;
$basesource_line += ($basetypes =~ tr/\n//);;
my $types = <>;
$source_line += ($types =~ tr/\n//);;
print "#line $base_source_line \"$srcTemplateDir/runtimebase.in\"\n";
$baseheader = ;
print $baseheader;
$basesource_line += ($baseheader =~ tr/\n//);;
print "#line $source_line \"$prefix.in\"\n";
$header = <>;
print $header;
$source_line += ($header =~ tr/\n//);;
print "\n#ifndef NOSYM";
print "\n#include \"$prefix.symbols.h\"\n";
print "\n#endif";
print "\nnamespace run {\n";
read_types($basetypes, "runtimebase.in", $basesource_type_line);
read_types($types, "$prefix.in", $source_type_line);
### Begining of `$prefix.h'
my @header;
push @header, $autogenerated;
# TODO: Capitalize prefix
push @header, "#pragma once\n";
push @header, "namespace run {\n";
my $count = 0;
my @builtin;
while (<>) {
my ($comments,$type,$name,$cname,$params,$code) =
m|^((?:\s*//[^\n]*\n)*) # comment lines
\s*
(\w*(?:\s*\*)?) # return type
\s*
([^(:]*)\:*([^(]*) # function name
\s*
\(([\w\s*,=.+\-]*)\) # parameters
\s*
\{(.*)} # body
|xs;
if (not $type) {
report_error("$prefix.in", $source_line, "bad function definition");
}
if($cname) {push @header, "void $cname(vm::stack *);\n";}
else {$cname="gen_$prefix${count}";} # Unique C++ function name
clean_type($type);
my @params = split m/,\s*/, $params;
# Build addFunc call for asymptote
if($name) {
$name =~ s/Operator\s*//;
if (not $type_map{$type}) {
assoc_error("$prefix.in", $source_line, $type);
}
my @asy_params = asy_params($params, "$prefix.in", $source_line);
push @builtin, "#line $source_line \"$srcTemplateDir/$prefix.in\"\n"
. " addFunc(ve, run::" . $cname
. ", " . $type_map{$type}
. ", " . symbolize($name)
. ( @params ? ", " . join(", ",@asy_params)
: "" )
. ");\n";
}
# Build REGISTER_BLTIN command for builtin functions which are not added to
# the environment.
if (not $name and $cname) {
push @builtin, "#line $source_line \"$srcTemplateDir/$prefix.in\"\n"
. " REGISTER_BLTIN(run::" . $cname
. ',"' . $cname . '"' . ");\n";
}
# Handle marshalling of values to/from stack
my $qualifier = ($type eq "item" ? "" : "<$type>");
$code =~ s/\breturn ([^;]*);/{$stack->push$qualifier($1); return;}/g;
my $args = join("",c_params(@params));
print $comments;
my $ncomments = ($comments =~ tr/\n//);
$source_line += $ncomments;
print "#line $source_line \"$srcTemplateDir/$prefix.in\"\n";
my $prototype=$type . " " . $name . "(" . $params . ");";
my $nprototype = ($prototype =~ tr/\n//)+1;
$source_line += $nprototype;
if($name) {
clean_params($prototype);
print "// $prototype\n";
}
print "void $cname(stack *";
if($type ne "void" or $params ne "") {print $stack;}
print ")\n{\n$args";
print "#line $source_line \"$srcTemplateDir/$prefix.in\"";
print "$code}\n\n";
$source_line -= $ncomments+$nprototype;
$source_line += ($_ =~ tr/\n//);
++$count;
}
print "} // namespace run\n";
print "\nnamespace trans {\n\n";
print "void gen_${prefix}_venv(venv &ve)\n{\n";
print @builtin;
print "}\n\n";
print "} // namespace trans\n";
### End of `header.h'
push @header, "}\n\n";
undef $/;
open my $HEADER, "<", $outHeaderFile;
my $orig_header = <$HEADER>;
close $HEADER;
my $new_header = join "", @header;
if ($new_header ne $orig_header) {
open $HEADER, ">", $outHeaderFile;
print $HEADER $new_header;
close $HEADER;
}
if ($errors) {
unlink($outHeaderFile);
unlink($outSrcFile);
}
exit($errors);