4 # generate [-d dir] [-t package] [--enclosing-type type ] [-s struct-root-pattern]* [--struct-file file]* [-c class [-llib]* [-f func-pattern]* [--func-file file]* [-e enum-pattern]]*
6 # root output directory
9 # --enclosing-type type
10 # If supplied, all structures and classes are written to an enclosing class
11 # -s struct-root-pattern
12 # provide one or more patterns for matching structure roots.
13 # all dependencies are automatically included.
14 # if no pattern is provided, all match.
16 # provide a filename with exact structure names in it. These are
19 # specify class name to generate
21 # specify link library used by class
23 # function name pattern to include for the current class
25 # point to a filename with exact function names in it, one per line ('#' is a comment).
27 # enum name pattern to include for the current class
29 # filename with enums in it
31 # TODO: scan all functions and include any types they use as struct roots
32 # TODO: some way to specify external types
36 # @classes = ( { name => 'class', match => [ func-pattern, ... ], match_file => [ file, ... ], enum => [ enum-pattern, ... ], enum_file => [ file, ...] } )
40 # map call signatures to a class name
45 my $cmd = shift(@ARGV);
49 push @{$class{match}}, qr/$v/;
50 } elsif ($cmd eq "--func-file") {
51 my $file = shift(@ARGV);
53 push @{$class{match_file}}, $file;
54 push @{$class{match}}, readMatchFile($file);
55 } elsif ($cmd eq "-e") {
57 push @{$class{enum}}, qr/$v/;
58 } elsif ($cmd eq "--enum-file") {
59 my $file = shift(@ARGV);
60 push @{$class{enum_file}}, $file;
61 push @{$class{enum}}, readMatchFile($file);
62 } elsif ($cmd eq "-s") {
64 push @matchStruct, qr/$v/;
65 } elsif ($cmd eq "--struct-file") {
66 my $file = shift(@ARGV);
67 push @matchStruct, readMatchFile($file);
68 } elsif ($cmd eq "-t") {
69 $package = shift(@ARGV);
70 } elsif ($cmd eq "-c") {
80 print "new:\n".Dumper(\%class);
81 } elsif ($cmd =~ m/^-l(.*)/) {
82 push @{$class{libs}}, $1;
83 } elsif ($cmd eq "-d") {
84 $output = shift(@ARGV);
85 } elsif ($cmd eq "--enclosing-type") {
86 $enclosingType = shift(@ARGV);
97 # box types for primitives
102 "char" => "Character",
104 "double" => "Double",
113 open(my $f,"<$path");
118 #push @lines, qr/\^$_\$/;
123 my $all = join ('|', @lines);
131 $name =~ s/_(.)/uc($1)/eg;
139 $name =~ s/^(.)/uc($1)/e;
140 $name =~ s/_(.)/uc($1)/eg;
146 sub structSignature {
147 my %struct = %{shift(@_)};
148 my $union = shift(@_);
150 my @fields = @{$struct{fields}};
160 my $off = $field{offset};
162 # bitfields, this only handles 1x u64 bitfield section
163 # They need to: align to u32/u64
164 # Group fields into one full u32/u64
165 # TODO: check alignment @ start?
166 # TODO: clean up and complete
167 # TODO: bitfields in unions are probably broken
168 if ($field{ctype} eq 'bitfield') {
170 if ($off - $offset) {
172 $bfsig .= ($off - $offset);
174 $bfsig .= $field{type};
175 $bfsig .= "($field{name})";
176 $offset = $off + $field{size};
179 $bfsig = $field{type};
180 $bfsig .= "($field{name})";
181 $offset = $off + $field{size};
182 $bfstart = $field{offset};
188 if (($offset - $bfstart) == 32) {
189 $bfsig = "u32=[$bfsig]";
190 } elsif (($offset - $bfstart) < 32) {
192 $bfsig .= 32 - ($offset - $bfstart);
193 $offset = $bfstart + 32;
194 $bfsig = "u32=[$bfsig]";
195 } elsif (($offset - $bfstart) == 64) {
196 $bfsig = "u64=[$bfsig]";
197 } elsif (($offset - $bfstart) < 64) {
199 $bfsig .= 64 - ($offset - $bfstart);
200 $offset = $bfstart + 64;
201 $bfsig = "u64=[$bfsig]";
205 $sig .= "|" if ($union && $fi != @fields[$#fields]);
209 if (($offset - $bfstart) == 32) {
210 $bfsig = "u32=[$bfsig]";
211 } elsif (($offset - $bfstart) < 32) {
213 $bfsig .= 32 - ($offset - $bfstart);
214 $offset = $bfstart + 32;
215 $bfsig = "u32=[$bfsig]";
216 } elsif (($offset - $bfstart) == 64) {
217 $bfsig = "u64=[$bfsig]";
218 } elsif (($offset - $bfstart) < 64) {
220 $bfsig .= 64 - ($offset - $bfstart);
221 $offset = $bfstart + 64;
222 $bfsig = "u64=[$bfsig]";
228 # skip to next offset if necessary
229 if ($off > $offset) {
231 $sig .= ($off - $offset);
233 $offset = $off + $field{size};
235 # normal field processing
237 my $deref = $field{deref};
239 # HACK: function -> Void
240 # if ($field{debug} eq 'function') {
241 # $sig .= "u64($field{name}):v";
243 if ($deref =~ m/^(u\d\d)(:.*)/) {
244 $sig .= "$1($field{name})$2";
246 $sig .= "$deref($field{name})";
249 if ($field{type} =~ m/(struct|union):(.*)/) {
251 } elsif ($field{type} =~ m/([iuf])(\d+)/) {
254 } elsif ($field{type} eq 'void') {
256 } elsif ($field{type} eq 'enum') {
257 # FIXME: set type in compiler
261 $sig .= "($field{name})";
264 $sig .= "|" if ($union && $fi != @fields[$#fields]);
267 # finish any trailing bitfield
270 if (($offset - $bfstart) == 32) {
271 $bfsig = "u32=[$bfsig]";
272 } elsif (($offset - $bfstart) < 32) {
274 $bfsig .= 32 - ($offset - $bfstart);
275 $offset = $bfstart + 32;
276 $bfsig = "u32=[$bfsig]";
277 } elsif (($offset - $bfstart) == 64) {
278 $bfsig = "u64=[$bfsig]";
279 } elsif (($offset - $bfstart) < 64) {
281 $bfsig .= 64 - ($offset - $bfstart);
282 $offset = $bfstart + 64;
283 $bfsig = "u64=[$bfsig]";
293 my %func = %{shift(@_)};
295 my @params = @{$func{arguments}};
301 # HACK: function to void
302 if ($param{debug} eq "function") {
305 $sig .= $param{deref};
308 if ($param{type} =~ m/struct:(.*)/) {
310 } elsif ($param{type} =~ m/([iuf])(\d*)/) {
313 } elsif ($param{type} eq "void") {
319 my %result = %{$func{result}};
322 if ($result{deref}) {
323 $ret .= $result{deref};
325 if ($result{type} =~ m/^struct:(.*)/) {
327 } elsif ($result{type} =~ m/^([iuf])(\d+)/) {
330 } elsif ($result{type} eq "void") {
343 if ($ref =~ m/\[\d*(.*)\]/) {
344 my $sub = deref($type, $1);
346 return "Array<$sub>";
347 } elsif ($ref =~ m/^u64:(.*)/) {
348 $type = "Pointer<$type>";
358 my %param = %{shift(@_)};
359 my $type = $param{type};
360 my $ref = $param{deref};
362 if ($type =~ m/^struct:(.*)/) {
363 $type = StudlyCaps($1);
364 } elsif ($type =~ m/call:/) {
365 # this re-writes ref to remove one pointer-to as the Callback absorbs it.
366 $type = "Callback<".$callMap{$type}.">";
367 $type || die ("No mapping for type ".Dumper(\%param));
368 $ref =~ s/^u(32|64)://;
369 } elsif ($type =~ m/^enum:(.*)/) {
370 # TODO: other enum options
372 } elsif ($type eq "void") {
374 } elsif ($type =~ m/^([iu])(\d*)/) {
380 } elsif ($size <= 16) {
386 } elsif ($size <= 32) {
391 } elsif ($type =~ m/^[f](\d*)$/) {
396 } elsif ($size == 64) {
402 $type = $map_box{$type} if ($map_box{$type});
403 $type = deref($type, $ref);
414 if ($name =~ /$pat/) {
424 # find all matching structures and then all that they require
426 my %all = %{shift @_};
430 $e{type} =~ m/(struct|union)/ && testMatch($e{name}, @match);
435 my $test = shift @stack;
437 if (!$visit{$test}) {
438 my %struct = %{$all{$test}};
443 print "class: $struct{name}\n";
444 # find all types this one uses
445 for $f (@{$struct{fields}}) {
448 if ($field{type} =~ m/^(struct|union):(.*)/) {
449 if (!$set{$field{type}}) {
450 $set{$field{type}} = $all{$field{type}};
451 push @stack, $field{type};
456 # this is an anon type, typically used for handles
457 $test =~ m/^(struct|union):(.*)/;
464 $data{$test} = \%rec;
472 my %all = %{shift @_};
477 $e{type} eq $type && testMatch($e{name}, @match);
483 # ######################################################################
487 # find all classes used by functions
491 my @libs = @{$class{libs}};
492 my @match = @{$class{match}};
494 for $k (findDefinition(\%data, 'func', @match)) {
495 my %func = %{$data{$k}};
496 my @params = @{$func{arguments}};
501 if ($param{type} =~ m/^(struct|union):(.*)/) {
506 my %result = %{$func{result}};
508 if ($result{type} =~ m/^(struct|union):(.*)/) {
514 # add roots for any types used by calls
515 # FIXME: only include ones used elsewhere
516 for $k (grep { $_ =~ m/^call:/n } keys %data) {
517 my %func = %{$data{$k}};
518 my @params = @{$func{arguments}};
523 if ($param{type} =~ m/^(struct|union):(.*)/) {
528 my %result = %{$func{result}};
530 if ($result{type} =~ m/^(struct|union):(.*)/) {
535 # Create anonymous structs for anything missing
536 for $k (keys %roots) {
537 my $s = 'struct:'.$k;
540 if (!$data{$u} && !$data{$s}) {
551 $all = join ('|', keys %roots);
553 push @matchStruct, qr/^($all)$/;
555 print "structures:\n";
556 print Dumper(@matchStruct);
558 # make a map for all callbacks (call: type) to generated names
559 for $c (grep { $_ =~ m/^call:/n } keys %data) {
565 $name =~ s/^call:/Call/;
566 $name =~ s/\$\{[^\}]*\}/L/g;
567 $name =~ s/[ui](64|32):/p/g;
568 $name =~ s/[ui]64/J/g;
569 $name =~ s/[ui]32/I/g;
570 $name =~ s/[ui]8/B/g;
573 $name =~ s/[\[\]\(\)]/_/g;
575 $callMap{$c} = "$name";
578 print "call mappings\n";
579 print Dumper(\%callMap);
581 # ######################################################################
586 use File::Path qw(make_path);
588 if ($package ne "") {
589 $packagePrefix = $package.".";
592 if ($enclosingType) {
593 my $classname = $packagePrefix.$enclosingType;
595 $classname =~ s@\.@/@g;
597 my $path = $output."/".$classname.".java";
598 my $dir = dirname($path);
599 my $class = basename($path, ".java");
601 print "path $path\n";
602 print "dirname $dir\n";
605 open ($dst, ">$path");
607 if ($package ne "") {
608 print $dst "package $package;\n";
612 import java.foreign.Libraries;
613 import java.foreign.annotations.*;
614 import java.foreign.memory.*;
615 import java.lang.invoke.MethodHandles;
617 print $dst "public class $class {\n";
621 for $k (findStructs(\%data, @matchStruct)) {
622 my %struct = %{$data{$k}};
623 my @fields = @{$struct{fields}};
624 my $signature = structSignature(\%struct, ($struct{type} eq "union"));
625 my $name = StudlyCaps($struct{name});
627 if (!$enclosingType) {
628 my $classname = $packagePrefix.$name;
630 open ($dst, ">$path");
631 $classname =~ s@\.@/@g;
633 my $path = $output."/".$classname.".java";
634 my $dir = dirname($path);
635 my $class = basename($path, ".java");
637 open ($dst, ">$path");
639 if ($package ne "") {
640 print $dst "package $package;\n";
643 import java.foreign.annotations.*;
644 import java.foreign.memory.*;
648 # any in-line structures need to be added to the resolutionContext
649 # TODO: only include actual inline, not pointers
654 if ($field{type} =~ m/^(struct|union):(.*)/) {
655 $resolve{StudlyCaps($2).".class"} = 1;
658 my $resolve = join (",", keys %resolve);
660 print $dst "\@NativeStruct(value=\"$signature($struct{name})\"";
661 print $dst ", resolutionContext={$resolve}" if ($resolve);
663 print $dst "public interface $name extends Struct<$name> {\n";
667 my $type = typeToJava(\%field);
668 my $cc = StudlyCaps($field{name});
670 print $dst "\t\@NativeGetter(value=\"$field{name}\")\n";
671 print $dst "\tpublic $type get$cc();\n";
673 print $dst "\t\@NativeSetter(value=\"$field{name}\")\n";
674 print $dst "\tpublic void set$cc($type value);\n";
679 if (!$enclosingType) {
684 # Dump classes for library linkage
687 my @libs = @{$class{libs}};
688 my @match = @{$class{match}};
690 if (!$enclosingType) {
691 my $classname = $packagePrefix.$class{name};
693 open ($dst, ">$path");
694 $classname =~ s@\.@/@g;
696 my $path = $output."/".$classname.".java";
697 my $dir = dirname($path);
698 my $class = basename($path, ".java");
700 open ($dst, ">$path");
702 if ($package ne "") {
703 print $dst "package $package;\n";
706 import java.foreign.Libraries;
707 import java.foreign.annotations.*;
708 import java.foreign.memory.*;
709 import java.lang.invoke.MethodHandles;
713 print $dst "\@NativeHeader(libraries={";
714 print $dst join(",", map { "\"$_\"" } @libs);
716 print $dst "public interface $class{name} {\n";
720 # TODO: static lib class?
721 # typedef enums might appear twice in the data, so ignore duplicates
722 # also, some api's have multiple definitions (?)
724 my @match_enum = @{$class{enum}};
725 for $k (sort(findDefinition(\%data, 'enum', @match_enum))) {
726 my %enum = %{$data{$k}};
727 my @values = @{$enum{values}};
730 if ($enum{value_type} =~ m/^[ui](\d+)/) {
731 $type = "long" if ($1 > 32)
734 print $dst "\n\t// enum $enum{name}\n";
738 if (!$visited{$value{label}}) {
739 #print $dst "\tpublic static final $type $value{label} = ($type)$value{value};\n";
740 print $dst "\tpublic static final $type $value{label} = $value{value};\n";
741 $visited{$value{label}} = 1;
747 print "class $class{name} -> match:\n".Dumper(\@match);
749 for $k (sort(findDefinition(\%data, 'func', @match))) {
750 my %func = %{$data{$k}};
751 my @params = @{$func{arguments}};
752 my $signature = funcSignature(\%func);
753 my $name = ($func{name});
754 my $result = typeToJava(\%{$func{result}});
756 print $dst "\n\t\@NativeFunction(value=\"$signature\")\n";
757 print $dst "\tpublic $result $name(";
761 my $type = typeToJava($pi);
763 print $dst "$type $param{name}";
764 print $dst ", " if ($pi != $params[$#params]);
771 print $dst "\tpublic static final $class{name} bind = Libraries.bind(MethodHandles.lookup(), $class{name}.class);\n";
775 if (!$enclosingType) {
781 # TODO: only those used by classes and functions that were exported
782 for $c (keys %callMap) {
783 my %call = %{$data{$c}};
784 my $name = $callMap{$c};
785 my @params = @{$call{arguments}};
786 my $result = typeToJava(\%{$call{result}});
788 if (!$enclosingType) {
789 my $classname = $packagePrefix.$name;
791 open ($dst, ">$path");
792 $classname =~ s@\.@/@g;
794 my $path = $output."/".$classname.".java";
795 my $dir = dirname($path);
796 my $class = basename($path, ".java");
798 open ($dst, ">$path");
800 if ($package ne "") {
801 print $dst "package $package;\n";
804 import java.foreign.Libraries;
805 import java.foreign.annotations.*;
806 import java.foreign.memory.*;
810 # any in-line structures need to be added to the resolutionContext
811 # TODO: only include actual inline, not pointers
814 unshift(@list,$call{result});
818 if ($param{type} =~ m/^(struct|union):(.*)/) {
819 $resolve{StudlyCaps($2).".class"} = 1;
822 my $resolve = join (",", keys %resolve);
824 # FIXME: use something other than name to store this
825 print $dst "\@FunctionalInterface\n";
826 print $dst "\@NativeCallback(value=\"$call{name}\"";
827 print $dst ", resolutionContext={$resolve}" if ($resolve);
829 print $dst "public interface $name {\n";
830 print $dst "\tpublic $result fn(";
834 my $type = typeToJava($pi);
836 print $dst "$type $param{name}";
837 print $dst ", " if ($pi != $params[$#params]);
843 if (!$enclosingType) {
849 if ($enclosingType) {