Partial move to new generator.
authorNot Zed <notzed@gmail.com>
Tue, 1 Feb 2022 00:55:47 +0000 (11:25 +1030)
committerNot Zed <notzed@gmail.com>
Tue, 1 Feb 2022 00:55:47 +0000 (11:25 +1030)
23 files changed:
api/Makefile [new file with mode: 0644]
api/api.c [moved from test-api/api.c with 100% similarity]
api/api.h [new file with mode: 0644]
src/apigen.pm [new file with mode: 0644]
src/code.api [new file with mode: 0644]
src/code.pm [new file with mode: 0644]
src/export-defines
src/export.cc
src/generate-api-2 [new file with mode: 0755]
src/method.pm [new file with mode: 0644]
src/tokenise.pm [new file with mode: 0644]
src/types.api [new file with mode: 0644]
test-api-object/Makefile [new file with mode: 0644]
test-api-object/README [moved from test-api/README with 100% similarity]
test-api-object/api-object.api [new file with mode: 0644]
test-api-object/src/api/test/TestAPI.java [new file with mode: 0644]
test-api-static/Makefile [new file with mode: 0644]
test-api-static/README [new file with mode: 0644]
test-api-static/api-static.api [new file with mode: 0644]
test-api-static/src/api/test/TestAPI.java [moved from test-api/src/api/test/TestAPI.java with 92% similarity]
test-api/Makefile [deleted file]
test-api/api.api [deleted file]
test-api/api.h [deleted file]

