diff options
Diffstat (limited to 'JPL/Compile.pm')
-rwxr-xr-x | JPL/Compile.pm | 772 |
1 files changed, 772 insertions, 0 deletions
diff --git a/JPL/Compile.pm b/JPL/Compile.pm new file mode 100755 index 0000000000..39dd6b806c --- /dev/null +++ b/JPL/Compile.pm @@ -0,0 +1,772 @@ +#!/usr/bin/perl -w + +# Copyright 1997, O'Reilly & Associate, Inc. +# +# This package may be copied under the same terms as Perl itself. + +package JPL::Compile; +use Exporter (); +@ISA = qw(Exporter); +@EXPORT = qw(files file); + +use strict; + + +warn "You don't have a recent JDK kit your PATH, so this may fail.\n" + unless $ENV{PATH} =~ /(java|jdk1.[1-9])/; + +sub emit; + +my $PERL = ""; +my $LASTCLASS = ""; +my $PERLLINE = 0; +my $PROTO; + +my @protos; + +my $plfile; +my $jpfile; +my $hfile; +my $h_file; +my $cfile; +my $jfile; +my $classfile; + +my $DEBUG = $ENV{JPLDEBUG}; + +my %ptype = qw( + Z boolean + B byte + C char + S short + I int + J long + F float + D double +); + +$ENV{CLASSPATH} =~ s/^/.:/ unless $ENV{CLASSPATH} =~ /^\.(?::|$)/; + +unless (caller) { + files(@ARGV); +} + +####################################################################### + +sub files { + foreach my $jpfile (@_) { + file($jpfile); + } + print "make\n"; + system "make"; +} + +sub file { + my $jpfile = shift; + my $JAVA = ""; + my $lastpos = 0; + my $linenum = 2; + my %classseen; + my %fieldsig; + my %staticfield; + + (my $file = $jpfile) =~ s/\.jpl$//; + $jpfile = "$file.jpl"; + $jfile = "$file.java"; + $hfile = "$file.h"; + $cfile = "$file.c"; + $plfile = "$file.pl"; + $classfile = "$file.class"; + + ($h_file = $hfile) =~ s/_/_0005f/g; + + emit_c_header(); + + # Extract out arg names from .java file, since .class doesn't have 'em. + + open(JPFILE, $jpfile) or die "Can't open $jpfile: $!\n"; + undef $/; + $_ = <JPFILE>; + close JPFILE; + + die "$jpfile doesn't seem to define class $file!\n" + unless /class\s+\b$file\b[\w\s.,]*{/; + + @protos = (); + open(JFILE, ">$jfile") or die "Can't create $jfile: $!\n"; + + while (m/\bperl\b([^\n]*?\b(\w+)\s*\(\s*(.*?)\s*\)[\s\w.,]*)\{\{(.*?)\}\}/sg) { + $JAVA = substr($`, $lastpos); + $lastpos = pos $_; + $JAVA .= "native"; + $JAVA .= $1; + + my $method = $2; + + my $proto = $3; + + my $perl = $4; + (my $repl = $4) =~ tr/\n//cd; + $JAVA .= ';'; + $linenum += $JAVA =~ tr/\n/\n/; + $JAVA .= $repl; + print JFILE $JAVA; + + $proto =~ s/\s+/ /g; + $perl =~ s/^[ \t]+\Z//m; + $perl =~ s/^[ \t]*\n//; + push(@protos, [$method, $proto, $perl, $linenum]); + + $linenum += $repl =~ tr/\n/\n/; + } + + print JFILE <<"END"; + static { + System.loadLibrary("$file"); + PerlInterpreter pi = new PerlInterpreter().fetch(); + // pi.eval("\$JPL::DEBUG = \$ENV{JPLDEBUG};"); + pi.eval("warn qq{loading $file\\n} if \$JPL::DEBUG"); + pi.eval("eval {require '$plfile'}; print \$@ if \$@;"); + } +END + + print JFILE substr($_, $lastpos); + + close JFILE; + + # Produce the corresponding .h file. Should really use make... + + if (not -s $hfile or -M $hfile > -M $jfile) { + if (not -s $classfile or -M $classfile > -M $jfile) { + unlink $classfile; + print "javac $jfile\n"; + system "javac $jfile" and die "Couldn't run javac: exit $?\n"; + if (not -s $classfile or -M $classfile > -M $jfile) { + die "Couldn't produce $classfile from $jfile!"; + } + } + unlink $hfile; + print "javah -jni $file\n"; + system "javah -jni $file" and die "Couldn't run javah: exit $?\n"; + if (not -s $hfile and -s $h_file) { + rename $h_file, $hfile; + } + if (not -s $hfile or -M $hfile > -M $jfile) { + die "Couldn't produce $hfile from $classfile!"; + } + } + + # Easiest place to get fields is from javap. + + print "javap -s $file\n"; + open(JP, "javap -s $file|"); + $/ = "\n"; + while (<JP>) { + if (/^\s+([A-Za-z_].*) (\w+)[\[\d\]]*;/) { + my $jtype = $1; + my $name = $2; + $_ = <JP>; + s!^\s*/\*\s*!!; + s!\s*\*/\s*!!; + print "Field $jtype $name $_\n" if $DEBUG; + $fieldsig{$name} = $_; + $staticfield{$name} = $jtype =~ /\bstatic\b/; + } + while (m/L([^;]*);/g) { + my $pclass = j2p_class($1); + $classseen{$pclass}++; + } + } + close JP; + + open(HFILE, $hfile) or die "Couldn't open $hfile: $!\n"; + undef $/; + $_ = <HFILE>; + close HFILE; + + die "panic: native method mismatch" if @protos != s/^JNIEXPORT/JNIEXPORT/gm; + + $PROTO = 0; + while (m{ + \*\s*Class:\s*(\w+)\s* + \*\s*Method:\s*(\w+)\s* + \*\s*Signature:\s*(\S+)\s*\*/\s* + JNIEXPORT\s*(.*?)\s*JNICALL\s*(\w+)\s*\((.*?)\) + }gx) { + my $class = $1; + my $method = $2; + my $signature = $3; + my $rettype = $4; + my $cname = $5; + my $ctypes = $6; + $class =~ s/_0005f/_/g; + if ($method ne $protos[$PROTO][0]) { + die "Method name mismatch: $method vs $protos[$PROTO][0]\n"; + } + print "$class.$method($protos[$PROTO][1]) => + $signature + $rettype $cname($ctypes)\n" if $DEBUG; + + # Insert argument names into parameter list. + + my $env = "env"; + my $obj = "obj"; + my @jargs = split(/\s*,\s*/, $protos[$PROTO][1]); + foreach my $arg (@jargs) { + $arg =~ s/^.*\b(\w+).*$/${1}/; + } + my @tmpargs = @jargs; + unshift(@tmpargs, $env, $obj); + print "\t@tmpargs\n" if $DEBUG; + $ctypes .= ","; + $ctypes =~ s/,/' ' . shift(@tmpargs) . '_,'/eg; + $ctypes =~ s/,$//; + $ctypes =~ s/env_/env/; + $ctypes =~ s/obj_/obj/; + print "\t$ctypes\n" if $DEBUG; + + my $jlen = @jargs + 1; + + (my $mangclass = $class) =~ s/_/_1/g; + (my $mangmethod = $method) =~ s/_/_1/g; + my $plname = $cname; + $plname =~ s/^Java_${mangclass}_${mangmethod}/JPL::${class}::${method}/; + $plname =~ s/Ljava_lang_String_2/s/g; + + # Make glue code for each argument. + + (my $sig = $signature) =~ s/^\(//; + + my $decls = ""; + my $glue = ""; + + foreach my $jarg (@jargs) { + if ($sig =~ s/^[ZBCSI]//) { + $glue .= <<""; +! /* $jarg */ +! PUSHs(sv_2mortal(newSViv(${jarg}_))); +! + + } + elsif ($sig =~ s/^[JFD]//) { + $glue .= <<""; +! /* $jarg */ +! PUSHs(sv_2mortal(newSVnv(${jarg}_))); +! + + } + elsif ($sig =~ s#^Ljava/lang/String;##) { + $glue .= <<""; +! /* $jarg */ +! tmpjb = (jbyte*)(*env)->GetStringUTFChars(env,${jarg}_,0); +! PUSHs(sv_2mortal(newSVpv((char*)tmpjb,0))); +! (*env)->ReleaseStringUTFChars(env,${jarg}_,tmpjb); +! + + } + elsif ($sig =~ s/^L([^;]*);//) { + my $pclass = j2p_class($1); + $classseen{$pclass}++; + $glue .= <<""; +! /* $jarg */ +! if (!${jarg}_stashhv_) +! ${jarg}_stashhv_ = gv_stashpv("$pclass", TRUE); +! +! PUSHs(sv_bless( +! sv_setref_iv(sv_newmortal(), Nullch, (IV)(void*)${jarg}_), +! ${jarg}_stashhv_)); +! if (jpldebug) +! fprintf(stderr, "Done with $jarg\\n"); +! + + $decls .= <<""; +! static HV* ${jarg}_stashhv_ = 0; + + + } + elsif ($sig =~ s/^\[+([ZBCSIJFD]|L[^;]*;)//) { + my $pclass = "jarray"; + $classseen{$pclass}++; + $glue .= <<""; +! /* $jarg */ +! if (!${jarg}_stashhv_) +! ${jarg}_stashhv_ = gv_stashpv("$pclass", TRUE); +! +! PUSHs(sv_bless( +! sv_setref_iv(sv_newmortal(), Nullch, (IV)(void*)${jarg}_), +! ${jarg}_stashhv_)); +! if (jpldebug) +! fprintf(stderr, "Done with $jarg\\n"); +! + + $decls .= <<""; +! static HV* ${jarg}_stashhv_ = 0; + + } + else { + die "Short signature: $signature\n" if $sig eq ""; + die "Unrecognized letter '" . substr($sig, 0, 1) . "' in signature $signature\n"; + } + } + + $sig =~ s/^\)// or die "Argument mismatch in signature: $method$signature\n"; + + my $void = $signature =~ /\)V$/; + + $decls .= <<"" if $signature =~ m#java/lang/String#; +! jbyte* tmpjb; + + $decls .= <<"" unless $void; +! SV* retsv; +! $rettype retval; +! +! if (jpldebug) +! fprintf(stderr, "Got to $cname\\n"); +! ENTER; +! SAVETMPS; + + emit <<""; +!JNIEXPORT $rettype JNICALL +!$cname($ctypes) +!{ +! static SV* methodsv = 0; +! static HV* stashhv = 0; +! dSP; +$decls +! PUSHMARK(sp); +! EXTEND(sp,$jlen); +! +! sv_setiv(perl_get_sv("JPL::_env_", 1), (IV)(void*)env); +! jplcurenv = env; +! +! if (jpldebug) +! fprintf(stderr, "env = %lx\\n", (long)$env); +! +! if (!methodsv) +! methodsv = (SV*)perl_get_cv("$plname", TRUE); +! if (!stashhv) +! stashhv = gv_stashpv("JPL::$class", TRUE); +! +! if (jpldebug) +! fprintf(stderr, "blessing obj = %lx\\n", obj); +! PUSHs(sv_bless( +! sv_setref_iv(sv_newmortal(), Nullch, (IV)(void*)obj), +! stashhv)); +! +$glue + + # Finally, call the subroutine. + + my $mod; + $mod = "|G_DISCARD" if $void; + + if ($void) { + emit <<""; +! PUTBACK; +! perl_call_sv(methodsv, G_EVAL|G_KEEPERR|G_DISCARD); +! + + } + else { + emit <<""; +! PUTBACK; +! if (perl_call_sv(methodsv, G_EVAL|G_KEEPERR)) +! retsv = *stack_sp--; +! else +! retsv = &sv_undef; +! + + } + + emit <<""; +! if (SvTRUE(GvSV(errgv))) { +! jthrowable newExcCls; +! +! (*env)->ExceptionDescribe(env); +! (*env)->ExceptionClear(env); +! +! newExcCls = (*env)->FindClass(env, "java/lang/RuntimeException"); +! if (newExcCls) +! (*env)->ThrowNew(env, newExcCls, SvPV(GvSV(errgv),na)); +! } +! + + # Fix up the return value, if any. + + if ($sig =~ s/^V//) { + emit <<""; +! return; + + } + elsif ($sig =~ s/^[ZBCSI]//) { + emit <<""; +! retval = ($rettype)SvIV(retsv); +! FREETMPS; +! LEAVE; +! return retval; + + } + elsif ($sig =~ s/^[JFD]//) { + emit <<""; +! retval = ($rettype)SvNV(retsv); +! FREETMPS; +! LEAVE; +! return retval; + + } + elsif ($sig =~ s#^Ljava/lang/String;##) { + emit <<""; +! retval = (*env)->NewStringUTF(env, SvPV(retsv,na)); +! FREETMPS; +! LEAVE; +! return retval; + + } + elsif ($sig =~ s/^L[^;]*;//) { + emit <<""; +! if (SvROK(retsv)) { +! SV* rv = (SV*)SvRV(retsv); +! if (SvOBJECT(rv)) +! retval = ($rettype)(void*)SvIV(rv); +! else +! retval = ($rettype)(void*)0; +! } +! else +! retval = ($rettype)(void*)0; +! FREETMPS; +! LEAVE; +! return retval; + + } + elsif ($sig =~ s/^\[([ZBCSIJFD])//) { + my $elemtype = $1; + my $ptype = "\u$ptype{$elemtype}"; + my $ntype = "j$ptype{$elemtype}"; + my $in = $elemtype =~ /^[JFD]/ ? "N" : "I"; + emit <<""; +! if (SvROK(retsv)) { +! SV* rv = (SV*)SvRV(retsv); +! if (SvOBJECT(rv)) +! retval = ($rettype)(void*)SvIV(rv); +! else if (SvTYPE(rv) == SVt_PVAV) { +! jsize len = av_len((AV*)rv) + 1; +! $ntype* buf = ($ntype*)malloc(len * sizeof($ntype)); +! int i; +! SV** esv; +! +! ${ntype}Array ja = (*env)->New${ptype}Array(env, len); +! for (esv = AvARRAY((AV*)rv), i = 0; i < len; esv++, i++) +! buf[i] = ($ntype)Sv${in}V(*esv); +! (*env)->Set${ptype}ArrayRegion(env, ja, 0, len, buf); +! free((void*)buf); +! retval = ($rettype)ja; +! } +! else +! retval = ($rettype)(void*)0; +! } +! else if (SvPOK(retsv)) { +! jsize len = sv_len(retsv) / sizeof($ntype); +! +! ${ntype}Array ja = (*env)->New${ptype}Array(env, len); +! (*env)->Set${ptype}ArrayRegion(env, ja, 0, len, ($ntype*)SvPV(retsv,na)); +! retval = ($rettype)ja; +! } +! else +! retval = ($rettype)(void*)0; +! FREETMPS; +! LEAVE; +! return retval; + + } + elsif ($sig =~ s!^\[Ljava/lang/String;!!) { + emit <<""; +! if (SvROK(retsv)) { +! SV* rv = (SV*)SvRV(retsv); +! if (SvOBJECT(rv)) +! retval = ($rettype)(void*)SvIV(rv); +! else if (SvTYPE(rv) == SVt_PVAV) { +! jsize len = av_len((AV*)rv) + 1; +! int i; +! SV** esv; +! static jclass jcl = 0; +! jarray ja; +! +! if (!jcl) +! jcl = (*env)->FindClass(env, "java/lang/String"); +! ja = (*env)->NewObjectArray(env, len, jcl, 0); +! for (esv = AvARRAY((AV*)rv), i = 0; i < len; esv++, i++) { +! jobject str = (jobject)(*env)->NewStringUTF(env, SvPV(*esv,na)); +! (*env)->SetObjectArrayElement(env, ja, i, str); +! } +! retval = ($rettype)ja; +! } +! else +! retval = ($rettype)(void*)0; +! } +! else +! retval = ($rettype)(void*)0; +! FREETMPS; +! LEAVE; +! return retval; + + } + elsif ($sig =~ s/^(\[+)([ZBCSIJFD]|L[^;]*;)//) { + my $arity = length $1; + my $elemtype = $2; + emit <<""; +! if (SvROK(retsv)) { +! SV* rv = (SV*)SvRV(retsv); +! if (SvOBJECT(rv)) +! retval = ($rettype)(void*)SvIV(rv); +! else if (SvTYPE(rv) == SVt_PVAV) { +! jsize len = av_len((AV*)rv) + 1; +! int i; +! SV** esv; +! static jclass jcl = 0; +! jarray ja; +! +! if (!jcl) +! jcl = (*env)->FindClass(env, "java/lang/Object"); +! ja = (*env)->NewObjectArray(env, len, jcl, 0); +! for (esv = AvARRAY((AV*)rv), i = 0; i < len; esv++, i++) { +! if (SvROK(*esv) && (rv = SvRV(*esv)) && SvOBJECT(rv)) { +! (*env)->SetObjectArrayElement(env, ja, i, +! (jobject)(void*)SvIV(rv)); +! } +! else { +! jobject str = (jobject)(*env)->NewStringUTF(env, +! SvPV(*esv,na)); +! (*env)->SetObjectArrayElement(env, ja, i, str); +! } +! } +! retval = ($rettype)ja; +! } +! else +! retval = ($rettype)(void*)0; +! } +! else +! retval = ($rettype)(void*)0; +! FREETMPS; +! LEAVE; +! return retval; + + } + else { + die "No return type: $signature\n" if $sig eq ""; + die "Unrecognized return type '" . substr($sig, 0, 1) . "' in signature $signature\n"; + } + + emit <<""; +!} +! + + my $perl = ""; + + if ($class ne $LASTCLASS) { + $LASTCLASS = $class; + $perl .= <<""; +package JPL::${class}; +use JNI; +use JPL::AutoLoader; +\@ISA = qw(jobject); +\$clazz = JNI::FindClass("$file");\n + + foreach my $field (sort keys %fieldsig) { + my $sig = $fieldsig{$field}; + my $ptype = $ptype{$sig}; + if ($ptype) { + $ptype = "\u$ptype"; + if ($staticfield{$field}) { + $perl .= <<""; +\$${field}_FieldID = JNI::GetStaticFieldID(\$clazz, "$field", "$sig"); +sub $field (\$;\$) { + my \$self = shift; + if (\@_) { + JNI::SetStatic${ptype}Field(\$clazz, \$${field}_FieldID, \$_[0]); + } + else { + JNI::GetStatic${ptype}Field(\$clazz, \$${field}_FieldID); + } +}\n + + } + else { + $perl .= <<""; +\$${field}_FieldID = JNI::GetFieldID(\$clazz, "$field", "$sig"); +sub $field (\$;\$) { + my \$self = shift; + if (\@_) { + JNI::Set${ptype}Field(\$self, \$${field}_FieldID, \$_[0]); + } + else { + JNI::Get${ptype}Field(\$self, \$${field}_FieldID); + } +}\n + + } + } + else { + my $pltype = $sig; + if ($pltype =~ s/^L(.*);/$1/) { + $pltype =~ s!/!::!g; + } + else { + $pltype = 'jarray'; + } + if ($pltype eq "java::lang::String") { + if ($staticfield{$field}) { + $perl .= <<""; +\$${field}_FieldID = JNI::GetStaticFieldID(\$clazz, "$field", "$sig"); +sub $field (\$;\$) { + my \$self = shift; + if (\@_) { + JNI::SetStaticObjectField(\$clazz, \$${field}_FieldID, + ref \$_[0] ? \$_[0] : JNI::NewStringUTF(\$_[0])); + } + else { + JNI::GetStringUTFChars(JNI::GetStaticObjectField(\$clazz, \$${field}_FieldID)); + } +}\n + + } + else { + $perl .= <<""; +\$${field}_FieldID = JNI::GetFieldID(\$clazz, "$field", "$sig"); +sub $field (\$;\$) { + my \$self = shift; + if (\@_) { + JNI::SetObjectField(\$self, \$${field}_FieldID, + ref \$_[0] ? \$_[0] : JNI::NewStringUTF(\$_[0])); + } + else { + JNI::GetStringUTFChars(JNI::GetObjectField(\$self, \$${field}_FieldID)); + } +}\n + + } + } + else { + if ($staticfield{$field}) { + $perl .= <<""; +\$${field}_FieldID = JNI::GetStaticFieldID(\$clazz, "$field", "$sig"); +sub $field (\$;\$) { + my \$self = shift; + if (\@_) { + JNI::SetStaticObjectField(\$clazz, \$${field}_FieldID, \$_[0]); + } + else { + bless JNI::GetStaticObjectField(\$clazz, \$${field}_FieldID), "$pltype"; + } +}\n + + } + else { + $perl .= <<""; +\$${field}_FieldID = JNI::GetFieldID(\$clazz, "$field", "$sig"); +sub $field (\$;\$) { + my \$self = shift; + if (\@_) { + JNI::SetObjectField(\$self, \$${field}_FieldID, \$_[0]); + } + else { + bless JNI::GetObjectField(\$self, \$${field}_FieldID), "$pltype"; + } +}\n + + } + } + } + } + } + + $plname =~ s/^JPL::${class}:://; + + my $proto = '$' x (@jargs + 1); + $perl .= "sub $plname ($proto) {\n"; + $perl .= ' my ($self, '; + foreach my $jarg (@jargs) { + $perl .= "\$$jarg, "; + } + $perl =~ s/, $/) = \@_;\n/; + $perl .= <<"END"; + warn "JPL::${class}::$plname(\@_)\\n" if \$JPL::DEBUG; +#line $protos[$PROTO][3] "$jpfile" +$protos[$PROTO][2]} + +END + + $PERLLINE += $perl =~ tr/\n/\n/ + 2; + $perl .= <<"END"; +#line $PERLLINE "" +END + $PERLLINE--; + + $PERL .= $perl; + } + continue { + $PROTO++; + print "\n" if $DEBUG; + } + + emit_c_footer(); + + rename $cfile, "$cfile.old"; + rename "$cfile.new", $cfile; + + open(PLFILE, ">$plfile") or die "Can't create $plfile: $!\n"; + print PLFILE "BEGIN { \$JPL::_env_ ||= 1; } # suppress bogus embedding\n\n"; + if (%classseen) { + my @classes = sort keys %classseen; + print PLFILE "use JPL::Class qw(@classes);\n\n"; + } + print PLFILE $PERL; + print PLFILE "1;\n"; + close PLFILE; + + print "perl -c $plfile\n"; + system "perl -c $plfile" and die "jpl stopped\n"; +} + +sub emit_c_header { + open(CFILE, ">$cfile.new") or die "Can't create $cfile.new: $!\n"; + emit <<""; +!/* This file is automatically generated. Do not modify! */ +! +!#include "$hfile" +! +!#ifdef __cplusplus +!extern "C" { +!#endif +! +!#include "EXTERN.h" +!#include "perl.h" +! +!#ifdef __cplusplus +!} +!# define EXTERN_C extern "C" +!#else +!# define EXTERN_C extern +!#endif +! +!extern int jpldebug; +!extern JNIEnv* jplcurenv; +! + +} + + +sub emit_c_footer { + close CFILE; +} + +sub emit { + my $string = shift; + $string =~ s/^!//mg; + print CFILE $string; +} + +sub j2p_class { + my $jclass = shift; + $jclass =~ s#/#::#g; + $jclass; +} |