diff --git a/api/Makefile b/api/Makefile
new file mode 100644 (file)
index 0000000..fbe6563
--- /dev/null
@@ -0,0 +1,18 @@
+
+CFLAGS=-g -fPIC
+
+all::
+       mkdir -p bin
+
+all:: bin/libapi.so
+
+bin/api.o: api.c api.h
+       $(CC) $(CFLAGS) -c -o $@ $<
+
+bin/libapi.so: bin/api.o
+       $(CC) -o $@ -shared $^
+
+clean:
+       rm -rf bin
+
+.PHONY: clean all
similarity index 100%
rename from test-api/api.c
rename to api/api.c
diff --git a/api/api.h b/api/api.h
new file mode 100644 (file)
index 0000000..50356a0
--- /dev/null
+++ b/api/api.h
@@ -0,0 +1,88 @@
+
+#define API_FOO "12"
+#define API_MAKEVERSION(a, b) (a <<16) | b
+#define API_VERSION API_MAKEVERSION(1, 0)
+#define API_A 12UL
+#define API_B 12L
+#define API_C 12U
+#define API_D 12.0
+#define API_E 12.0f
+
+enum api_enum {
+       OK,
+       NOT_OK
+};
+
+union number {
+       int int32;
+       float float32;
+};
+
+struct foo {
+       int a;
+       int b;
+       struct anon *c;
+};
+
+typedef void (*foo_fn1)(float x, float y);
+typedef float (*foo_fn2)(float x, float y);
+
+struct data {
+       struct data *next;
+
+       int a;
+       int b;
+       int c:3;
+       unsigned d:5;
+       int (*test_a)(void);
+
+       enum api_enum status;
+#if 0
+       int (*test_b)(int a, int b, int c);
+       int (*test_c)(int d, int (*foo)(int e, int f), int g);
+       void (*test_d)(void);
+
+       foo_fn test_e;
+       void (*test_f)(foo_fn h);
+
+
+       char array1d[12];
+       char array2d[12][3];
+
+       struct foo foo;
+#endif
+};
+
+void print_data(struct data *data);
+
+struct api {
+       void (*funca)(int a);
+       int (*funcb)(int b);
+       int (*funcc)(float b);
+};
+
+void *api_func(const char *name);
+void api_free(struct api *api);
+struct api *api_create(void);
+void api_void(struct api *, int *);
+
+void api_data(struct api *, int size, void *data);
+
+// constructor which returns object
+struct api *api_new_a(void);
+// constructor which returns object, rc via pointer
+struct api *api_new_b(int *rc);
+// constructor which returns value in pointer-holder, and error via return code
+int api_new_c(struct api **apip);
+// constructor which returns value in pointer-holder, error via ?
+void api_new_d(struct api **apip);
+// constructor which returns value in pointer-holder, and error via return code holder
+void api_new_e(struct api **apip, int *rc);
+
+/*
+struct list_node {
+       struct list_node *succ;
+       struct list_node *pred;
+       char name[64-16];
+};
+*/
diff --git a/src/apigen.pm b/src/apigen.pm
new file mode 100644 (file)
index 0000000..afb5cda
--- /dev/null
@@ -0,0 +1,602 @@
+
+package apigen;
+
+use strict;
+use Data::Dumper;
+require genconfig;
+
+my %renameTable = (
+       'studly-caps' => sub { my $s = shift; $s =~ s/(?:^|_)(.)/\U$1/g; return $s; },
+       'camel-case' => sub { my $s = shift; $s =~ s/(?:_)(.)/\U$1/g; return $s; },
+       'upper-leadin' => sub { my $s = shift; $s =~ s/^([^_]+)/\U$1/; return $s; },
+       'identity' => sub {     return $_[0]; },
+       'call' => sub {
+               my $s = shift;
+
+               if ($s =~ m/\(/) {
+                       $s =~ s/u32:|u64:/p/g;
+                       $s =~ s/\$\{([^\}]+)\}/$1/g;
+                       $s =~ s/[\(\)]/_/g;
+                       $s =~ s/^/Call/;
+               }
+               $s;
+       }
+
+);
+
+my %defaultTable = (
+       'struct:<default>' => {
+               name => '<default>',
+               items => [],
+               options => [ 'default=none', 'access=rw' ],
+               regex => qr/^struct:<default>$/,
+               type => 'struct'
+       },
+       'union:<default>' => {
+               name => '<default>',
+               items => [],
+               options => [ 'default=none', 'access=rw' ],
+               regex => qr/^union:<default>$/,
+               type => 'union'
+       },
+       'call:<default>' => {
+               name => '<default>',
+               items => [],
+               options => [ 'rename=call', 'access=r' ],
+               regex => qr/^call:<default>$/,
+               type => 'call'
+       },
+);
+
+# hmm, so do i iterate 'control' and find matches in 'api'
+# or do i iterate 'api' and find matches in 'control'?
+
+sub new {
+       my $class = shift;
+       my $file = shift;
+       my $self = { };
+
+       $self->{api} = genconfig::loadControlFile($file);
+       $self->{index} = {};
+       foreach my $obj (@{$self->{api}}) {
+               $self->{index}->{"$obj->{type}:$obj->{name}"} = $obj;
+       }
+       foreach my $k (keys %defaultTable) {
+               $self->{index}->{$k} = $defaultTable{$k} if !defined($self->{index}->{$k});
+       }
+
+       $self->{data} = {};
+       while ($#_ >= 0) {
+               my $name = shift;
+               my $info = loadAPIFile($name);
+
+               $self->{data} = { %{$self->{data}}, %{$info} };
+       }
+
+       analyseAPI($self);
+       preprocess($self);
+
+       # add phantom 'api' entries for anything
+       foreach my $s (findDependencies($self)) {
+               my $n = "$s->{type}:$s->{name}";
+               my $def = $self->{index}->{"$s->{type}:<default>"};
+
+               die "no default for implicit dependency $n" if (!$def);
+
+               my $obj = {
+                       name => $s->{name},
+                       items => $def->{items},
+                       options => $def->{options},
+                       regex => qr/^$n$/,
+                       type => $def->{type}
+               };
+
+               print " implicit $n\n";
+               push @{$self->{api}}, $obj;
+               $self->{index}->{$n} = $obj;
+       }
+
+       postprocess($self);
+
+       bless $self, $class;
+
+       return $self;
+}
+
+sub renameFunction {
+       my $api = shift;
+       my $name = shift;
+       $renameTable{$name};
+}
+
+# find data objects matching top-level data objects
+# return [ (obj, def) ] pairs in a list
+# if there are multiple matches only return the first
+sub findObjects {
+       my $api = shift;
+       my $type = shift;
+       my @list;
+       my %seen = ();
+
+       #print Dumper($api->{data});
+
+       foreach my $obj (grep { $_->{type} eq $type} @{$api->{api}}) {
+               my $s = $api->{data}->{"$type:$obj->{name}"};
+               print "? $obj->{name}  regex $obj->{regex} s=$s\n";
+               if ($s) {
+                       next if $seen{$s->{name}}++;
+                       push @list, [ $obj, $s ];
+               } else {
+                       foreach my $k (grep { $_ =~ $obj->{regex} } keys(%{$api->{data}})) {
+                               $s = $api->{data}->{$k};
+                               next if $seen{$s->{name}}++;
+                               #print "+  $k\n";
+                               push @list, [ $obj, $s ];
+                       }
+               }
+       }
+
+       return @list;
+}
+
+sub findAllObjects {
+       my $api = shift;
+       my @list;
+
+       foreach my $obj (@{$api->{api}}) {
+               print "? $obj->{name}\n";
+               foreach my $dat (grep { $_->{name} =~ $obj->{regex} } values(%{$api->{data}})) {
+                       #print "+  $dat->{name}\n";
+                       push @list, [ $obj, $dat ];
+               }
+       }
+
+       return @list;
+}
+
+# TODO: check these next few against the one in generate-api,
+
+# find option(s) in an obj or inc
+sub option {
+       my $obj = shift;
+       my $name = shift;
+       my $rx = qr/^$name=/;
+
+       return grep { $_ =~ m/$rx/ } @{$obj->{options}};
+}
+
+# find value of first option of the given name
+sub optionValue {
+       my $name = shift;
+       my $or = shift;
+       my $rx = qr/$name/;
+
+       foreach my $obj (@_) {
+               foreach my $opt (@{$obj->{options}}) {
+                       return $1 if ($opt =~m/^$rx=(.*)/);
+               }
+       }
+       $or;
+}
+
+# look for all matching options of the given name
+# multiple objects are searched, the first one with
+#  the given parameter overrides the rest.
+# name, object, object *
+sub optionValues {
+       my $name = shift;
+       my $rx = qr/$name/;
+       my @list;
+
+       foreach my $obj (@_) {
+               foreach my $opt (@{$obj->{options}}) {
+                       push @list, $1 if ($opt =~m/^$rx=(.*)/);
+               }
+               last if ($#list >= 0);
+       }
+       @list;
+}
+
+# find first occurance of a flag
+sub optionFlag {
+       my $name = shift;
+
+       foreach my $obj (@_) {
+               foreach my $opt (@{$obj->{options}}) {
+                       return 1 if ($opt eq $name);
+               }
+       }
+       0;
+}
+
+# find live/desired items of a given type under a particular object
+# returns list of [ $inc, $field/argument ]
+sub findItems {
+       my $api = shift;
+       my $obj = shift;
+       my $s = shift;
+       my $mode = shift;
+       my %visited = ();
+       my @fields = ();
+
+       #print Dumper($s);
+
+       my @all = @{$s->{items}};
+       my %index;
+
+       foreach my $m (@all) {
+               $index{$m->{name}} = $m;
+       }
+
+       #print "find mode $mode in $s->{name} $s->{type}\n";
+       #print Dumper($s);
+
+       foreach my $inc (grep { $_->{mode} eq $mode } @{$obj->{items}}) {
+               my $d = $index{$inc->{match}};
+
+               if ($d) {
+                       next if $visited{$d->{type}.':'.$d->{name}}++;
+                       push @fields, [ $inc, $d ];
+               } else {
+                       foreach my $d (grep { $_->{name} =~ m/$inc->{regex}/ } @all) {
+                               next if $visited{$d->{type}.':'.$d->{name}}++;
+                               push @fields, [ $inc, $d ];
+                       }
+               }
+       }
+
+       # FIXME: rather than all, use 'struct:<default>' options for fallback
+       if (optionValue('default', 'all', $obj) eq 'all') {
+               foreach my $d (@all) {
+                       next if $visited{$d->{type}.':'.$d->{name}}++;
+                       # TODO: other things might need moving over
+                       my $inc = {};
+                       $inc->{"$mode:rename"} = $obj->{"$mode:rename"} if $obj->{"$mode:rename"};
+                       push @fields, [ $inc, $d ];
+               }
+       }
+
+       return @fields;
+}
+
+sub findAllItems {
+       my $api = shift;
+       my $obj = shift;
+       my $s = shift;
+       my %visited = ();
+       my @fields = ();
+
+       #print Dumper($obj);
+
+       my @all = @{$s->{items}};
+       my %index;
+
+       foreach my $m (@all) {
+               $index{$m->{name}} = $m;
+       }
+
+       foreach my $inc (@{$obj->{items}}) {
+               my $d = $index{$inc->{match}};
+
+               if ($d) {
+                       next if $visited{$d->{type}.':'.$d->{name}}++;
+                       push @fields, [ $inc, $d ];
+               } else {
+                       foreach my $d (grep { $_->{name} =~ m/$inc->{regex}/ } @all) {
+                               next if $visited{$d->{type}.':'.$d->{name}}++;
+                               push @fields, [ $inc, $d ];
+                       }
+               }
+       }
+
+       if (optionValue('default', 'all', $obj) eq 'all') {
+               #print "* add all items\n";
+               foreach my $d (@all) {
+                       next if $visited{$d->{type}.':'.$d->{name}}++;
+                       push @fields, [ $obj, $d ];
+               }
+       }
+
+       return @fields;
+}
+
+# ######################################################################
+
+sub addDependencies {
+       my $api = shift;
+       my $obj = shift;
+       my $s = shift;
+       my $add = shift;
+
+       #print "add deps for '$s->{name}'\n";
+       if ($s->{type} =~ m/^(struct|union)$/) {
+               # include embedded structures always
+               foreach my $d (grep { $_->{type} =~ m/^(struct|union):/ && $_->{deref} =~ m/\[\d+\$\{|^\$\{/ } @{$s->{items}}) {
+                       #print "  embedded $d->{name} $d->{deref}\n";
+                       $add->($d->{type});
+               }
+
+               # include selected fields optionally
+               if ($obj) {
+                       foreach my $i (findAllItems($api, $obj, $s)) {
+                               my ($inc, $d) = @{$i};
+                               #print "  selected $d->{name} $d->{type} $d->{deref}\n";
+                               $add->($d->{type}) if ($d->{type} =~ m/^(struct|union|func|call|enum):/);
+                       }
+               }
+       } elsif ($s->{type} =~ m/^(call|func)/) {
+               # for calls/func need all fields
+               foreach my $d (grep { $_->{type} =~ m/^(struct|union|func|call|enum):/ } @{$s->{items}}, $s->{result}) {
+                       #print "  argument $d->{name} $d->{type} $d->{deref}\n";
+                       $add->($d->{type});
+               }
+       }
+}
+
+# find all extra types used by the api requested
+sub findDependencies {
+       my $api = shift;
+       my %data = %{$api->{data}};
+       my %seen;
+       my %deps;
+       my $setdeps = sub { my $d = shift; $deps{$d} = 1; };
+
+       foreach my $obj (@{$api->{api}}) {
+               if ($obj->{type} eq 'library') {
+                       foreach my $inc (@{$obj->{items}}) {
+                               print "? $inc->{regex}\n";
+                               foreach my $n (grep { $_ =~ m/$inc->{regex}/ } keys %data) {
+                                       my $s = $data{$n};
+
+                                       print "+ $n\n";
+
+                                       $seen{$n}++;
+                                       addDependencies($api, $obj, $s, $setdeps);
+                               }
+                       }
+               } elsif ($obj->{type} =~ m/^(struct|union|call|func|enum|define)$/) {
+                       foreach my $n (grep { $_ =~ m/$obj->{regex}/ } keys %data) {
+                               my $s = $data{$n};
+
+                               $seen{$n}++;
+                               addDependencies($api, $obj, $s, $setdeps);
+                       }
+               }
+       }
+
+       # at this point 'seen' contains everything explicitly requested
+       # and deps is anything else they need but not referenced directly
+       # recursively grab anything else
+
+       my @list = ();
+       my @stack = sort keys %deps;
+       my $pushstack = sub { my $d = shift; push @stack, $d; };
+       while ($#stack >= 0) {
+               my $n = shift @stack;
+               my $s;
+
+               next if $seen{$n}++;
+
+               $s = $data{$n};
+
+               if ($s) {
+                       print "Add referent: $n\n";
+                       addDependencies($api, $api->{index}->{"$s->{type}:<default>"}, $s, $pushstack);
+               } elsif ($n =~ m/^(.*):(.*)$/) {
+                       print "Add anonymous: $n\n";
+                       # type not know, add anonymous
+                       $s = {
+                               name => $2,
+                               type => $1,
+                               size => 0,
+                               items => [],
+                       };
+                       $api->{data}->{$n} = $s;
+               }
+
+               # maybe it should have some skeleton metadata?
+               # depends on where it's used i suppose
+               push @list, $s;
+       }
+
+       print "Added ".($#list+1)." dependencies\n";
+       return @list;
+}
+
+# ######################################################################
+
+sub loadAPIFile {
+       my $file = shift;
+       my $info;
+
+       unless ($info = do $file) {
+               die "couldn't parse $file: $@"  if $@;
+               die "couldn't import $file: $!" unless defined $info;
+               die "couldn't run $file"                unless $info;
+       }
+
+       return $info;
+}
+
+sub parseRename {
+       my $how = shift;
+       my $rename = $renameTable{'identity'};
+
+       foreach my $n (split /,/,$how) {
+               my $old = $rename;
+               my $new = $renameTable{$n};
+
+               if ($n =~ m@^s/(.*)/(.*)/$@) {
+                       my $rx = qr/$1/;
+                       my $rp = $2;
+                       $rename = sub { my $s=shift; $s = $old->($s); $s =~ s/$rx/$rp/; return $s;};
+               } elsif ($new) {
+                       $rename = sub { my $s=shift; $s = $old->($s); return $new->($s); };
+               } else {
+                       my $x = $n;
+                       $rename = sub { return $x; };
+               }
+       }
+       $rename;
+}
+
+# take a signature-name and convert it to mangled java name
+sub renameCall {
+       my $new = shift;
+
+       if ($new =~ m/\(/) {
+               $new =~ s/u32:|u64:/p/g;
+               $new =~ s/\$\{([^\}]+)\}/$1/g;
+               $new =~ s/[\(\)]/_/g;
+               $new =~ s/^/Call/;
+       }
+       $new;
+}
+
+# pre-process {data}
+sub preprocess {
+       my $api = shift;
+
+       # Find any anonymous types and add them in
+       my %anonymous = ();
+       foreach my $s (values %{$api->{data}}) {
+               # FIXME: just do this in export.cc and export-defines
+               $s->{items} = $s->{fields} if $s->{fields};
+               $s->{items} = $s->{arguments} if $s->{arguments};
+               $s->{items} = $s->{values} if $s->{values};
+
+               foreach my $m (grep { $_->{type} =~ m/struct:|union:/} @{$s->{items}}) {
+                       $anonymous{$m->{type}} = 1 if !defined($api->{data}->{$m->{type}});
+               }
+       }
+
+       foreach my $k (sort keys %anonymous) {
+               print " anon $k\n";
+               if ($k =~ m/^(.*):(.*)$/) {
+                       $api->{data}->{$k} = {
+                               name => $2,
+                               type => $k,
+                               size => 0,
+                               items => [],
+                       };
+               }
+       }
+
+       my $rename = {};
+       foreach my $i (findObjects($api, 'struct')) {
+               my ($obj, $dat) = @{$i};
+
+               if ($obj->{rename}) {
+                       my $old = $dat->{name};
+                       my $new = $obj->{rename}->($old);
+                       my $oldkey = $obj->{type}.':'.$old;
+                       my $newkey = $obj->{type}.':'.$new;
+
+                       $rename->{$oldkey} = $newkey;
+               }
+       }
+
+       $api->{rename} = $rename;
+}
+
+# preprocess {api}
+sub analyseAPI {
+       my $api = shift;
+
+       # Note that type:name regexes always start at the beginning
+
+       foreach my $obj (@{$api->{api}}) {
+               if ($obj->{name} =~ m@^/(.*)/$@) {
+                       $obj->{regex} = qr/^$obj->{type}:$1/;
+               } else {
+                       $obj->{regex} = qr/^$obj->{type}:$obj->{name}$/;
+               }
+
+               foreach my $opt (@{$obj->{options}}) {
+                       if ($opt =~ m/^(.*:?rename)=(.*)$/) {
+                               $obj->{$1} = parseRename($2);
+                       }
+               }
+
+               my $defmode = ($obj->{type} eq 'library' ? 'func' : 'field');
+               foreach my $inc (@{$obj->{items}}) {
+                       my $match = $inc->{match};
+                       my $mode = $defmode;
+
+                       if ($inc->{match} =~ m/^(.*):(.*)$/) {
+                               $match = $2;
+                               $mode = $1;
+                       }
+
+                       $inc->{mode} = $mode;
+                       if ($match =~ m@^/(.*)/$@) {
+                               $inc->{regex} = $mode ne 'field' ? qr/$mode:$1/ : qr/$1/;
+                       } else {
+                               $inc->{regex} = $mode ne 'field' ? qr/^$mode:$match$/ : qr/^$match$/;
+                       }
+
+                       foreach my $opt (@{$inc->{options}}) {
+                               if ($opt =~ m/^rename=(.*)$/) {
+                                       #print "option $opt ".Dumper($inc);
+                                       $inc->{"$mode:rename"} = parseRename($1);
+                               }
+                       }
+
+                       $inc->{"$mode:rename"} = $obj->{"$mode:rename"} if (!(defined($inc->{"$mode:rename"})) && defined($obj->{"$mode:rename"}));
+               }
+       }
+}
+
+# transfer info from {api} to {data}
+sub postprocess {
+       my $api = shift;
+       my $seen = {};
+       my %data = %{$api->{data}};
+
+       # apply func stuff
+       foreach my $obj (grep {$_->{type} eq 'func'} @{$api->{api}}) {
+               my @list;
+
+               if ($api->{data}->{$obj->{name}}) {
+                       push @list, ${data}{$obj->{name}};
+               } else {
+                       push @list, map { $data{$_} } grep { $_ =~ m/$obj->{regex}/ } keys %data;
+               }
+
+               foreach my $s (@list) {
+                       my $isconstructor = optionFlag('constructor', $obj);
+                       my $isstatic = optionFlag('static', $obj);
+
+                       $s->{constructor} = $isconstructor if $isconstructor;
+                       $s->{static} = $isstatic if $isstatic;
+                       $s->{rename} = $obj->{"$obj->{type}:rename"} ? $obj->{"$obj->{type}:rename"}->($s->{name}) : $s->{name};
+
+                       foreach my $inc (@{$obj->{items}}) {
+                               my @items;
+
+                               my $array = optionFlag('array', $inc, $obj);
+                               my $arraysize = optionValue('array-size', undef, $inc);
+                               my $instance = optionFlag('instance', $inc);
+                               my $success = optionValue('success', undef, $inc);
+
+                               if ($inc->{match} eq '<result>') {
+                                       push @items, $s->{result};
+                               } else {
+                                       @items = grep { $_->{name} =~ m/$inc->{regex}/ } @{$s->{items}};
+                               }
+
+                               foreach my $m (@items) {
+                                       $m->{array} = $array if $array;
+                                       $m->{arraysize} = $arraysize if defined($arraysize);
+                                       $m->{instance} = $instance if $instance;
+                                       $m->{success} = $success if defined($success);
+                               }
+                       }
+                       print Dumper({func=>$s, obj=>$obj});
+               }
+
+       }
+
+}
+
+1;
diff --git a/src/code.api b/src/code.api
new file mode 100644 (file)
index 0000000..884866a
--- /dev/null
@@ -0,0 +1,339 @@
+# -*- Mode:text; tab-width:4; electric-indent-mode: nil; indent-line-function:insert-tab; -*-
+
+# method template
+# 'invoke' is a simple string template
+# variables are defined by method.pm
+code method {
+       # normal function invocation
+       invoke {{
+       static final MethodHandle {name}$FH = Memory.downcall("{name}", {function-descriptor});
+       public {static}{java-result} {rename}({java-arguments}) {
+               {native-output-define}
+               {native-result-define}
+               try {create-frame}{
+                       {native-output-init}
+                       {native-result-assign}{name}$FH.invokeExact({native-call});
+                       {native-output-copy}
+                       {result-test}{
+                               {java-result-assign}
+                               {on-success}
+                               {java-result-return}
+                       }
+               } catch (Throwable t) {
+                       throw new RuntimeException(t);
+               }
+               {result-throw}
+       }
+       }}
+
+       # callback function/types
+       downcall {{
+       public static Memory.FunctionPointer<{rename}> downcall(MemoryAddress addr$, ResourceScope scope$) {
+               NativeSymbol symbol$ = NativeSymbol.ofAddress("{rename}", addr$, scope$);
+               MethodHandle {rename}$FH = Memory.downcall(symbol$, descriptor());
+               return new Memory.FunctionPointer<{rename}>(
+                       symbol$,
+                       ({java-arguments}) -> {
+                               {native-output-define}
+                               {native-result-define}
+                               try {create-frame}{
+                                       {native-output-init}
+                                       {native-result-assign}{rename}$FH.invokeExact({native-call});
+                                       {native-output-copy}
+                                       {result-test}{
+                                               {java-result-assign}
+                                               {on-success}
+                                               {java-result-return}
+                                       }
+                               } catch (Throwable t) {
+                                       throw new RuntimeException(t);
+                               }
+                       });
+       }
+       }}
+
+       upcall {{
+       public static Memory.FunctionPointer<{rename}> upcall({rename} target$, ResourceScope scope$) {
+               interface Trampoline {
+                       {java-result} call({native-arguments});
+               }
+               Trampoline trampoline = ({native-arguments}) -> {
+                       // frame?  scope?
+                       {trampoline-result-define}target$.call({java-call});
+                       {trampoline-result-return}
+               };
+               return new Memory.FunctionPointer<>(
+                               Memory.upcall(
+                                       trampoline,
+                                       "call",
+                                       "{java-signature}",
+                                       descriptor(),
+                                       scope$),
+                               target$);
+       }
+       }}
+}
+
+
+# structs - normal structs
+# handle  - anonymous structs
+# library - library template
+
+code class {
+       library {{
+package {package};
+import jdk.incubator.foreign.*;
+import java.lang.invoke.*;
+import {package}.Memory.*;
+
+public class {name} {
+{defines}
+{enums}
+{funcs}
+}
+       }}
+       constants {{
+package {package};
+
+public interface {name} {
+{enums}
+{defines}
+}
+       }}
+       struct {{
+package {package};
+import jdk.incubator.foreign.*;
+import jdk.incubator.foreign.MemoryLayout.*;
+import java.lang.invoke.*;
+import {package}.Memory.*;
+
+public class {rename} implements Memory.Addressable {
+
+       public final MemorySegment segment;
+
+       private {rename}(MemorySegment segment) {
+               this.segment = segment;
+       }
+
+       public static {rename} create(MemorySegment segment) {
+               return new {rename}(segment);
+       }
+
+       public static {rename} create(MemoryAddress address, ResourceScope scope) {
+               return MemoryAddress.NULL != address ? create(MemorySegment.ofAddress(address, LAYOUT.byteSize(), scope)) : null;
+       }
+
+       public static {rename} create(SegmentAllocator frame) {
+               return create(frame.allocate(LAYOUT));
+       }
+
+       @Override
+       public final MemoryAddress address() {
+               return segment.address();
+       }
+
+       @Override
+       public final ResourceScope scope() {
+               return segment.scope();
+       }
+
+       public final MemorySegment segment() {
+               return segment;
+       }
+
+{defines}
+{enums}
+{accessors}
+{methods}
+{layout}
+{varhandles}
+}
+}}
+       struct-array {{
+package {package};
+import jdk.incubator.foreign.*;
+import jdk.incubator.foreign.MemoryLayout.*;
+import java.lang.invoke.*;
+import {package}.Memory.*;
+
+public class {rename} implements Memory.Addressable, Memory.Array<{rename}> {
+
+       public final MemorySegment segment;
+
+       private {rename}(MemorySegment segment) {
+               this.segment = segment;
+       }
+
+       public static {rename} create(MemorySegment segment) {
+               return new {rename}(segment);
+       }
+
+       public static {rename} create(MemoryAddress address, ResourceScope scope) {
+               return MemoryAddress.NULL != address ? create(MemorySegment.ofAddress(address, LAYOUT.byteSize(), scope)) : null;
+       }
+
+       public static {rename} create(SegmentAllocator frame) {
+               return create(frame.allocate(LAYOUT));
+       }
+
+       public static {rename} createArray(MemoryAddress address, ResourceScope scope) {
+               return MemoryAddress.NULL != address ? create(MemorySegment.ofAddress(address, Long.MAX_VALUE, scope)) : null;
+       }
+
+       @Override
+       public final MemoryAddress address() {
+               return segment.address();
+       }
+
+       @Override
+       public final ResourceScope scope() {
+               return segment.scope();
+       }
+
+       public final MemorySegment segment() {
+               return segment;
+       }
+
+       @Override
+       public long length() {
+               return segment.byteSize() / LAYOUT.byteSize();
+       }
+
+       @Override
+       public {rename} getAtIndex(long index) {
+               try {
+                       return {rename}.create((MemorySegment){name}$SH.invokeExact(segment, index));
+               } catch (Throwable t) {
+                       throw new RuntimeException(t);
+               }
+       }
+
+{defines}
+{enums}
+{accessors}
+{methods}
+{layout}
+       final static MethodHandle {name}$SH = MemoryLayout.sequenceLayout(LAYOUT).sliceHandle(PathElement.sequenceElement());
+{varhandles}
+}
+}}
+       handle {{
+package {package};
+import jdk.incubator.foreign.*;
+import java.lang.invoke.*;
+import {package}.Memory.*;
+
+public class {rename} implements Memory.Addressable {
+
+       MemoryAddress address;
+       ResourceScope scope;
+
+       private {rename}(MemoryAddress address, ResourceScope scope) {
+               this.address = address;
+               this.scope = scope;
+       }
+
+       public static {rename} create(MemoryAddress address, ResourceScope scope) {
+               return MemoryAddress.NULL != address ? new {rename}(address, scope) : null;
+       }
+
+       @Override
+       public MemoryAddress address() {
+               return address;
+       }
+
+       @Override
+       public ResourceScope scope() {
+               return scope;
+       }
+
+{defines}
+{enums}
+{methods}
+}
+}}
+       call {{
+package {package};
+import jdk.incubator.foreign.*;
+import java.lang.invoke.*;
+import {package}.Memory.*;
+
+@FunctionalInterface
+public interface {rename} {
+       {java-result} call({java-arguments});
+
+       public static FunctionDescriptor descriptor() {
+               return {function-descriptor};
+       }
+
+{upcall}
+{downcall}
+}
+}}
+}
+
+# do I want this to be perl code as template or just a template?
+code getset {
+       get set=value={getnative} {{
+       public {type} get{rename}() {
+               return {tojava};
+       }
+       }}
+       geti set=value={getnative} set=segment=segment {{
+       public {type} get{rename}AtIndex(long index) {
+               // option a: resolve an offset segment and asme as above with set=segment=segment
+               // option b: an indexed varhandle
+               MemorySegment segment = segment().asSlice(index * LAYOUT.byteSize(), LAYOUT.byteSize());
+               return {tojava};
+       }
+       }}
+       set set=value=value {{
+       public void set{rename}({type} value) {
+               {setnative};
+       }
+       }}
+       seti set=value=value set=segment=segment {{
+       public void set{rename}AtIndex(long index, {type} value) {
+               MemorySegment segment = segment().asSlice(index * LAYOUT.byteSize(), LAYOUT.byteSize());
+               {setnative};
+       }
+       }}
+}
+
+code getsetelement {
+       get set=index=i {{
+       public {typei} get{rename}Element(long i) {
+               return {getnativei};
+       }
+       }}
+       set set=index=i set=value=value {{
+       public void set{rename}Element(long i, {typei} value) {
+               {setnativei};
+       }
+       }}
+}
+
+code getsetelement2d {
+       get set=index0=i set=index1=j {{
+       public {typei} get{rename}Element(long i, long j) {
+               return {getnativei};
+       }
+       }}
+       set set=index0=i set=index1=j set=value=value {{
+       public void set{rename}Element(long i, long j, {typei} value) {
+               {setnativei};
+       }
+       }}
+}
+
+code getbyvalue {
+       get {{
+       public {type} get{rename}() {
+               try {
+                       return {getnative};
+               } catch (Throwable t) {
+                       throw new RuntimeException(t);
+               }
+       }
+       }}
+}
diff --git a/src/code.pm b/src/code.pm
new file mode 100644 (file)
index 0000000..156aeee
--- /dev/null
@@ -0,0 +1,294 @@
+package code;
+
+use strict;
+
+use File::Path qw(make_path);
+use File::Basename;
+use Data::Dumper;
+use List::Util qw(first);
+
+require genapi;
+
+my %typeSizes = (
+       i8 => 'byte', u8 => 'byte',
+       i16 => 'short', u16 => 'short',
+       i32 => 'int', u32 => 'int',
+       i64 => 'long', u64 => 'long',
+       f32 => 'float',
+       f64 => 'double',
+);
+
+my %typeSuffix = (
+       'long' => 'L',
+       'float' => 'f'
+);
+
+my %defineType = (
+       %typeSizes,
+       string => 'String'
+);
+
+my %typePrefix = (
+       i8 => '(byte)',
+       u8 => '(byte)',
+       i16 => '(short)',
+       u16 => '(short)',
+       string => '"'
+);
+
+my %typeSuffix = (
+       u64 => 'L',
+       i64 => 'L',
+       f32 => 'f',
+       string => '"'
+);
+
+my %typeSignature = (
+       'byte' => 'B',
+       'short' => 'S',
+       'int' => 'I',
+       'long' => 'J',
+       'float' => 'F',
+       'double' => 'D',
+       'void' => 'V',
+       'MemorySegment' => 'Ljdk/incubator/foreign/MemorySegment;',
+       'MemoryAddress' => 'Ljdk/incubator/foreign/MemoryAddress;',
+);
+
+# creates per-field type info, used by methods and structs
+# the names are bit naff here
+sub scanFields {
+       my $api = shift;
+       my $s = shift;
+       my $data = $api->{data};
+
+       my @members = ();
+       foreach my $m (defined($s->{result}) ? $s->{result} : (), @{$s->{items}}) {
+               my $info = $api->findType($m);
+               my $type = $info->{type};
+               my $match = $info->{match};
+               my $typeSizes = $code::typeSizes;
+
+               foreach my $k (keys %{$type->{items}}) {
+                       my $f = $type->{items}->{$k};
+
+                       if (defined $f) {
+                               my $e = eval $f;
+                               if (!defined $e) {
+                                       print "$typeSizes{$match->{ctype}}\n";
+
+                                       print "$s->{name} field=$m->{name} deref=$m->{deref} regex=$type->{regex} $k=\n$f\n";
+                                       print "match=".Dumper($match);
+                                       print "error: $! $@\n";
+                                       die;
+                               } else {
+                                       #print "$m->{name} $type->{regex} $k = $f = $e\n";
+                               }
+                               $match->{$k} = $e;
+                       }
+               }
+
+               # hmm this should probably be in loop above, but there's clashes with things like {type}
+               foreach my $o (grep { defined $m->{$_} } 'tonative') {
+                       $match->{$o} = findCode($api, $m->{$o});
+               }
+
+               $match->{name} = $m->{name};
+               $match->{rename} = $m->{rename};
+               $match->{segment} = 'segment()';
+               $match->{scope} = 'scope()';
+
+               push @members, { field=>$m, type=>$type, match=>$match };
+       }
+       @members;
+}
+
+sub findCode {
+       my $api = shift;
+       my $v = shift;
+
+       if ($v =~ m/^(code:.+)=(.*)$/) {
+               my $t = $api->{index}->{$1};
+               my $l = genapi::findItem($t, $2);
+
+               die "Uknown template '$1.$2'" if !defined($t) || !defined($l);
+               $l->{literal};
+       } else {
+               $v;
+       }
+}
+
+sub formatFunctionDescriptor {
+       my $api = shift;
+       my $members = shift;
+       my $d;
+
+       if ($members->[0]->{field}->{deref} eq 'void') {
+               shift @$members;
+               $d = "FunctionDescriptor.ofVoid(";
+       } else {
+               $d = "FunctionDescriptor.of(";
+       }
+
+       $d .= join(', ', map { formatTemplate($_->{match}->{layout}, $_->{match}) } @$members);
+       $d .= ')';
+
+       return $d;
+}
+
+sub formatFunctionSignature {
+       my $api = shift;
+       my $members = shift;
+       my $result = shift @$members;
+
+       return '('.join('', map { $typeSignature{$_->{match}->{carrier}} } @$members).')'
+               .$typeSignature{$result->{match}->{carrier}};
+}
+
+sub formatStructLayout {
+       my $api = shift;
+       my $s = shift;
+       my $members = shift;
+       my $count = 0;
+       my $lastOffset = 0;
+       my $maxSize = 8;
+       my $layout;
+
+       $layout = "\tpublic static final GroupLayout LAYOUT = MemoryLayout.$s->{type}Layout(\n\t\t";
+
+       foreach my $i (@$members) {
+               my $m = $i->{field};
+               my $type = $i->{type};
+               my $match = $i->{match};
+
+               $maxSize = bitfieldSize($m) if (bitfieldSize($m) > $maxSize);
+
+               if ($match->{layout}) {
+                       if ($m->{offset} > $lastOffset) {
+                               $layout .= ",\n\t\t" if $count++;
+                               $layout .= 'MemoryLayout.paddingLayout('.($m->{offset} - $lastOffset).')';
+                       }
+                       $layout .= ",\n\t\t" if $count++;
+                       $layout .= formatTemplate($match->{layout}, $match).".withName(\"$m->{name}\")";
+                       $lastOffset = $m->{offset} + $m->{size};
+               }
+       }
+       if ($s->{size} > $lastOffset) {
+               $layout .= ",\n\t\t" if ($count++ > 0);
+               $layout .= 'MemoryLayout.paddingLayout('.($s->{size} - $lastOffset).')';
+       }
+
+       $layout .= "\n\t).withBitAlignment($maxSize);\n";
+       $layout;
+}
+
+sub formatDefine {
+       my $api = shift;
+       my $s = shift;
+
+       my $d = join "\n\t", map {
+               my $type = $_->{type};
+               my $name = $_->{name};
+               my $value = $_->{value};
+
+               "public static final $defineType{$type} $name = $typePrefix{$type}$value$typeSuffix{$type};";
+       } @{$s->{items}};
+
+       return "\t$d\n";
+}
+
+sub formatEnum {
+       my $api = shift;
+       my $s = shift;
+       my $type = $defineType{$s->{value_type}};
+
+       my $d = join "\n\t", "// enum $s->{name}", map {
+               my $name = $_->{name};
+               my $value = $_->{value};
+
+               "public static final $type $name = $typePrefix{$type}$value$typeSuffix{$type};";
+       } @{$s->{items}};
+
+       return "\t$d\n";
+}
+
+sub formatTemplate {
+       my $template = shift;
+       my $vars = shift;
+       my $prefix = shift;
+       my $result;
+
+       #print "apply-template vars=".Dumper($vars);
+
+       while ($template =~ m/^(.*?)\{([\w-]+)\}(.*)$/s) {
+               $result .= $1;
+
+               #print "  $2 -> $vars->{$2}\n";
+
+               if (defined $vars->{$2}) {
+                       $template = $vars->{$2}.$3;
+               } else {
+                       $result .= "{$2}";
+                       $template = $3;
+               }
+       }
+       $result .= $template;
+       $result =~ s/^/$prefix/gm;
+       $result;
+}
+
+# takes a template entry applies options and then formats it
+# applyTemplate(template-item, vars)
+sub applyTemplate {
+       my $code = shift;
+       my $match = shift;
+       my $vars = \%{$match};
+
+       foreach my $set (genapi::optionValuesAll('set', $code)) {
+               $vars->{$1} = $2 if ($set =~ m/^(\w+)=(.*)/);
+       }
+
+       formatTemplate($code->{literal}, $vars);
+}
+
+sub bitfieldSize {
+       my $m = shift;
+       my $size = $m->{size};
+
+       return 64 if ($size > 32);
+       return 32 if ($size > 16);
+       return 16 if ($size > 8);
+       return 8;
+}
+
+sub bitfieldType {
+       my $m = shift;
+       my $size = $m->{size};
+       return 'long' if ($size > 32);
+       return 'int' if ($size > 16);
+       return 'short' if ($size > 8);
+       return 'byte';
+}
+
+sub bitfieldIndex {
+       use integer;
+       my $m = shift;
+
+       $m->{offset} / bitfieldSize($m);
+}
+
+sub bitfieldOffset {
+       use integer;
+       my $m = shift;
+
+       $m->{offset} & (bitfieldSize($m) - 1);
+}
+
+sub bitfieldMask {
+       use integer;
+       my $m = shift;
+
+       sprintf("0x%x", ((1 << $m->{size}) - 1) << bitfieldOffset($m)).$typeSuffix{bitfieldType($m)};
+}
+
+1;
index b32d5e8..1774d21 100755 (executable)
@@ -19,6 +19,11 @@ my $hackformat;
 while (@ARGV) {
        my $cmd = shift;
 
+       if ($cmd =~ m/^(-[^-])(.+)/) {
+               $cmd = $1;
+               unshift @ARGV, $2;
+       }
+
        if ($cmd eq "-t") {
                $package = shift;
     } elsif ($cmd eq "-d") {
@@ -48,6 +53,9 @@ if ($hackformat == 1) {
        my $conf = new genconfig2({ include => \@includes }, $control);
        $defs = $conf->{objects};
        @exports = grep { $_->{type} eq 'define' } @{$defs};
+       foreach $export (@exports) {
+               $export->{options}->[0] = $conf->findInclude($export->{options}->[0]);
+       }
 } else {
        $defs = loadControlFile($control);
        @exports = @{$defs->{define}};
@@ -67,12 +75,7 @@ foreach $export (@exports) {
        foreach $inc (@{$export->{items}}) {
                my @options = @{$inc->{options}};
 
-               if ($inc->{match} =~ m@^/(.*)/$@) {
-                       print "$export->{name} - $inc->{match} - regex $1\n";
-                       $inc->{regex} = qr/$1/;
-               } else {
-                       $inc->{regex} = qr/^$inc->{match}$/;
-               }
+               next if ($inc->{match} =~ m/^(define|enum):/on);
 
                $inc->{mode} = "include";
                foreach $o (@{$inc->{options}}) {
@@ -80,6 +83,16 @@ foreach $export (@exports) {
                                $inc->{mode} = $o;
                        }
                }
+
+               if ($inc->{match} =~ m@^/(.*)/$@) {
+                       print "$export->{name} - $inc->{match} - regex $1\n";
+                       $inc->{regex} = qr/$1/;
+               } elsif ($inc->{mode} =~ m/^file-/) {
+                       $inc->{regex} = qr@/$inc->{match}$|$inc->{match}$@;
+               } else {
+                       $inc->{regex} = qr/^$inc->{match}$/;
+               }
+
                if ($inc->{mode} =~ m/include/) {
                        $includes += 1;
                } else {
@@ -92,13 +105,11 @@ foreach $export (@exports) {
 
        my @options = @{$export->{options}};
 
-       foreach $o (@options) {
-               if ($o =~ m/header=(.*)/) {
-                       $export->{header} = $1;
-               } elsif ($o =~ m/default=(.*)/) {
+       $export->{header} = $options[0];
+
+       foreach $o (@options[1..$#options]) {
+               if ($o =~ m/default=(.*)/) {
                        $export->{default} = $1;
-               } elsif ($#options == 0) {
-                       $export->{header} = $o;
                } else {
                        print STDERR "unknown defines option '$o'\n";
                }
@@ -118,7 +129,7 @@ foreach $export (@exports) {
        my $header = $export->{header};
 
        if (!defined($rawDefines{$header})) {
-               $rawDefines{$header} = scanDefines($header, { CPPFLAGS=>$cppflags, keep_comments=>0 });
+               $rawDefines{$header} = scanDefines($header, { CPPFLAGS=>$CPPFLAGS, keep_comments=>0 });
        }
 
        $export->{rawDefines} = $rawDefines{$header};
@@ -158,12 +169,21 @@ foreach $export (@exports) {
 
 }
 
-open (my $fh, ">", "$output~") || die("can't open $output~ for writing");
-
+# output the .c file
+open (my $fh, ">", "$output-gen.c~") || die("can't open $output-gen,c~ for writing");
 export($fh, \@exports);
-
 close ($fh) || die("error writing");
-rename "$output~",$output || die("error overwriting $output");
+rename "$output-gen.c~","$output-gen.c" || die("error overwriting $output-gen.c");
+
+# compile it
+print "cc -o $output-gen $CPPFLAGS $output-gen.c\n";
+system("cc -o $output-gen $CPPFLAGS $output-gen.c") == 0 || die("error compiling $output-gen.c");
+
+# execute it
+print "$output-gen $output~\n";
+system("$output-gen $output~") == 0 || die("error executing $output-gen");
+print "mv $output~\ $output\n";
+rename "$output~","$output" || die("error overwriting $output");
 
 exit 0;
 
@@ -189,6 +209,7 @@ END
        # this is effectively a function overloading system so that the
        # compiler can select the datatype of the evaluation of the
        # definition.
+       # it should probably be c++
        print $fp <<END;
 /* note unsigned types are output as signed for java compatability */
 /* unsigned long long might not be 64 bit i guess, could use sizeof i suppose */
@@ -262,14 +283,16 @@ sub export_generator {
 # }
 sub scanDefines {
        my $header = shift;
-       my %o = %{shift @_};
+       my $o = shift;
     my $lastc = "";
        my $source;
        my $sourceLine;
 
     print STDERR "Scanning $header\n";
 
-    open (my $in,"-|","cpp -dD ".($o{keep_comments} ? '-CC' : '')." $o{CPPFLAGS} $header") || die("Can't find include file: $header");
+    print STDERR "cpp -dD ".($o->{keep_comments} ? '-CC' : '')." $o->{CPPFLAGS} $header\n";
+
+    open (my $in,"-|","cpp -dD ".($o->{keep_comments} ? '-CC' : '')." $o->{CPPFLAGS} $header") // die("Can't find include file: $header");
     while (<$in>) {
                # line markers
                if (m/^\# (\d+) \"([^\"]*)/) {
@@ -279,7 +302,7 @@ sub scanDefines {
                }
                # accumulate comments
                # single line comments override multi-line
-               if ($o{keep_comments}) {
+               if ($o->{keep_comments}) {
                        if (m@/\*(.*)\*/@) {
                                do {
                                        $lastc = $1 if $lastc eq "";
@@ -364,7 +387,7 @@ sub loadControlFile {
                        };
                        push @{$def{$1}}, $target;
                } elsif (/\S/) {
-                       die("invalid line: %_");
+                       die("invalid line: '$_'");
                }
        }
 
index 611ab3f..ac87eb3 100644 (file)
@@ -73,6 +73,8 @@ extern const char *tree_codes[];
 int plugin_is_GPL_compatible; // must be defined for the plugin to run
 
 static FILE *output_file;
+static char *output_path;      // name
+static char *output_tmp;       // name~
 static int output_enabled = 1;
 
 static int debug_level = 0;
@@ -133,12 +135,24 @@ static void list_clear(struct list *list) {
        struct node *node;
 
        while ((node = list_remhead(list))) {
-               if (debug_level > 2)
+               if (debug_level > 0)
                        fprintf(stderr, " free: %s\n", node->name);
                free(node);
        }
 }
 
+static void todump_add(tree type, const char *name) {
+       struct node *node = node_alloc(type, name);
+
+       list_add(&todump, node);
+}
+
+static void todump_addhead(tree type, const char *name) {
+       struct node *node = node_alloc(type, name);
+
+       list_addhead(&todump, node);
+}
+
 // returns 0 if type has no size (i.e VOID_TYPE)
 static bool is_struct_or_union(const_tree type) {
        return RECORD_TYPE == TREE_CODE(type) || UNION_TYPE == TREE_CODE(type);
@@ -267,7 +281,7 @@ static void export_desc(tree field, tree field_type, struct buffer *b) {
        case RECORD_TYPE:
        case UNION_TYPE:
                if (TYPE_IDENTIFIER(field_type)) {
-                       list_add(&todump, node_alloc(field_type, NULL));
+                       todump_add(field_type, NULL);
 
                        buffer_add(b, "${");
                        buffer_add(b, value_name(field_type));
@@ -275,7 +289,7 @@ static void export_desc(tree field, tree field_type, struct buffer *b) {
                } else {
                        char *name = stack_path(&context_stack, "_");
 
-                       list_add(&todump, node_alloc(field_type, name));
+                       todump_add(field_type, name);
 
                        buffer_add(b, "${");
                        buffer_add(b, name);
@@ -382,7 +396,7 @@ static void export_cdesc(tree field, tree field_type, struct buffer *b) {
        case RECORD_TYPE:
        case UNION_TYPE:
                if (TYPE_IDENTIFIER(field_type)) {
-                       list_add(&todump, node_alloc(field_type, NULL));
+                       todump_add(field_type, NULL);
 
                        buffer_add(b, "${");
                        buffer_add(b, value_name(field_type));
@@ -390,7 +404,7 @@ static void export_cdesc(tree field, tree field_type, struct buffer *b) {
                } else {
                        char *name = stack_path(&context_stack, "_");
 
-                       list_add(&todump, node_alloc(field_type, name));
+                       todump_add(field_type, name);
 
                        buffer_add(b, "${");
                        buffer_add(b, name);
@@ -482,7 +496,7 @@ static void export_param(tree field, tree field_type, size_t field_size) {
                        // note it is added to todump in reverse
                        buffer_init(&b, 256);
                        export_desc(field, field_type, &b);
-                       list_addhead(&todump, node_alloc(field_type, b.data));
+                       todump_addhead(field_type, b.data);
                        generate(" type => 'call:%s', ", b.data);
                        if (debug_level > 0) {
                                fprintf(stderr, "save for later param type %p root type %p '%s'\n", field_type, root_type, b.data);
@@ -495,9 +509,9 @@ static void export_param(tree field, tree field_type, size_t field_size) {
                        }
                }
 
-               buffer_init(&b, 256);
-               export_cdesc(field, field_type, &b);
-               free(b.data);
+               //buffer_init(&b, 256);
+               //export_cdesc(field, field_type, &b);
+               //free(b.data);
 
                break;
        }
@@ -523,7 +537,7 @@ static void export_param(tree field, tree field_type, size_t field_size) {
                } else {
                        char *name = stack_path(&context_stack, "_");
 
-                       list_add(&todump, node_alloc(field_type, name));
+                       todump_add(field_type, name);
                        generate(" type => '%s:%s',", us, name);
                        free(name);
                }
@@ -548,7 +562,12 @@ static void export_params(tree func) {
        if (fwd) {
                // use the forward reference to find the names
                if (debug_level > 0)
-                       fprintf(stderr, "found forward reference @ %p\n", fwd);
+                       fprintf(stderr, "found forward reference @ %p\n", fwd->type);
+
+               // so sometimes at this point the parameters stack has items we should
+               // remove ... but not always.  apart from leaving garbage on the
+               // stack it doesn't seem to upset the output
+
                name = fwd->list.head;
        } else {
                // paramter names are in the paramters list
@@ -654,6 +673,21 @@ static void export_fields(tree first_field, size_t base_offset, int indent) {
        }
 }
 
+// try to find a filename for this node
+// it isn't really good enough for filtering, e.g. enums have no name
+static const char *source_filename(tree type) {
+       if (DECL_P(type)) {
+               expanded_location xloc = expand_location(DECL_SOURCE_LOCATION(type));
+               return xloc.file;
+       } else if (is_struct_or_union(type)) {
+               for (tree field = TYPE_FIELDS(type); field; field = TREE_CHAIN(field)) {
+                       return source_filename((field));
+               }
+       }
+       // shrug
+       return "<unknown>";
+}
+
 /*
   Main entry point for exporting any type.
 */
@@ -662,9 +696,6 @@ static void export_type(tree type, const char *names) {
        tree deftype;
        tree target;
 
-       if (debug_level > 1)
-               fprintf(stderr, "export_type(%s, %s)\n", ZTREE_CODE(type), names);
-
        switch (TREE_CODE(type)) {
        case TYPE_DECL: {
                if (!names) {
@@ -673,15 +704,19 @@ static void export_type(tree type, const char *names) {
                                return;
                        names = IDENTIFIER_POINTER(name);
                }
-               if (hash_lookup(&dumped, names))
+               if (hash_lookup(&dumped, names)) {
+                       if (debug_level > 1)
+                               fprintf(stderr, " - type already output, skipping");
                        return;
+               }
                hash_put(&dumped, node_alloc(type, names));
 
-               if (debug_level > 1)
-                       fprintf(stderr, "export: %s %s\n", names, ZTREE_CODE(type));
-
                deftype = TREE_TYPE(type);
                target = target_type(deftype);
+
+               if (debug_level > 1)
+                       fprintf(stderr, "export: %-16s %s\n", ZTREE_CODE(type), names);
+
                switch (TREE_CODE(target)) {
                case FUNCTION_TYPE: {
                        // function pointer typdef
@@ -764,7 +799,7 @@ static void export_type(tree type, const char *names) {
                hash_put(&dumped, node_alloc(type, names));
 
                if (debug_level > 1)
-                       fprintf(stderr, "export type func decl %s\n", names);
+                       fprintf(stderr, "export: %-16s %s\n", ZTREE_CODE(type), names);
 
                generate("'func:%s' => { name => '%s', type => 'func',", names, names);
 
@@ -809,7 +844,7 @@ static void export_type(tree type, const char *names) {
                hash_put(&dumped, node_alloc(type, names));
 
                if (debug_level > 1)
-                       fprintf(stderr, "export: %s %s\n", names, ZTREE_CODE(type));
+                       fprintf(stderr, "export: %-16s %s\n", ZTREE_CODE(type), names);
 
                generate("'call:%s' => { name => '%s', type => 'call',", names, names);
                generate(" ctype => '%s',", print_generic_expr_to_str(type));
@@ -859,7 +894,7 @@ static void export_type(tree type, const char *names) {
                hash_put(&dumped, node_alloc(type, names));
 
                if (debug_level > 1)
-                       fprintf(stderr, "export: %s %s\n", names, ZTREE_CODE(type));
+                       fprintf(stderr, "export: %-16s %s\n", ZTREE_CODE(type), names);
 
                generate("'%s:%s' => { name => '%s', type => '%s', size => %zu, fields => [\n",
                        su, names, names, su, tree_to_uhwi(TYPE_SIZE(type)));
@@ -931,9 +966,9 @@ static void export_type(tree type, const char *names) {
                target = target_type(deftype);
 
                if (debug_level > 1) {
-                       fprintf(stderr, "field_decl type is '%s\n", ZTREE_CODE(deftype));
-                       fprintf(stderr, "type name is '%s'\n", TYPE_IDENTIFIER(deftype) ? IDENTIFIER_POINTER(TYPE_IDENTIFIER(deftype)) : "<anon>");
-                       fprintf(stderr, "target is '%s'\n", ZTREE_CODE(target));
+                       fprintf(stderr, "field: %-16s %s %s -> %s\n", ZTREE_CODE(deftype), names,
+                               TYPE_IDENTIFIER(deftype) ? IDENTIFIER_POINTER(TYPE_IDENTIFIER(deftype)) : "<anon>",
+                               ZTREE_CODE(target));
                }
 
                // nb almost same as next case below, but we don't save the param info here
@@ -980,7 +1015,7 @@ static void export_type(tree type, const char *names) {
 
                                buffer_init(&b, 256);
                                export_desc(deftype, target, &b);
-                               list_addhead(&todump, node_alloc(target, b.data));
+                               todump_addhead(target, b.data);
 
                                if (debug_level > 0)
                                        fprintf(stderr, "save for later param type %p '%s'\n", target, b.data);
@@ -1050,7 +1085,7 @@ static void export_type(tree type, const char *names) {
 
                                buffer_init(&b, 256);
                                export_desc(deftype, target, &b);
-                               list_addhead(&todump, node_alloc(target, b.data));
+                               todump_addhead(target, b.data);
 
                                if (debug_level > 0)
                                        fprintf(stderr, "save for later param type %p '%s'\n", target, b.data);
@@ -1061,7 +1096,7 @@ static void export_type(tree type, const char *names) {
                }
 
                if (debug_level > 0)
-                       fprintf(stderr, "(push parameter '%s')\n", names);
+                       fprintf(stderr, "(push parameter '%s' %s)\n", names, source_filename(type));
                stack_push(&parameters, node_alloc(type, names));
 
                break; }
@@ -1082,6 +1117,8 @@ static void plugin_finish_type(void *event_data, void *user_data) {
                fprintf(stderr, "finish_type\n");
        if (debug_level > 1)
                debug_tree(type);
+       if (debug_level > 2)
+               dump_node(type, TDF_ALL_VALUES, stderr);
 
        export_type(type, NULL);
 }
@@ -1093,6 +1130,8 @@ static void plugin_finish_decl(void *event_data, void *user_data) {
                fprintf(stderr, "finish_decl %s\n", ZTREE_CODE(type));
        if (debug_level > 1)
                debug_tree(type);
+       if (debug_level > 2)
+               dump_node(type, TDF_ALL_VALUES, stderr);
 
        export_type(type, NULL);
 }
@@ -1116,11 +1155,16 @@ static void plugin_finish(void *event_data, void *user_data) {
                generate("# %s\n", n->name);
 
        generate("}\n");
-       fclose(output_file);
 
        if (debug_level > 0)
                fprintf(stderr, "unhandled paramters:\n");
        list_clear(&parameters);
+
+       if (fclose(output_file) != 0
+               || rename(output_tmp, output_path) != 0) {
+               perror(output_tmp);
+               exit(EXIT_FAILURE);
+       }
 }
 
 int plugin_init(struct plugin_name_args *plugin_info, struct plugin_gcc_version *version) {
@@ -1139,7 +1183,11 @@ int plugin_init(struct plugin_name_args *plugin_info, struct plugin_gcc_version
                exit(EXIT_FAILURE);
        }
 
-       output_file = fopen(output, "w");
+       output_path = xstrdup(output);
+       output_tmp = (char *)xmalloc(strlen(output)+2);
+       strcpy(stpcpy(output_tmp, output), "~");
+
+       output_file = fopen(output_tmp, "w");
        if (NULL == output_file) {
                perror(output);
                exit(EXIT_FAILURE);
@@ -1147,6 +1195,7 @@ int plugin_init(struct plugin_name_args *plugin_info, struct plugin_gcc_version
 
        generate("{\n");
 
+       //register_callback(plugin_info->base_name, PLUGIN_INCLUDE_FILE, plugin_include_file, NULL);
        register_callback(plugin_info->base_name, PLUGIN_FINISH_DECL, plugin_finish_decl, NULL);
        register_callback(plugin_info->base_name, PLUGIN_FINISH_TYPE, plugin_finish_type, NULL);
        register_callback(plugin_info->base_name, PLUGIN_FINISH, plugin_finish, NULL);
diff --git a/src/generate-api-2 b/src/generate-api-2
new file mode 100755 (executable)
index 0000000..df765b2
--- /dev/null
@@ -0,0 +1,390 @@
+#!/usr/bin/perl
+
+# usage
+#  -t package    target package
+#  -d directory  output root
+#  -v            verbose
+#  -a datafile   add datafile to the dataset, can be from export.so or export-defines, etc
+
+use Data::Dumper;
+
+use File::Path qw(make_path);
+use File::Basename;
+
+use strict;
+
+use Carp 'verbose';
+$SIG{ __DIE__ } = sub { Carp::confess( @_ ) };
+
+my $scriptPath = dirname(__FILE__);
+push @INC,$scriptPath;
+
+require genapi;
+require code;
+require method;
+
+$Data::Dumper::Indent = 1;
+
+my $apidef = "api.api";
+my $vars = {
+       package => '',
+       output => 'bin',
+       verbose => 0,
+};
+my @apilist;
+
+while (@ARGV) {
+       my $cmd = shift;
+
+       if ($cmd =~ m/^(-[^-])(.+)/) {
+               $cmd = $1;
+               unshift @ARGV, $2;
+       }
+
+       if ($cmd eq "-t") {
+               $vars->{package} = shift;
+    } elsif ($cmd eq "-d") {
+               $vars->{output} = shift;
+    } elsif ($cmd eq "-I") {
+               push @{$vars->{include}}, shift;
+    } elsif ($cmd eq "-v") {
+               $vars->{verbose}++;
+    } elsif ($cmd eq "-a") {
+               push @apilist, shift;
+       } else {
+               $apidef = $cmd;
+       }
+}
+
+push @{$vars->{include}}, $scriptPath;
+
+print Dumper($vars) if $vars->{verbose};
+
+my $api = new genapi($apidef, $vars, @apilist);
+
+#print Dumper($api);
+
+# TODO: make a module for this.
+copySkeletonFile($api, 'Memory.java');
+copySkeletonFile($api, 'Frame.java');
+
+exportLibraries($api);
+exportStructs($api);
+exportConstants($api);
+exit 0;
+
+# copies a skeleton file and patches it to the target package
+sub copySkeletonFile {
+       my $api = shift;
+       my $name = shift;
+       my $src = "$scriptPath/template/$name";
+       my $dst = $api->{vars}->{package};
+
+       $dst =~ s@\.@/@g;
+       $dst = "$api->{vars}->{output}/$dst/$name";
+
+       make_path(dirname($dst));
+
+       open (my $d, ">", $dst.'~') || die ("Cannot open '$src' for writing");
+       open (my $s, "<", $src) || die ("Cannot open '$dst' for reading");
+
+       while (<$s>) {
+               s/^package .*;/package $api->{vars}->{package};/o;
+               print $d $_;
+       }
+
+       close $s;
+       close $d;
+
+       rename ($dst.'~', $dst) || die ("unable to rename $d: $!");
+}
+
+sub formatFunction {
+       my $api = shift;
+       my $c = shift;
+       my $template = $api->{index}->{'code:method'};
+       my $invoke = genapi::findItem($template, 'invoke');
+       my $info = new method($api, $c);
+
+       #print 'function='.Dumper($c);
+       #print 'members='.Dumper(\@members);
+       #print 'info='.Dumper($info);
+
+       my $code;
+
+       foreach my $l (split /\n/,Dumper($info->{vars}).Dumper($info->{arguments})) {
+               $code .= "// $l\n";
+       }
+
+       $code .= code::applyTemplate($invoke, $info->{vars});
+       $code =~ s/^\s*\n//osgm;
+
+       return $code;
+}
+
+sub formatCall {
+       my $api = shift;
+       my $c = shift;
+       my $template = $api->{index}->{'code:method'};
+       my $upcall = genapi::findItem($template, 'upcall');
+       my $downcall = genapi::findItem($template, 'downcall');
+       my $info = new method($api, $c);
+
+       #print 'function='.Dumper($c);
+       #print 'members='.Dumper(\@members);
+
+       my $code;
+       foreach my $l (split /\n/,Dumper($info)) {
+               $code .= "//$l\n";
+       }
+
+       $info->{vars}->{downcall} = ($c->{access} =~ m/r/) ? code::applyTemplate($downcall, $info->{vars}) : '';
+       $info->{vars}->{upcall} = ($c->{access} =~ m/w/) ? code::applyTemplate($upcall, $info->{vars}) : '';
+
+       return $code.code::applyTemplate(genapi::findItem($api->{index}->{'code:class'}, 'call'), $info->{vars});
+}
+
+sub formatItems {
+       my $api = shift;
+       my $inc = shift;
+       my $res = shift;
+       my @list;
+
+       @list = grep {
+               $api->{output}->{"$_->{type}:$_->{name}"}++;
+               $res->{seen}->{"$_->{type}:$_->{name}"}++ == 0
+       } $api->findMatches($inc);
+
+       if ($inc->{type} eq 'func') {
+               push @{$res->{func}}, map { formatFunction($api, $_) } @list;
+       } elsif ($inc->{type} eq 'define') {
+               push @{$res->{define}}, map { code::formatDefine($api, $_) } @list;
+       } elsif ($inc->{type} eq 'enum') {
+               push @{$res->{enum}}, map { code::formatEnum($api, $_) } @list;
+       } elsif ($inc->{type} eq 'call') {
+               push @{$res->{enum}}, map { formatCall($api, $_) } @list;
+       } else {
+               die;
+       }
+}
+
+sub formatLibrary {
+       my $api = shift;
+       my $obj = shift;
+       my $res = shift;
+       my $data = $api->{data};
+       my $d;
+
+       print "library $obj->{name}\n";
+
+       foreach my $inc (@{$obj->{items}}) {
+               if ($inc->{type} eq 'library') {
+                       $api->{output}->{"$inc->{match}"}++;
+                       formatLibrary($api, $api->{index}->{$inc->{match}}, $res);
+               } elsif ($inc->{type} =~ m/func|call|define|enum/) {
+                       print "  $inc->{match}\n";
+                       formatItems($api, $inc, $res);
+               } elsif ($inc->{type} ne 'field') {
+                       die;
+               }
+       }
+}
+
+sub findTemplate {
+       my $api = shift;
+       my $obj = shift;
+       my $s = shift;
+       my $data = $api->{data};
+       my $def = $api->{index}->{"$s->{type}:<default>"};
+       my $tmp = genapi::optionValue('template', $s->{size} == 0 ? 'code:class=handle' : 'code:class=struct', $obj, $def);
+
+       if ($tmp =~ m/^(.+)=(.+)$/) {
+               return genapi::findItem($api->{index}->{$1}, $2);
+       }
+       die;
+
+}
+
+# TODO: embedded structs
+sub formatStruct {
+       my $api = shift;
+       my $obj = shift;
+       my $s = shift;
+       my $data = $api->{data};
+       my $seen = {};
+       my $structTemplate = findTemplate($api, $obj, $s);
+
+       my @members = code::scanFields($api, $s);
+       my @membersOutput = grep { $_->{field}->{output} } @members;
+
+       my $varhandles = join '', map { code::formatTemplate($_->{match}->{varhandle}, $_->{match}, "\t") } @membersOutput;
+       my $accessors = join "\n", map {
+               my ($m, $type, $match) = ($_->{field}, $_->{type}, $_->{match});
+               map {
+                       my $accessor = $api->{index}->{$_};
+
+                       map { code::applyTemplate($_, $match) } grep { $_ } map { genapi::findItem($accessor, $_) } map {
+                               my @list = ();
+                               if ($_ =~ m/r/o) {
+                                       push @list, 'get';
+                                       push @list, 'geti' if $_ =~ m/i/o;
+                               }
+                               if ($_ =~ m/w/o) {
+                                       push @list, 'set';
+                                       push @list, 'seti' if $_ =~ m/i/o;
+                               }
+                               @list;
+                       } $m->{access};
+               } split(/,/,genapi::optionValue('template', undef, $type));
+       } @membersOutput;
+
+       my $res = {
+               library => [],
+               func => [],
+               define => [],
+               enum => [],
+               call => [],
+               seen => {}
+       };
+
+       formatLibrary($api, $obj, $res);
+
+       my $vars = {
+               %{$api->{vars}},
+               layout => code::formatStructLayout($api, $s, \@members),
+               rename => $s->{rename},
+               name => $s->{name},
+               varhandles => $varhandles,
+               accessors => $accessors,
+               methods => join("\n", @{$res->{func}}),
+               defines => join("\n", @{$res->{define}}),
+               enums => join("\n", @{$res->{enum}}),
+       };
+
+       my $code;
+       foreach my $l (split /\n/,Dumper($s, \@members)) {
+               $code .= "//$l\n";
+       }
+
+       $api->{output}->{"$s->{type}:$s->{name}"} = 1;
+
+       return $code.code::applyTemplate($structTemplate, $vars);
+}
+
+# output all libraries
+sub exportLibraries {
+       my $api = shift;
+       my $data = $api->{data};
+       my $template = $api->{index}->{'code:class'};
+       my $library = genapi::findItem($template, 'library');
+
+       foreach my $obj (grep { $_->{type} eq 'library' } @{$api->{api}}) {
+               next if $api->{output}->{"$obj->{type}:$obj->{name}"};
+               next if $obj->{output} != 1;
+
+               my $res = {
+                       library => [],
+                       func => [],
+                       define => [],
+                       enum => [],
+                       call => [],
+                       seen => {}
+               };
+
+               formatLibrary($api, $obj, $res);
+
+               my $vars = {
+                       %{$api->{vars}},
+                       name => $obj->{name},
+                       defines => join("\n", @{$res->{define}}),
+                       enums => join("\n", @{$res->{enum}}),
+                       funcs => join("\n", @{$res->{func}}),
+                       calls => join("\n", @{$res->{call}}),
+               };
+
+               export($api, $obj->{name}, code::applyTemplate($library, $vars));
+       }
+}
+
+sub export {
+       my $api = shift;
+       my $name = shift;
+       my $text = shift;
+
+       my $f = $api->openOutput($name);
+       print $f $text;
+       $api->closeOutput($name, $f);
+}
+
+sub formatClass {
+       my $api = shift;
+       my $obj = shift;
+       my $s = shift;
+
+       if ($s->{type} =~ m/struct|union/) {
+               return formatStruct($api, $obj, $s);
+       } elsif ($s->{type} eq 'call') {
+               return formatCall($api, $s);
+       } else {
+               die;
+       }
+}
+
+sub exportStructs {
+       my $api = shift;
+       my $data = $api->{data};
+
+       # first those directly referenced
+       foreach my $obj (grep { $_->{type} =~ m/call|struct|union/ } @{$api->{api}}) {
+               my @list = $api->findMatches($obj);
+
+               print "gen ".($#list+1)." $obj->{type} $obj->{name}\n";
+               foreach my $s (@list) {
+                       next if $api->{output}->{"$s->{type}:$s->{name}"};
+                       print "  $s->{name}\n";
+
+                       export($api, $s->{rename}, formatClass($api, $obj, $s));
+               }
+       }
+
+       # then anything else left using the default outputs
+       foreach my $s (grep { $_->{output} && ($_->{type} =~ m/struct|union|call/) && !$api->{output}->{"$_->{type}:$_->{name}"} } @{$api->{api}}) {
+               my $obj = $data->{"$s->{type}:<default>"};
+               export($api, $s->{rename}, formatClass($api, $obj, $s));
+       }
+}
+
+# exports any define/enum not yet included elsehwere
+# TODO: this is sort of not very useful
+sub exportConstants {
+       my $api = shift;
+       my $data = $api->{data};
+       my $template = $api->{index}->{'code:class'};
+       my $constant = genapi::findItem($template, 'constants');
+
+       # hmm, not sure if i should use obj or just the values directly here
+       foreach my $s (grep { $_->{type} =~ m/define|enum/ } values %{$api->{data}}) {
+               next if $api->{output}->{"$s->{type}:$s->{name}"};
+               next if !$s->{output};
+
+               my $defines = '';
+               my $enums = '';
+
+               $defines = code::formatDefine($api, $s) if $s->{type} eq 'define';
+               $enums = code::formatEnum($api, $s) if $s->{type} eq 'enum';
+
+               my $vars = {
+                       %{$api->{vars}},
+                       name => $s->{name},
+                       defines => $defines,
+                       enums => $enums,
+               };
+
+               my $code;
+               foreach my $l (split /\n/,Dumper($s)) {
+                       $code .= "//$l\n";
+               }
+
+               export($api, $s->{name}, $code.code::applyTemplate($constant, $vars));
+       }
+}
diff --git a/src/method.pm b/src/method.pm
new file mode 100644 (file)
index 0000000..490a743
--- /dev/null
@@ -0,0 +1,156 @@
+
+package method;
+
+use strict;
+
+require code;
+
+sub fieldScope {
+       my $m = shift;
+
+       if ($m->{scope} =~ m/^explicit/) {
+               'scope$';
+       } elsif ($m->{scope} eq 'instance') {
+               'scope()';
+       } else {
+               'ResourceScope.globalScope()';
+       }
+}
+sub fieldScopeAction {
+       my $m = shift;
+
+       if ($m->{field}->{scope} =~ m/^explicit,(.*)$/) {
+               return code::formatTemplate("scope\$.addCloseAction(() -> $1", { %{$m->{match}}, value => 'res$' });
+       } elsif ($m->{field}->{scope} =~ m/^explicit$/) {
+               'scope$.addCloseAction(() -> res$.close());';
+       } else {
+               ();
+       }
+}
+sub apply {
+       my $t = shift;
+       my $m = shift;
+       return code::formatTemplate($t, $m->{match});
+}
+
+sub new {
+       my $class = shift;
+       my $api = shift;
+       my $c = shift;
+       my @members = code::scanFields($api, $c);
+       my $result = shift @members;
+
+       my $self = {
+               result => $result,
+               arguments => \@members,
+               vars => \%{$api->{vars}},
+               method => $c,
+       };
+       my $info = $self->{vars};
+
+       $info->{name} = $c->{name};
+       $info->{rename} = $c->{rename};
+
+       my @list =  map { apply('{type} {name}', $_) } grep { $_->{field}->{output} } @members;
+       push @list, 'ResourceScope scope$' if ($c->{scope} =~ m/explicit/);
+       $info->{'java-arguments'} = join ', ', @list;
+       $info->{'native-arguments'} = join ', ', map { apply('{carrier} {name}', $_) } @members;
+
+       # for native downcalls
+       $info->{'native-result-define'} = ($result->{match}->{type} ne 'void') ? apply('{carrier} result$;', $result)    : '';
+       $info->{'native-result-assign'} = ($result->{match}->{type} ne 'void') ? apply('result$ = ({carrier})', $result) : '';
+       $info->{'native-call'} = join ', ', map {
+               my $m = $_->{field};
+
+               if ($m->{instance}) {
+                       "(jdk.incubator.foreign.Addressable)address()";
+               } else {
+                       my $name = $_->{match}->{name};
+
+                       if ($m->{output} == 0 && ($m->{deref} =~ m/^u64:/)) {
+                               $name .= '$h';
+                       } elsif ($m->{output} == 0 && $m->{implied}) {
+                               $name = $m->{implied};
+                       } elsif (defined($m->{'array-size-source'})) {
+                               # or some function?
+                               $name = "Memory.size($m->{'array-size-source'}->{name})";
+                       }
+                       code::formatTemplate("{tonative}", { %{$_->{match}}, value=>$name })
+               }
+       } @members;
+
+       # for java upcalls
+       $info->{'java-call'} = join ', ', map { code::formatTemplate('{tojava}', { %{$_->{match}}, value=>'{name}' }) } grep { $_->{field}->{output} } @members;
+
+       $info->{'java-signature'} = code::formatFunctionSignature($api, [$result, @members]);
+       $info->{'function-descriptor'} = code::formatFunctionDescriptor($api, [$result, @members]);
+
+       # hidden output arguments
+       # TODO: what about in/out arguments?  they just fall out by not specifying them as scoped?
+       my @output = grep {     $_->{field}->{output} == 0 && ($_->{field}->{deref} =~ m/^u64:/) && $_->{field}->{instance} == 0} @members;
+
+       $info->{'native-output-define'} = join "\n\t\t",        map { apply('{carrieri} {name};', $_)                                                           } @output;
+       $info->{'native-output-init'} = join ";\n\t\t\t",       map { apply('{type} {name}$h = {type}.createArray(1, frame$);', $_)     } @output;
+       $info->{'native-output-copy'} = join ";\n\t\t\t",       map { apply('{name} = {name}$h.get(0);', $_)                                            } @output;
+
+       # also required for some tonative types?
+       my $x =  grep { $_->{match}->{type} eq 'String' } @members;
+       $info->{'create-frame'} = ($#output >= 0 || grep { $_->{match}->{type} eq 'String' } @members) ? '(Frame frame$ = Memory.createFrame()) ' : '';
+
+       # result code handling
+       if ($c->{success}) {
+               $info->{'result-code'} = $c->{success}->{name};
+
+               # success test
+               if ($c->{success}->{success} eq '!null') {
+                       $info->{'result-test'} = "if ($c->{success}->{name} != MemoryAddress.NULL) ";
+                       $info->{'result-throw'} = 'throw new NullPointerException();';
+               } else {
+                       $info->{'result-test'} = 'if ('.join('||', map { "$c->{success}->{name} == $_" } split(/,/,$c->{success}->{success})).') ';
+                       $info->{'result-throw'} = 'throw new RuntimeException("error="+'.$c->{success}->{name}.');';
+               }
+       } else {
+               $info->{'result-test'} = '';
+               $info->{'result-throw'} = '';
+       }
+
+       # success actions
+       my @onsuccess = ();
+
+       push @onsuccess, code::findCode($api, $c->{onsuccess}) if defined($c->{onsuccess});
+
+       if (defined($c->{return})) {
+               # nb: this is only used for output parameters
+               my $res = (grep { $_->{field} == $c->{return} } $result, @members)[0];
+
+               $info->{'java-result'} = $res->{match}->{typei};
+               push @onsuccess, fieldScopeAction($res);
+               #push @onsuccess, code::formatTemplate('return {tojavai};', { %{$res->{match}}, value => $res->{field}->{name}, scope=>fieldScope($res->{field}) });
+
+               $info->{'java-result-assign'} = code::formatTemplate('{typei} res$ = {tojavai};', { %{$res->{match}}, value => $res->{field}->{name}, scope=>fieldScope($res->{field}) });
+               $info->{'java-result-return'} = 'return res$;';
+               $info->{'trampoline-result-define'} = 'error';
+               $info->{'trampoline-result-return'} = 'error';
+       } elsif ($result->{field}->{output} && $result->{match}->{type} ne 'void') {
+               $info->{'java-result'} = $result->{match}->{type};
+               push @onsuccess, fieldScopeAction($result);
+
+               $info->{'java-result-assign'} = code::formatTemplate('{type} res$ = {tojava};', { %{$result->{match}}, value => 'result$', scope=>fieldScope($result->{field}) });
+               $info->{'java-result-return'} = 'return res$;';
+
+               $info->{'trampoline-result-define'} = apply('{type} res$ = ', $result);
+               $info->{'trampoline-result-return'} = code::formatTemplate('return {tonative};', { %{$result->{match}}, value => 'res$' });
+       } else {
+               $info->{'java-result'} = 'void';
+               $info->{'java-result-assign'} = '';
+               $info->{'java-result-return'} = 'return;';
+               $info->{'trampoline-result-define'} = '';
+               $info->{'trampoline-result-return'} = '';
+       }
+       $info->{'on-success'} = join("\n\t\t\t\t", @onsuccess);
+
+       $info->{'static'} = $c->{static} ? 'static ' : '';
+
+       bless $self, $class;
+       $self;
+}
diff --git a/src/tokenise.pm b/src/tokenise.pm
new file mode 100644 (file)
index 0000000..cddf789
--- /dev/null
@@ -0,0 +1,135 @@
+
+#
+# simple tokeniser
+#
+# tokens are
+#  'self' : '{' ';' '}'
+#  'token':'\S+'
+#  'literal': '{{.*}}\n'  may span multiple lines but final }}
+#                         must be at end of line
+
+package tokenise;
+
+use Data::Dumper;
+use strict;
+
+sub new {
+       my $class = shift;
+       my $path = shift;
+       my $self = {
+               state => 0,
+               line => [],
+               colno => 0,
+               type => 'none',
+               files => [],
+               file => { path=>$path, lineno=>0, lines=>[] }
+       };
+
+       open (my $in,"<",$path) || die("unable to open '$path': $@");
+       @{$self->{file}->{lines}} = <$in>;
+       close $in;
+
+       bless $self, $class;
+       return $self;
+}
+
+# include a file at the current point
+sub include {
+       my $self = shift;
+       my $path = shift;
+
+       print "include '$path'\n";
+
+       push @{$self->{files}}, $self->{file};
+
+       $self->{file} = { path=>$path, lineno=>0, lines=>[] };
+
+       open (my $in,"<",$path) || die("unable to open '$path': $@");
+       @{$self->{file}->{lines}} = <$in>;
+       close $in;
+
+       $self->{state} = 0;
+       # should it swallow the rest of {line}?
+}
+
+sub next {
+       my $self = shift;
+       my $token;
+
+       #print "next $self->{state}  $#{$self->{lines}} $#{$self->{line}}\n";
+       # on entry state is in [0, 3, 4]
+       while ($#{$self->{file}->{lines}} >= 0 || $#{$self->{line}} >= 0 || $#{$self->{files}} >= 0) {
+               if ($#{$self->{line}} < 0) {
+                       if ($#{$self->{file}->{lines}} < 0) {
+                               $self->{file} = pop @{$self->{files}};
+                       }
+                       my $t = shift @{$self->{file}->{lines}};
+                       $t =~ s/(?:^\#.*|\s+\#.*)//;
+                       push @{$self->{line}}, split //,$t;
+                       $self->{file}->{lineno} += 1;
+                       $self->{colno} = 0;
+               }
+               my $c = shift @{$self->{line}};
+
+               $self->{colno} += 1;
+
+               #printf("$self->{state} '$c'=\$%02x\n", ord($c));
+
+               if ($self->{state} == 0) {
+                       if (($c =~ m/\s/)) {
+                               next;
+                       } elsif ($c =~ m/[;\}]/) {
+                               $self->{type} = $c;
+                               #print "char: $c\n";
+                               return $c;
+                       } elsif ($c eq '{') {
+                               if ($#{$self->{line}} >= 0 && $self->{line}->[0] eq '{') {
+                                       shift @{$self->{line}};
+                                       $self->{state} = 3;
+                               } else {
+                                       $self->{type} = $c;
+                                       #print "char: $c\n";
+                                       return $c;
+                               }
+                       } elsif ($c) {
+                               $token .= $c;
+                               $self->{state} = 1;
+                       }
+               } elsif ($self->{state} == 1) {
+                       if ($c eq ';') {
+                               unshift @{$self->{line}}, $c;
+                               $self->{state} = 2;
+                               $self->{type} = 'token';
+                               #print "tok: $token\n";
+                               return $token;
+                       } elsif ($c =~ m/\S/) {
+                               $token .= $c;
+                       } else {
+                               $self->{state} = 0;
+                               $self->{type} = 'token';
+                               #print "tok: $token\n";
+                               return $token;
+                       }
+               } elsif ($self->{state} == 2) {
+                       $self->{state} = 0;
+                       #print "char: $c\n";
+                       return $c;
+               } elsif ($self->{state} == 3) {
+                       if ($c eq '}' && $#{$self->{line}} == 1 && $self->{line}->[0] eq '}') {
+                               shift @{$self->{line}};
+                               $self->{state} = 0;
+                               $self->{type} = 'literal';
+                               #print "lit: $token\n";
+                               return $token;
+                       } else {
+                               $token .= $c;
+                       }
+               }
+       }
+
+       die "invalid state=$self->{state}" if $self->{state} != 0;
+
+       return undef;
+}
+
+1;
diff --git a/src/types.api b/src/types.api
new file mode 100644 (file)
index 0000000..094311a
--- /dev/null
@@ -0,0 +1,256 @@
+# -*- Mode:text; tab-width:4; electric-indent-mode: nil; indent-line-function:insert-tab; -*-
+
+#
+# TODO: add a 'mode' to type matching
+#       this woudld allow per-field overrides, e.g. char * field use String or MemorySegment or ByteArray depending on option
+
+# special case for bitfields
+# TODO: well this is bit of a mess, maybe should use <init> like code:method?
+type /bitfield/ copy=<common> {
+       layout;
+       type            {{ bitfieldType($m) }}
+       carrier         {{ "{type}" }}
+       tonative        {{ "({type})({value})" }}
+       tojava          {{ "({type})({value})" }}
+
+       getshiftl       {{ bitfieldSize($m) - $m->{size} - bitfieldOffset($m) }}
+       getshiftr       {{ bitfieldSize($m) - $m->{size} }}
+       getshiftop      {{ ($m->{type} =~ m/^i/) ? ">>" : ">>>" }}
+
+       setmask         {{ bitfieldMask($m) }}
+       setoffset       {{ bitfieldOffset($m) }}
+
+       varindex        {{ bitfieldIndex($m) }}
+
+       getnative       {{
+               "({type})((({type}){name}\$VH.get({segment}) << {getshiftl}) {getshiftop} {getshiftr})"
+       }}
+       setnative       {{
+               "$m->{name}\$VH.set({segment}, ({type})((({type}){name}\$VH.get({segment}) & ~{setmask}) | ((({value}) << {setoffset}) & {setmask})))"
+       }}
+       varhandle       {{ "final static VarHandle {name}\$VH = MemoryLayout.sequenceLayout(Memory.".uc(bitfieldType($m)).").varHandle(MemoryLayout.PathElement.sequenceElement({varindex}));\n" }}
+}
+
+# void, only for return type
+type /void/ {
+       type    {{ 'void' }}
+       carrier {{ 'void' }}
+       getnative;
+       setnative;
+       tonative;
+       tojava;
+}
+
+# 1d array of pointer to pointer
+type /^\[(\d+)u64:u64.*\]$/ {
+}
+
+# 1d array of pointer to primitive
+type /^\[(?<length>\d+)u64:(?<ctype>[uif]\d+)\]$/
+               copy=<pointer> template=code:getbyvalue,code:getsetelement {
+
+       type            {{ !$m->{array} ? 'PointerArray' : 'HandleArray<{typei}>' }}
+       layout          {{ 'MemoryLayout.sequenceLayout({length}, Memory.POINTER)' }}
+       tojava          {{ !$m->{array} ? 'PointerArray.createArray({value}, {length}, {scope})' : 'HandleArray.createArray({value}, {length}, {typei}::create, {scope})' }}
+
+       getsegment      {{ '(MemorySegment){name}$SH.invokeExact({segment})' }}
+       getnative       {{ !$m->{array} ? 'PointerArray.create({getsegment})' : 'HandleArray.create({getsegment}, (a, s) -> {typei}.createArray(a, Long.MAX_VALUE, s))' }}
+       setnative;
+       varhandle       {{
+               'final static MethodHandle {name}$SH = LAYOUT.sliceHandle(MemoryLayout.PathElement.groupElement("{name}"));'."\n".
+               'final static VarHandle {name}$EH = LAYOUT.varHandle(MemoryLayout.PathElement.groupElement("{name}"), MemoryLayout.PathElement.sequenceElement());'."\n"
+       }}
+
+       typei           {{ ucfirst($typeSizes{$match->{ctype}}).'Array' }}
+       getnativei      {{ '{typei}.createArray((MemoryAddress){name}$EH.get({segment}, {index}), Long.MAX_VALUE, {scope})' }}
+       setnativei      {{ '{name}$EH.set({segment}, {index}, (jdk.incubator.foreign.Addressable)Memory.address({value}))' }}
+
+}
+
+# 1d array of pointer to type (struct or union or function?)
+type /^\[(?<length>\d+)u64:\$\{(\w+)\}\]$/ {
+       type            {{ "HandleArray<$data->{$m->{type}}->{rename}>" }}
+       layout          {{ "MemoryLayout.sequenceLayout({length}, Memory.POINTER)" }}
+}
+
+# 1d array of (struct or union or function?)
+type /^\[(?<length>\d+)\$\{(\w+)\}\]$/ {
+       type            {{ "$data->{$m->{type}}->{rename}" }}
+       layout          {{ "MemoryLayout.sequenceLayout({length}, {type}.LAYOUT)" }}
+}
+
+# 1d array of primitive
+type /^\[(?<length>\d+)(?<ctype>[uif]\d+)\]$/
+               copy=<pointer> template=code:getbyvalue,code:getsetelement {
+       layout     {{ "MemoryLayout.sequenceLayout({length}, Memory.".uc($typeSizes{$match->{ctype}}).")" }}
+       type       {{ ucfirst($typeSizes{$match->{ctype}})."Array" }}
+       tojava     {{ "{type}.createArray({value}, $match->{length}, {scope})" }}
+       tonative   {{ "(jdk.incubator.foreign.Addressable)Memory.address({value}" }}
+
+       getnative  {{ "{type}.create((MemorySegment)$m->{name}\$SH.invokeExact({segment}))" }}
+       setnative;
+       varhandle  {{
+               "final static MethodHandle $m->{name}\$SH = LAYOUT.sliceHandle(MemoryLayout.PathElement.groupElement(\"$m->{name}\"));\n".
+               "final static VarHandle $m->{name}\$EH = LAYOUT.varHandle(MemoryLayout.PathElement.groupElement(\"$m->{name}\"), MemoryLayout.PathElement.sequenceElement());\n"
+       }}
+
+       typei      {{ "$typeSizes{$match->{ctype}}" }}
+       getnativei {{ "({typei})$m->{name}\$EH.get({segment}, {index})" }}
+       setnativei {{ "$m->{name}\$EH.set({segment}, {index}, ({typei}){value})" }}
+}
+
+# 2d array of primitive
+type /^\[(?<length0>\d+)\[(?<length1>\d+)(?<ctype>[uif]\d+)\]\]$/
+               copy=<pointer> template=code:getbyvalue,code:getsetelement2d {
+       layout     {{ "MemoryLayout.sequenceLayout({length0}, MemoryLayout.sequenceLayout({length1}, Memory.".uc($typeSizes{$match->{ctype}})."))" }}
+       type       {{ ucfirst($typeSizes{$match->{ctype}})."Array" }}
+       tojava     {{ "{type}.create({value})" }}
+       tonative   {{ "(jdk.incubator.foreign.Addressable)Memory.address({value})" }}
+
+       getnative  {{ "{type}.create((MemorySegment)$m->{name}\$SH.invokeExact({segment}))" }}
+       setnative;
+
+       typei      {{ "$typeSizes{$match->{ctype}}" }}
+       getnativei {{ "({typei})$m->{name}\$EH.get({segment}, {index0}, {index1}))" }}
+       setnativei {{ "$m->{name}\$EH.set({segment}, {index0}, {index1}, ({typei}){value}))" }}
+
+       varhandle  {{
+               "final static MethodHandle $m->{name}\$SH = LAYOUT.sliceHandle(MemoryLayout.PathElement.groupElement(\"$m->{name}\"));\n".
+               "final static VarHandle $m->{name}\$EH = LAYOUT.varHandle(MemoryLayout.PathElement.groupElement(\"$m->{name}\"), MemoryLayout.PathElement.sequenceElement(), MemotyLayout.PathElement.sequenceElement());\n"
+       }}
+}
+
+# function pointer
+type /^u64:\(/ copy=<pointer> {
+       type    {{ "FunctionPointer<$data->{$m->{type}}->{rename}>" }}
+       tojava  {{ "$data->{$m->{type}}->{rename}.downcall({value}, {scope})" }}
+}
+
+# **primitive
+type /^u64:u64:(?<ctype>[uif]\d+)$/ copy=<pointer> {
+       length  {{ $m->{'array-size'} ? 'get'.($m->{'array-size'}->{rename}).'()' : 'Long.MAX_VALUE' }}
+       type    {{ !$m->{array} ? 'PointerArray' : 'HandleArray<{typei}>' }}
+       tojava  {{
+               !$m->{array}
+               ? 'PointerArray.createArray({value}, {length}, {scope})'
+               : 'HandleArray.createArray({value}, {length}, (a, s) -> {typei}.createArray(a, {length}, s), {scope})'
+       }}
+       typei   {{ ucfirst($typeSizes{$match->{ctype}})."Array" }}
+}
+
+# **type
+type /^u64:u64:\$\{(\w+)\}$/ copy=<pointer> {
+       length          {{ $m->{'array-size'} ? 'get'.($m->{'array-size'}->{rename}).'()' : 'Long.MAX_VALUE' }}
+       type            {{ !$m->{array} ? 'PointerArray' : 'HandleArray<{typei}>' }}
+       tojava          {{
+               !$m->{array}
+               ? 'PointerArray.createArray({value}, {length}, {scope})'
+               : 'HandleArray.createArray({value}, {length}, {typei}::create, {scope})'
+       }}
+       # tojavai ... ?
+       carrieri        {{ "MemoryAddress" }}
+       typei           {{ "$data->{$m->{type}}->{rename}" }}
+       tojavai         {{ "{typei}.create({value}, {scope})" }}
+}
+
+# **void
+type /^u64:u64:v$/ copy=<pointer> {
+       typei           {{ 'PointerArray' }}
+       type            {{ "HandleArray<{typei}>" }}
+       tojava          {{ "HandleArray.createArray({value}, Long.MAX_VALUE, PointerArray"."::create, {scope})" }}
+}
+
+# *primitive, or string for [iNNu8]
+
+# idea for when i implement 'flag' matching for types
+# *i8 with 'array' flag
+# TODO: length, multiple flags?
+type /^u64:(?<ctype>i8)$/ select=array copy=<pointer> {
+       type            {{ ucfirst($typeSizes{$match->{ctype}}).'Array' }}
+       tojava          {{ '{type}.createArray({value}, Long.MAX_VALUE, {scope})' }}
+       carrieri        {{ "{typei}" }}
+       typei           {{ $typeSizes{$match->{ctype}} }}
+       tojavai         {{ "({typei}){value}" }}
+}
+
+# *i8 with 'segment' flag, length must be supplied (somewhere??)
+type /^u64:(?<ctype>i8)$/ select=segment copy=<pointer> {
+       type            {{ 'MemorySegment' }}
+       tojava          {{ '{value}' }}
+       carrieri        {{ "{typei}" }}
+       typei           {{ 'byte' }}
+       tojavai         {{ "({typei}){value}" }}
+}
+
+# *i8 with no flag = String
+type /^u64:(?<ctype>i8)$/ copy=<pointer> {
+       type            {{ 'String' }}
+       tonative        {{ '(jdk.incubator.foreign.Addressable)frame$.copy({value})' }}
+
+       setnative       {{ '{name}$VH.set({segment}, {copynative})' }}
+       copynative      {{ 'SegmentAllocator.nativeAllocator(segment.scope()).allocateUtf8String({value})' }}
+
+       tojava          {{ '({value}).getUtf8String(0L)' }}
+}
+
+# *Primitive fallback
+type /^u64:(?<ctype>[uif]\d+)$/ copy=<pointer> {
+       type            {{ ucfirst($typeSizes{$match->{ctype}})."Array" }}
+       tonative        {{ '(jdk.incubator.foreign.Addressable)Memory.address({value})' }}
+       tojava          {{ ucfirst($typeSizes{$match->{ctype}})."Array.createArray({value}, Long.MAX_VALUE, {scope})" }}
+       carrieri        {{ "{typei}" }}
+       typei           {{ $typeSizes{$match->{ctype}} }}
+       tojavai         {{ "({typei}){value}" }}
+}
+
+# *type  struct?  handle?
+type /^u64:\$\{(\w+)\}$/ copy=<pointer> {
+       type            {{ "$data->{$m->{type}}->{rename}" }}
+       tojava          {{ "$data->{$m->{type}}->{rename}.create({value}, {scope})" }}
+}
+
+# *void with size
+type /^u64:v$/ select=array-size copy=<pointer> {
+       type            {{ 'MemorySegment' }}
+       tojava          {{ "MemorySegment.ofAddress({value}, $m->{'array-size'}->{name}, {scope})" }}
+}
+
+# *void
+type /^u64:v$/ copy=<pointer> {
+}
+
+# primitive
+type /^(?<ctype>[uif]\d+)$/ copy=<common> {
+       layout     {{ "Memory.".uc($typeSizes{$match->{ctype}}) }}
+       carrier    {{ "$typeSizes{$match->{ctype}}" }}
+       type       {{ "$typeSizes{$match->{ctype}}" }}
+       tonative   {{ '({type})({value})' }}
+       tojava     {{ '({type})({value})' }}
+}
+
+# type inline
+type /^\$\{(\w+)\}$/ byvalue template=code:getbyvalue {
+       layout          {{ '{type}.LAYOUT' }}
+       carrier         {{ 'MemoryAddress' }}
+       type            {{ "$data->{$m->{type}}->{rename}" }}
+       tojava          {{ '{type}.create({value}, {scope})' }}
+       tonative        {{ '(jdk.incubator.foreign.Addressable)Memory.address({value})' }}
+       getnative       {{ '{type}.create(({carrier}){name}$SH.invokeExact({segment}), {scope})' }}
+       setnative;
+       varhandle       {{ 'final static MethodHandle {name}$SH = LAYOUT.sliceHandle(MemoryLayout.PathElement.groupElement("{name}"));'."\n" }}
+}
+
+type <common> template=code:getset {
+       getnative       {{ '({carrier}){name}$VH.get({segment})' }}
+       setnative       {{ '{name}$VH.set({segment}, {tonative})' }}
+       varhandle       {{ 'final static VarHandle {name}$VH = LAYOUT.varHandle(MemoryLayout.PathElement.groupElement("{name}"));'."\n" }}
+       tojava          {{ '{value}' }}
+       tonative        {{ '{value}' }}
+}
+
+type <pointer> copy=<common> {
+       layout          {{ 'Memory.POINTER' }}
+       carrier         {{ 'MemoryAddress' }}
+       type            {{ 'MemoryAddress' }}
+       tonative        {{ '(jdk.incubator.foreign.Addressable)Memory.address({value})' }}
+}
diff --git a/test-api-object/Makefile b/test-api-object/Makefile
new file mode 100644 (file)
index 0000000..e18fc77
--- /dev/null
@@ -0,0 +1,44 @@
+
+JAVA_HOME?=/opt/jdk-foreign/jvm/openjdk-19-internal
+JAVAC=$(JAVA_HOME)/bin/javac
+JAVA=$(JAVA_HOME)/bin/java
+
+JAVACFLAGS=--add-modules jdk.incubator.foreign
+
+api_SOURCES := $(wildcard ../src/api/*.java)
+api_demo_SOURCES := $(wildcard src/api/test/*.java)
+apigen_DEPS := $(api_SOURCES) $(wildcard ../src/*.api) $(wildcard ../src/*.pm ../src/generate-api-2)
+
+all::
+       mkdir -p bin
+
+all:: bin/demo.built
+
+bin/api.built: bin/api-object.gen
+       $(JAVAC) $(JAVACFLAGS) -cp bin/classes -d bin/classes \
+               $(shell find bin/java -name '*.java')
+       touch $@
+
+bin/api-object.gen: bin/api.pm bin/api-defines.pm $(apigen_DEPS)
+       ../src/generate-api-2 -v -d bin/java -t proto.apiobject -a ./bin/api.pm -a ./bin/api-defines.pm api-object.api
+       touch $@
+
+bin/api-defines.pm: ../api/api.h ../src/export-defines api-object.api
+       ../src/export-defines --hack-new-format-2 -v -I.. -d $@ api-object.api
+
+bin/api.pm: ../api/api.h ../src/export.so
+       gcc -fplugin=../src/export.so -fplugin-arg-export-output=$@ ./$< -o /dev/null
+
+bin/demo.built: $(api_demo_SOURCES) bin/api.built
+       $(JAVAC) $(JAVACFLAGS) -cp bin/classes -d bin/classes $(api_demo_SOURCES)
+       touch $@
+
+demo: bin/demo.built
+       $(JAVA) --enable-native-access=ALL-UNNAMED --add-modules jdk.incubator.foreign \
+               -Djava.library.path=../api/bin -cp bin/classes \
+               api.test.TestAPI
+
+clean:
+       rm -rf bin
+
+.PHONY: demo clean all
similarity index 100%
rename from test-api/README
rename to test-api-object/README
diff --git a/test-api-object/api-object.api b/test-api-object/api-object.api
new file mode 100644 (file)
index 0000000..91d14b8
--- /dev/null
@@ -0,0 +1,46 @@
+# -*- Mode:text; tab-width:4; electric-indent-mode: nil; indent-line-function:insert-tab; -*-
+
+# maps functions to objects
+
+include types.api
+include code.api
+
+struct <default> default=all field:rename=studly-caps access=rw {
+}
+call <default> default=all call:rename=call access=rw {
+}
+
+struct api {
+       enum://;
+       define:API;
+       library:api-static;
+       library:api-calls;
+}
+
+struct data {
+       func:print_data instance:0 rename=print;
+}
+
+library api-static func:rename=s/^api_//,camel-case {
+       api_create      success:result$=!null scope:result$=global;
+       api_func;
+
+       # different forms of constructor
+       api_new_a       scope:result$=global;
+       api_new_b       scope:result$=global success:rc=0;
+       api_new_c       scope:apip=global success:result$=0 return:apip;
+       api_new_d       scope:apip=global success:api=!null return:apip;
+       api_new_e       scope:apip=global success:rc=0 return:apip;
+}
+
+# instance methods using library-level options
+library api-calls func:rename=s/^api_//,camel-case instance:0 {
+       api_free;
+       api_data        array-size:data=size;
+       api_void        rename=avoid;
+}
+
+# grab all defines that come from api.h
+define API api/api.h {
+       api.h   file-include;
+}
diff --git a/test-api-object/src/api/test/TestAPI.java b/test-api-object/src/api/test/TestAPI.java
new file mode 100644 (file)
index 0000000..cc92c00
--- /dev/null
@@ -0,0 +1,68 @@
+
+package api.test;
+
+import jdk.incubator.foreign.*;
+
+import proto.apiobject.*;
+import java.lang.invoke.*;
+
+public class TestAPI {
+
+       public static void main(String[] args) {
+               System.loadLibrary("api");
+
+               try (Frame frame = Memory.createFrame();
+                       ResourceScope scope = ResourceScope.newConfinedScope()) {
+
+                       data a = data.create(frame);
+                       data b = data.create(frame);
+
+                       Memory.FunctionPointer<Call__i32> cb = Call__i32.upcall(() -> {
+                                       return 56;
+                               }, scope);
+
+
+                       a.setNext(b);
+                       a.setA(1);
+                       a.setB(2);
+                       a.setC((byte)3);
+                       a.setD((byte)4);
+
+
+                       b.setA(5);
+                       b.setB(6);
+                       b.setC((byte)255);
+                       b.setD((byte)255);
+
+                       b.setTestA(cb);
+
+                       System.out.println("from a");
+                       a.print();
+                       System.out.println("from b");
+                       b.print();
+
+                       //api api = proto.api.api.create(frame);
+                       //api.setFunca(api_func(Memory.ByteArray.create("funca", frame)));
+                       //api.setFuncb(api_func(Memory.ByteArray.create("funcb", frame)));
+                       //api.setFuncc(api_func(Memory.ByteArray.create("funcc", frame)));
+
+                       // dynamic lookup
+                       System.out.println("call funca via symbol lookup");
+                       Memory.FunctionPointer<Call_i32_v> funca = Call_i32_v.downcall(api.func("funca"), scope);
+                       System.out.printf(" %s\n", funca.symbol());
+                       funca.function().call(12);
+
+                       api api = proto.apiobject.api.create();
+
+                       System.out.println("call funca via function table");
+                       api.getFunca().function().call(99);
+
+                       api.setFunca(Call_i32_v.upcall(
+                                       i-> System.out.printf("java.funca: %d\n", i),
+                                       scope));
+
+                       System.out.println("call funca via java upcall");
+                       api.getFunca().function().call(22);
+               }
+       }
+}
diff --git a/test-api-static/Makefile b/test-api-static/Makefile
new file mode 100644 (file)
index 0000000..05df38a
--- /dev/null
@@ -0,0 +1,46 @@
+
+CFLAGS=-g -fPIC
+
+JAVA_HOME?=/opt/jdk-foreign/jvm/openjdk-19-internal
+JAVAC=$(JAVA_HOME)/bin/javac
+JAVA=$(JAVA_HOME)/bin/java
+
+JAVACFLAGS=--add-modules jdk.incubator.foreign
+
+api_SOURCES := $(wildcard ../src/api/*.java)
+api_demo_SOURCES := $(wildcard src/api/test/*.java)
+apigen_DEPS := $(api_SOURCES) $(wildcard ../src/*.api) $(wildcard ../src/*.pm ../src/generate-api-2)
+
+all::
+       mkdir -p bin
+
+all:: bin/demo.built
+
+bin/api.built: bin/api-static.gen
+       $(JAVAC) $(JAVACFLAGS) -cp bin/classes -d bin/classes \
+               $(shell find bin/java -name '*.java')
+       touch $@
+
+bin/api-static.gen: bin/api.pm bin/api-defines.pm api-static.api $(apigen_DEPS)
+       ../src/generate-api-2 -v -d bin/java -t proto.apistatic -a ./bin/api.pm -a ./bin/api-defines.pm api-static.api
+       touch $@
+
+bin/api-defines.pm: ../api/api.h ../src/export-defines api-static.api
+       ../src/export-defines --hack-new-format-2 -I.. -d $@ api-static.api
+
+bin/api.pm: ../api/api.h ../src/export.so
+       gcc -fplugin=../src/export.so -I.. -fplugin-arg-export-output=$@ $< -o /dev/null
+
+bin/demo.built: $(api_demo_SOURCES) bin/api.built
+       $(JAVAC) $(JAVACFLAGS) -cp bin/classes -d bin/classes $(api_demo_SOURCES)
+       touch $@
+
+demo: bin/demo.built
+       $(JAVA) --enable-native-access=ALL-UNNAMED --add-modules jdk.incubator.foreign \
+               -Djava.library.path=../api/bin -cp bin/classes \
+               api.test.TestAPI
+
+clean:
+       rm -rf bin
+
+.PHONY: demo clean all
diff --git a/test-api-static/README b/test-api-static/README
new file mode 100644 (file)
index 0000000..59a3438
--- /dev/null
@@ -0,0 +1,8 @@
+
+Introduction
+------------
+
+Small generator and prototyping area for a c library.
+
+This is mostly to test out ideas like pattern matching for mostly
+automatic api conversion.
diff --git a/test-api-static/api-static.api b/test-api-static/api-static.api
new file mode 100644 (file)
index 0000000..e3c7eca
--- /dev/null
@@ -0,0 +1,25 @@
+# -*- Mode:text; tab-width:4; electric-indent-mode: nil; indent-line-function:insert-tab; -*-
+
+# example of a simple 'static' library with all functions
+# and constants in one class and no special handling
+
+include types.api
+include code.api
+
+# collect all functions and constants into one object
+library APILib load=api {
+       enum://;
+       define:API;
+       func://;
+}
+
+struct <default> default=all access=rw field:rename=studly-caps {
+}
+
+call <default> access=rw call:rename=call {
+}
+
+# grab all defines in api.h
+define API api/api.h {
+       api.h       file-include;
+}
similarity index 92%
rename from test-api/src/api/test/TestAPI.java
rename to test-api-static/src/api/test/TestAPI.java
index 108d898..559498f 100644 (file)
@@ -3,8 +3,8 @@ package api.test;
 
 import jdk.incubator.foreign.*;
 
-import proto.api.*;
-import static proto.api.APILib.*;
+import proto.apistatic.*;
+import static proto.apistatic.APILib.*;
 import java.lang.invoke.*;
 
 public class TestAPI {
@@ -26,8 +26,8 @@ public class TestAPI {
                        a.setNext(b);
                        a.setA(1);
                        a.setB(2);
-                       a.setC(3);                      //a.setC((byte)3);
-                       a.setD(4);                      //a.setD((byte)4);
+                       a.setC((byte)3);
+                       a.setD((byte)4);
 
 
                        b.setA(5);
diff --git a/test-api/Makefile b/test-api/Makefile
deleted file mode 100644 (file)
index a7a8666..0000000
+++ /dev/null
@@ -1,55 +0,0 @@
-
-CFLAGS=-g -fPIC
-
-JAVA_HOME?=/opt/jdk-foreign/jvm/openjdk-19-internal
-JAVAC=$(JAVA_HOME)/bin/javac
-JAVA=$(JAVA_HOME)/bin/java
-
-JAVACFLAGS=--add-modules jdk.incubator.foreign
-
-api_SOURCES := $(wildcard ../src/api/*.java)
-api_demo_SOURCES := $(wildcard src/api/test/*.java)
-
-all::
-       mkdir -p bin
-
-all:: bin/demo.built bin/libapi.so
-
-bin/api.built: bin/api.gen
-       $(JAVAC) $(JAVACFLAGS) -cp bin/classes -d bin/classes \
-               $(shell find bin/java -name '*.java')
-       touch $@
-
-bin/api.gen: bin/api.pm ../src/generate-native $(api_SOURCES)  bin/api-defines.pm api.api
-       ../src/generate-native -v -d bin/java -t proto.api -a ./bin/api.pm -a ./bin/api-defines.pm api.api
-       touch $@
-
-bin/api-defines.pm: api.h ../src/export-defines api.api
-       ../src/export-defines -d bin/api-defines.c api.api
-       $(CC) -o bin/api-defines -I. bin/api-defines.c
-       bin/api-defines $@~
-       mv $@~ $@
-
-bin/api.pm: api.h ../src/export.so
-       gcc -fplugin=../src/export.so -fplugin-arg-export-output=$@~ ./$< -o /dev/null
-       mv $@~ $@
-
-bin/demo.built: $(api_demo_SOURCES) bin/api.built
-       $(JAVAC) $(JAVACFLAGS) -cp bin/classes -d bin/classes $(api_demo_SOURCES)
-       touch $@
-
-bin/api.o: api.c api.h
-       $(CC) $(CFLAGS) -c -o $@ $<
-
-bin/libapi.so: bin/api.o
-       $(CC) -o $@ -shared $^
-
-demo: bin/demo.built bin/libapi.so
-       $(JAVA) --enable-native-access=ALL-UNNAMED --add-modules jdk.incubator.foreign \
-               -Djava.library.path=bin -cp bin/classes \
-               api.test.TestAPI
-
-clean:
-       rm -rf bin
-
-.PHONY: demo clean all
diff --git a/test-api/api.api b/test-api/api.api
deleted file mode 100644 (file)
index cfe3103..0000000
+++ /dev/null
@@ -1,15 +0,0 @@
-
-# collect all functions and the defines we want
-library APILib load=api {
-       define:api-defines
-       func://
-}
-
-# grab all structures
-struct // default=all field:rename=studly-caps {
-}
-
-# grab all defines in api.h
-define api-defines api.h {
-       api.h       file-include
-}
diff --git a/test-api/api.h b/test-api/api.h
deleted file mode 100644 (file)
index 4e6b023..0000000
+++ /dev/null
@@ -1,39 +0,0 @@
-
-#define API_FOO "12"
-#define API_MAKEVERSION(a, b) (a <<16) | b
-#define API_VERSION API_MAKEVERSION(1, 0)
-#define API_A 12UL
-#define API_B 12L
-#define API_C 12U
-#define API_D 12.0
-#define API_E 12.0f
-
-struct data {
-       struct data *next;
-
-       int a;
-       int b;
-       int c;          // c:3;  // bitfields not implemented with new generator yet
-       unsigned d;     // d:5;
-
-       int (*test_a)(void);
-
-       char array[12];
-};
-
-void print_data(struct data *data);
-
-struct api {
-       void (*funca)(int a);
-       int (*funcb)(int b);
-       int (*funcc)(float b);
-};
-
-void *api_func(const char *name);
-struct api *api_create(void);
-
-struct list_node {
-       struct list_node *succ;
-       struct list_node *pred;
-       char name[64-16];
-};