Convert to using jdk.foreign rather than jni.
authorNot Zed <notzed@gmail.com>
Fri, 17 Jun 2022 09:42:03 +0000 (19:12 +0930)
committerNot Zed <notzed@gmail.com>
Fri, 17 Jun 2022 09:42:03 +0000 (19:12 +0930)
42 files changed:
Makefile
README
config.make.in
java.make
nbproject/project.properties
src/notzed.nativez/bin/export-api [new file with mode: 0755]
src/notzed.nativez/bin/export-defines [new file with mode: 0755]
src/notzed.nativez/bin/generate-api [new file with mode: 0755]
src/notzed.nativez/classes/au/notzed/nativez/Array.java [new file with mode: 0644]
src/notzed.nativez/classes/au/notzed/nativez/ByteArray.java [new file with mode: 0644]
src/notzed.nativez/classes/au/notzed/nativez/DoubleArray.java [new file with mode: 0644]
src/notzed.nativez/classes/au/notzed/nativez/FloatArray.java [new file with mode: 0644]
src/notzed.nativez/classes/au/notzed/nativez/Frame.java [new file with mode: 0644]
src/notzed.nativez/classes/au/notzed/nativez/FunctionPointer.java [new file with mode: 0644]
src/notzed.nativez/classes/au/notzed/nativez/HandleArray.java [new file with mode: 0644]
src/notzed.nativez/classes/au/notzed/nativez/IntArray.java [new file with mode: 0644]
src/notzed.nativez/classes/au/notzed/nativez/LongArray.java [new file with mode: 0644]
src/notzed.nativez/classes/au/notzed/nativez/Memory.java [new file with mode: 0644]
src/notzed.nativez/classes/au/notzed/nativez/Native.java [new file with mode: 0644]
src/notzed.nativez/classes/au/notzed/nativez/NativeZ.java [deleted file]
src/notzed.nativez/classes/au/notzed/nativez/Pointer.java [new file with mode: 0644]
src/notzed.nativez/classes/au/notzed/nativez/PointerArray.java [new file with mode: 0644]
src/notzed.nativez/classes/au/notzed/nativez/ShortArray.java [new file with mode: 0644]
src/notzed.nativez/classes/module-info.java
src/notzed.nativez/jni/jni.make [deleted file]
src/notzed.nativez/jni/nativez-gen [deleted file]
src/notzed.nativez/jni/nativez-jni.c [deleted file]
src/notzed.nativez/jni/nativez-jni.def [deleted file]
src/notzed.nativez/jni/nativez-linux.c [deleted file]
src/notzed.nativez/jni/nativez-windows.c [deleted file]
src/notzed.nativez/jni/nativez.h [deleted file]
src/notzed.nativez/lib/api.pm [new file with mode: 0644]
src/notzed.nativez/lib/code.api [new file with mode: 0644]
src/notzed.nativez/lib/code.pm [new file with mode: 0644]
src/notzed.nativez/lib/config.pm [new file with mode: 0644]
src/notzed.nativez/lib/method.pm [new file with mode: 0644]
src/notzed.nativez/lib/tokenise.pm [new file with mode: 0644]
src/notzed.nativez/lib/types.api [new file with mode: 0644]
src/notzed.nativez/native/export.cc [new file with mode: 0644]
src/notzed.nativez/native/list.h [new file with mode: 0644]
src/notzed.nativez/native/native.make [new file with mode: 0644]
src/notzed.nativez/native/tree-codes.c [new file with mode: 0644]

index 55f2a0a..6b5c0a5 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -1,5 +1,5 @@
 
-dist_VERSION=1.0.99
+dist_VERSION=2.0.99-jdk-foreign
 dist_NAME=nativez
 dist_EXTRA=README                              \
  build.xml                                     \
diff --git a/README b/README
index da90562..1fa1148 100644 (file)
--- a/README
+++ b/README
@@ -6,12 +6,9 @@ See the section LICENSE for details.
 Introduction
 ------------
 
-This is a C link library and Java base class which can be used to help
-implement libraries which use the Java Native Interface (JNI).
-
-It provides a base class for native objects which provides automatic
-(and manual) reclaimation, and a shared library for easy access to
-these and other JNI utilities.
+This is a set of tools and a Java utility module which can be used to
+implement libraries which use the jdk.foreign mechanisms for calling
+native C libraries.
 
 Compile
 -------
@@ -30,179 +27,120 @@ The target-specific jmodule:
 
 `bin/<target>/jmods/notzed.nativez.jmod'
 
+$ make sdk
+
+Will compile a usable in-tree version to bin/<target>/bin.
+
 Other files of import which can be used for an IDE, dependent project
 compilation, or non-modular use:
 
-`bin/<target>/lib/notzed.nativez.jar' - portable modular jar
-`bin/<target>/include' - header files
-`bin/<target>/lib/libnativez.so' - linux shared library
-`bin/<target>/bin/nativez.dll' - microsoft windows dynamic link library
-`bin/<target>/bin/nativez-gen' - table generator
+`bin/<target>/lib/notzed.nativez.jar' - portable modular jar for utility classes.
+`bin/<target>/lib/libexport.so' - gcc plugin for extracting C api information.
+`bin/<target>/bin/export-api' - driver script for api generation.
+`bin/<target>/bin/generate-api' - internal script for C structs/functions.
+`bin/<target>/bin/export-defines' - internal script for #define processing.
 
 `bin/modules/notzed.nativez/' - compiled class files
 
 Usage
 -----
 
-Java Library
-- - - - - --
+The export-api script is used to generate source files from a control
+file.  `java.make` includes targets for processing the control file
+from gen.make.
 
-Define a common base class and derive from
-`au.notzed.nativez.NativeZ'.  This (and all derived classes) must
-implement the single long (pointer) constructor and load the
-corresponding dynamic library which implements the native interfaces
-from any derived classes.
+`src/<module>/gen/gen.make` should include targets such as:
 
-Each derived class should include a `static void release(long p)'
-method which will be called once the object is no longer accessible
-from Java.  This will typically be a native method that frees the
-associated resources.
+<module>_API = name
 
-The module for the library must export the package containing the
-derived classes to the `notzed.nativez' module using the `opens'
-directive.
+This will expect two control files:
 
-Native Library
-- - - - - - --
+`src/<module>/gen/name.api` - api control file
+`src/<module>/gen/name.h`   - header file which includes all C headers used by the control file.
 
-Whilst not necessary one can use the native library for access to the
-NativeZ_* functions and some other cross-platform tools.
 
-The native library should link with -lnativez and include "nativez.h".
+The <name>.api file is somewhat complex and still in development,
+there is little documentation apart from examples in other projects
+and the source code in lib/*.pm.
 
-Before using any functions invoke the nativez_OnLoad() function,
-typically in the native library's JNI_OnLoad() function.
+The basic format however is:
 
-Any instance of a class derived from NativeZ must be created or
-referenced using the NativeZ_* functions in order to partake of
-automatic cleanup.
+thing name options {
+   entry options...;
+   entry options...;
+}
 
-Native functions should take object arguments and resolve them to
-native pointers using NativeZ_getP().
+Options are of the form:
 
-The nativez_* functions are just general JNI and useful cross-platform
-utility functions which may be freely used within (any) JNI code.
+  flag                         - simple flag
+  class:flag                   - flag for a specific class of variable
+  option=value                 - an option
+  class:option=value   - option on a specific class.
 
-See nativez.h for more information.
+No spaces are allowed in options or values, there are no quoting rules
+and tokens are separated by whitespace.
 
-nativez-gen
--- - - - --
+The whole { } block can be replaced by {{ }} which is a literal, and
+no internal processing will be performed.
 
-nativez provides a couple of functions for compact table based
-resolution of shared library entry points from dynamically loaded
-libraries, and for resolving Java classes and field and method
-acccessors.
+Each named option may also end with a literal block.
 
-Whilst the tables can be generated manually nativez-gen can be used to
-generate correct tables from a definition file.  When run on a
-definition file it will create one or two pairs of static variables to
-be included in the C file that uses it.
+type
+----
 
-nativez-gen requires `perl' and the header section processing requires
-`cproto'.
+Types define handling templates for canonicalised C types.
 
-The definition file is made of any number of sections.  Each section
-starts with a header which must be on the same line (including the
-`{') and a number of entries each on their own line followed by a `}'.
-Non-EOL whitespace is ignored. No quoting or escaping is available.
+library
+-------
 
-See also `src/notzed.nativez/jni/nativez.gen'.  It's dumb but somewhat
-messy perl.
+A library type defines a class which will contain static fields and
+members.  Each entry specifies which objects to include,
+e.g. func[tions], define[s], enum[erations] and are specified by a
+regex.
 
-See also `src/notzed.nativez/jni/jni.make' for an example of how to
-invoke it automatically.
+If a library is referenced by a struct it is not generated directly
+but included within that struct.
 
-java section
--  -- --  --
+Included functions can be renamed, can define in/out parameters,
+return code processing, and whether functions are static or not, or
+constructors.
 
-Each entry must be on a separate line as below.
+struct
+------
 
-java VarName com/package/name/ClassName {
-   static funcName, funcSig
-   funcName, funcSig
-   static fieldName,fieldSig
-   fieldName,fieldSig
-}
+A struct defines an object which maps directly to a struct of the same
+name.  Each entry specifies which structure fields to include as
+public accessors but it can also include constants and functions.
 
-VarName must be unique for each section in a single definition file.
-
-The *Name* and *Sig values are those passed to the corresponding JNI
-functions.  The static modifier calls the *Static* variaants.  A
-funcSig will contain a pair of parentheses and a fieldSig will not.
-
-For each java section the following definitions will be created:
-
-static struct {
-       jclass VarName_classid;
-       jmethodID funcName_[encodedSig];
-       jmethodID funcName_[encodedSig];
-       jfieldID fieldName_[encodedSig];
-       jfieldID fieldName_[encodedSig];
-} java;
-
-static const char *java_names =
-       "#com/package/name/ClassName\0"
-       ":funcName\0funcSig\0"
-       ".funcName\0funcSig\0"
-       ";fieldName\0fieldSig\0"
-       ",fieldName\0fieldSig\0"
-       ;
-  
-encodedSig depends on the arguments to nativez-gen.  By default for
-functions a compact variation of JNI name mangling is used where the
-primitive types are lowercased and any object types are converted to
-'l' with all [ collapsed to nothing.  A longer option (--java-long) is
-somewhat similar but not (current) quite the same as the JNI name
-mangling and allows for overriden functions taking different object
-types in the same position.
-
-See the nativez-gen header for the rest of the options.
-
-header section
- --  -- --  --
-
-A header section extracts prototypes from C .h header files and
-creates a typesafe structure of same.
-
-header libname path/header.h {
-   funcname_a
-   funcmame_b
-}
+The name `<default>` can be used to apply to all structures defined by
+including `<name>.h`.
 
-libname is the name of the library as used in the
-nativez_ResolveLibraries() table.  i.e. it's just a key and not the
-actual name.
+enum
+----
 
-path/header.h should be the name as included directly in a C file,
-i.e. it can be used direclty in a #include directive.  It is relative
-to the (-b path) command line argument.
+Defines any special handling of enums.
 
-The contents of the section are just the function names required.
+define
+------
 
-All header sections in a definition file will be combined to create a
-pair of variables.  If one assumes a second section for libname2 is in
-the file, this is an example output:
+Includes a list of patterns to include or exclude from the list of
+defines generated by the header file.  It can also be filtered by
+which header defined the value, so for example that system header
+files are ignored.
 
-#inclulde <path/header.h>
-#inclulde <path/header2.h>
+Only simple defines can be included (those that resolve to a basic C
+type or string).  The define exporter uses the C compiler to resolve
+the types and automatically determine their base type.
 
-static struct {
-       /* libname */
-       type (*funcname_a)(args);
-       type (*funcname_b)(args);
-       /* libname2 */
-       type (*funcname_c)(args);
-} fn;
+code
+----
 
-static const char *fn_names =
-       "#libname\0"
-       "funcname_a\0"
-       "funcname_b\0"
-       "@libname2\0"
-       "funcname_c\0"
-       ;
+Defines Java code template fragments.  The template system uses a
+simple {name} which resolves to a string which is context-sesitive.
+Templates are expanded recursively.
 
-See the nativez-gen header for the options.
+The code generator is pretty messy at this point and still work in
+progress.
 
 LICENSE
 -------
index 18b53d1..72e4bba 100644 (file)
@@ -6,6 +6,7 @@ JAVA_HOME ?= /usr/local/jdk
 JAVAC ?= $(JAVA_HOME)/bin/javac
 JAR ?= $(JAVA_HOME)/bin/jar
 JMOD ?= $(JAVA_HOME)/bin/jmod
+GCCPLUGINDIR:=$(shell gcc -print-file-name=plugin)
 
 JAVACFLAGS +=
 
@@ -14,6 +15,8 @@ linux-amd64_CPPFLAGS = \
        -I$(JAVA_HOME)/include -I$(JAVA_HOME)/include/linux
 linux-amd64_CFLAGS = -fPIC -Os -Wall
 linux-amd64_CC = cc
+linux-amd64_CXXFLAGS = -fPIC -Os -Wall
+linux-amd64_CXX = g++
 linux-amd64_LD = ld
 
 linux-amd64_SO = .so
@@ -27,7 +30,11 @@ windows-amd64_CPPFLAGS = \
 windows-amd64_CFLAGS = -Os -Wall
 windows-amd64_CC = x86_64-w64-mingw32-gcc
 windows-amd64_LD = x86_64-w64-mingw32-ld
+windows-amd64_CXXFLAGS = -Os -Wall
+windows-amd64_CXX = x86_64-w64-mingw32-g++
 windows-amd64_LDFLAGS = -Wl,--subsystem,windows
 
 windows-amd64_SO = .dll
 windows-amd64_LIB =
+
+$(info target $($(TARGET)_CC))
index ee876b1..c3b9030 100644 (file)
--- a/java.make
+++ b/java.make
@@ -1,5 +1,5 @@
 #
-# Copyright (C) 2019 Michael Zucchi
+# Copyright (C) 2019,2022 Michael Zucchi
 #
 # This is the copyright for java.make
 #
@@ -31,6 +31,9 @@
 #              generators must exist in src/<module>/gen.  Native
 #              libraries must exist in src/<module>/jni.
 
+# native_MODULES list of native-only "modules".
+
+
 # Global variables
 
 # JAVA_HOME            location of jdk.
@@ -40,6 +43,7 @@
 # JARFLAGS             jar flags
 # JMOD                 jmod command.
 # JMODFLAGS            jmod flags.
+# JAVAFLAGS            java flags for run targets
 
 # Module specific variables
 
 # <module>_JARFLAGS
 # <module>_JMODFLAGS
 
+# all paths are relative to the root package name
+
 # <module>_JAVA                        Java sources.  If not set it is found from src/<module>/classes/(*.java)
 # <module>_RESOURCES           .jar resources.  If not set it is found from src/<module>/classes/(not *.java)
-# <module>_JAVA_GENERATED      Java generated sources.  These must be relative to the package name.
+# <module>_JAVA_GENERATED      Java generated sources.
+# <module>_RESOURCES_GENERATED Java generated sources.
 
 # Variables for use in fragments
 
@@ -59,7 +66,6 @@
 
 # <module>_gendir      Location for files used in Java generation process (per project).
 # <module>_genjavadir  Location where _JAVA_GENERATED .java files will be created (per project).
-# <module>_jnidir      Location for jni generated files (per target).
 # <module>_objdir      Location for c objects (per target).
 # <module>_incdir      Location for output includes, .jmod staging.
 # <module>_libdir      Location for output libraries, .jmod staging.  May point to _bindir.
@@ -73,7 +79,7 @@
 # These are compiled after the java sources have been compiled as that
 # process also generates any native binding headers.
 
-# <module>_JNI_LIBRARIES       list of libraries to build.
+# <module>_NATIVE_LIBRARIES    list of libraries to build.
 # library names match System.loadLibrary().
 
 # Global variables
@@ -82,6 +88,9 @@
 # <target>_LDLIBS
 # <target>_CPPFLAGS
 # <target>_CFLAGS
+# <target>_CC
+# <target>_CXXFLAGS
+# <target>_CXX
 # SO           shared library suffix
 # LIB          shared library prefix
 
 
 # Per library variables.
 
-# <library>_SOURCES    .c, .cc, .C - source files for library.  Paths are relative to src/<module>/jni.
-# <library>_HEADERS    header files for jmod
-# <library>_COMMANDS   commands/bin/scripts for jmod
+# <library>_SOURCES    .c source files for library.  Paths are relative to src/<module>/native.
+# <library>_CXXSOURCES .c source files for library.  Paths are relative to src/<module>/native.
+# <library>_HEADERS    header files for install/jmod
+# <library>_COMMANDS   commands/bin/scripts for install/jmod
 
 # <library>_LDFLAGS    link flags
 # <library>_LIBADD     extra objects to add to link line
 # <library>_LDLIBS     link libraries
-# <library>_CPPFLAGS   c pre-processor flags.  "-Isrc/<module>/jni -Ibin/include/<module>" is implicit.
+# <library>_CPPFLAGS   c and c++ pre-processor flags.  "-Isrc/<module>/jni -Ibin/include/<module>" is implicit.
 # <library>_CCFLAGS    c compiler flags
+# <library>_CXXFLAGS   c++ compiler flags
 
 # <library>_DEPENDENCIES       A list of other objects on which this library depends before linking.
 
-# <library>_DEFS       A list of .def files for nativez-gen.
-# <library>_DEFSFLAGS  Flags for nativez-gen invocation.
-
-# .c files have dependencies automatically generated
+# .c and .cc files have dependencies automatically generated
 
 # Targets
 # -------
 # make clean           rm -rf bin
 # make dist            create dist tar in bin/
 # make | make jar      make all jars and jmods
-# make bin             make everything but jars and mods
 
 # Outputs
 # -------
 
 # ######################################################################
 
+all_MODULES = $(java_MODULES) $(native_MODULES)
+
 E:=
 S:=$(E) $(E)
-
-# All modules with native code
-java_JMODS=$(foreach module,$(java_MODULES),$(if $(wildcard src/$(module)/jni/jni.make),$(module)))
-# Only modules with no native code
-java_JARS=$(foreach module,$(java_MODULES),$(if $(wildcard src/$(module)/jni/jni.make),,$(module)))
-# Modules with generated java source
-java_JGEN=$(foreach module,$(java_MODULES),$(if $(wildcard src/$(module)/gen/gen.make),$(module)))
+SO=$($(TARGET)_SO)
+LIB=$($(TARGET)_LIB)
 
 # Define some useful variables before including fragments
+define common_variables=
+$1_gendir:=bin/gen/$1/gen
+$1_genjavadir:=bin/gen/$1/classes
+$1_objdir:=bin/$1/$(TARGET)/obj
+$1_incdir:=bin/$1/$(TARGET)/include
+$1_libdir:=$$(if $$(filter windows-%,$(TARGET)),bin/$1/$(TARGET)/bin,bin/$1/$(TARGET)/lib)
+$1_bindir:=bin/$1/$(TARGET)/bin
+endef
+
 define java_variables=
-$(1)_gendir:=bin/gen/$(1)/gen
-$(1)_genjavadir:=bin/gen/$(1)/classes
-$(1)_jnidir:=bin/$(1)/$(TARGET)/jni
-$(1)_objdir:=bin/$(1)/$(TARGET)/obj
-$(1)_incdir:=bin/$(1)/$(TARGET)/include
-$(1)_libdir:=$$(if $$(filter windows-%,$(TARGET)),bin/$(1)/$(TARGET)/bin,bin/$(1)/$(TARGET)/lib)
-$(1)_bindir:=bin/$(1)/$(TARGET)/bin
-ifndef $(1)_JAVA
-$(1)_JAVA := $$(shell find src/$(1)/classes -type f -name '*.java')
+ifndef $1_JAVA
+$1_JAVA := $$(shell cd src/$1/classes && find * -type f -name '*.java')
 endif
-ifndef $(1)_RESOURCES
-$(1)_RESOURCES := $$(shell find src/$(1)/classes -type f \! -name '*.java')
+ifndef $1_RESOURCES
+$1_RESOURCES := $$(shell cd src/$1/classes && find * -type f \! -name '*.java')
 endif
 endef
 
@@ -179,45 +185,117 @@ java_jardir:=bin/$(TARGET)/lib
 java_incdir:=bin/$(TARGET)/include
 java_jmoddir:=bin/$(TARGET)/jmods
 
+$(foreach module,$(java_MODULES) $(native_MODULES),$(eval $(call common_variables,$(module))))
 $(foreach module,$(java_MODULES),$(eval $(call java_variables,$(module))))
 
 # ######################################################################
 
-all: jar
-bin:
+all:
+jar:
 gen:
 
-.PHONY: all clean jar bin gen
+.PHONY: all clean jar gen $(java_MODULES)
 clean:
        rm -rf bin
 
-include $(patsubst %,src/%/gen/gen.make,$(java_JGEN))
-include $(patsubst %,src/%/jni/jni.make,$(java_JMODS))
+# Gen is things that go into the jar (sources and resources)
+include $(wildcard $(all_MODULES:%=src/%/gen/gen.make))
+# Native is things that go into the sdk/jmod
+include $(wildcard $(all_MODULES:%=src/%/native/native.make))
+
+# ######################################################################
+
+# create module depencies
+# variables:
+# <module>_sdk is the target location of an expanded 'sdk' for this module
+#  it resides in a common location bin/<target>/
+# <module>_jmod is the target location of a staging area for jmod files
+#  is resides in a per-module lcoation bin/<module>/<target>/
+# <module>_java is all the targets that will cause the invocation of javac
+#  it includes the module source, generated sources, and sentinals for generated sources
+
+# targets:
+# bin/status/<module>.depjava marks all source/generated sources are ready/updated
+# bin/status/<module>.depjar all compiled class files and resources are ready/updated
+# bin/status/<module>.sdk all files are available in bin/<target> as if it was an installed image
+
+define module_vars=
+$1_sdk  := $(addprefix $(java_bindir)/,$($1_COMMANDS)) $(addprefix $(java_libdir)/,$($1_LIBRARIES)) $($1_NATIVE_LIBRARIES:%=$(java_libdir)/lib%.so)
+$1_jmod := $(addprefix $($1_bindir)/,$($1_COMMANDS)) $(addprefix $($1_libdir)/,$($1_LIBRARIES)) $($1_NATIVE_LIBRARIES:%=$($1_libdir)/lib%.so)
+$1_java :=$($1_JAVA:%=src/$1/classes/%) $($1_JAVA_GENERATED:%=$($1_genjavadir)/%)
+$1_resources:= $($1_RESOURCES:%=src/$1/classes/%) $($1_RESOURCES_GENERATED:%=$($1_genjavadir)/%)
+$1_depjava := $($1_API:%=bin/status/$1-%.export) $(patsubst %,bin/status/%.classes, $(filter $($1_JDEPMOD),$(java_MODULES)))
+
+ifneq ("$$(strip $$($1_java) $$($1_depjava))", "")
+bin/status/$1.depjava: $$($1_java) $$($1_depjava)
+       @install -d $$(@D)
+       touch $$@
+bin/status/$1.depjar: bin/status/$1.classes $$($1_resources)
+       @install -d $$(@D)
+       touch $$@
+bin/status/$1.depmod: bin/status/$1.classes $$($1_resources) $$($1_jmod)
+       @install -d $$(@D)
+       touch $$@
+bin/status/$1.sdk: $(java_jardir)/$1.jar
+jar: $(java_jardir)/$1.jar
+gen: bin/status/$1.depjava
+$1 all: $(java_jardir)/$1.jar $(java_jmoddir)/$1.jmod
+else
+# acutally not sure here?
+$1 all: bin/status/$1.sdk
+endif
+
+$1-sdk sdk: bin/status/$1.sdk
+
+bin/status/$1.sdk: $$($1_sdk) $$($1_jmod)
+       @install -d $$(@D)
+       touch $$@
+
+endef
+
+#$(foreach m,$(all_MODULES),$(info $(call module_vars,$m)))
+$(foreach m,$(all_MODULES),$(eval $(call module_vars,$m)))
+
+# ######################################################################
+# notzed.nativez export-api
+# ######################################################################
+
+define api_targets=
+bin/status/$1-$2.export: src/$1/gen/$2.api src/$1/gen/$2.h
+bin/status/$1-$2.export:
+       mkdir -p bin/gen/$1/gen bin/status
+       $(NATIVEZ_HOME)/bin/export-api \
+               -w bin/gen/$1/gen -d bin/gen/$1/classes $($1_APIFLAGS) $($1_$2_APIFLAGS) src/$1/gen/$2.api
+       touch $$@
+
+bin/status/$1-$2.export.d:
+       @$(NATIVEZ_HOME)/bin/export-api -M -MT "$$(@:.d=) $$@" -MF $$@ \
+               -w bin/gen/$1/gen -d bin/gen/$1/classes $($1_APIFLAGS) $($1_$2_APIFLAGS) src/$1/gen/$2.api 2>/dev/null
+
+$(if $(filter clean dist gen,$(MAKECMDGOALS)),,-include bin/status/$1-$2.export.d)
+endef
+
+$(foreach m,$(all_MODULES),$(foreach a,$($m_API),$(eval $(call api_targets,$m,$a))))
 
 # ######################################################################
 # Java
 # ######################################################################
 
+# Build targets for java modules
+
 define java_targets=
-# Rules for module $(1)
-$(1)_JAVA_generated = $$(addprefix $$($(1)_genjavadir)/,$$($(1)_JAVA_GENERATED))
-
-bin/status/$(1).data: $$($(1)_RESOURCES)
-bin/status/$(1).classes: $(patsubst %,bin/status/%.classes,$($(1)_JDEPMOD)) $$($(1)_JAVA) $$($(1)_JAVA_generated)
-jar $(1): $(java_jardir)/$(1).jar $(java_jmoddir)/$(1).jmod
-bin: bin/status/$(1).classes bin/status/$(1).data
-sources: $(java_jardir)/$(1)-sources.zip
-gen: $$($(1)_JAVA_generated)
-
-# Create modular jar
-$(java_jardir)/$(1).jar: bin/status/$(1).classes bin/status/$(1).data
+
+# Create (modular) jar
+$(java_jardir)/$1.jar: bin/status/$1.depjar
        @install -d $$(@D)
        $(JAR) cf $$@ \
          $(JARFLAGS) $$($(1)_JARFLAGS) \
-         -C bin/modules/$(1) .
+         -C bin/modules/$(1) . \
+         $(if $($1_RESOURCES),$($1_RESOURCES:%=-C src/$1/classes %)) \
+         $(if $($1_RESOURCES_GENERATED),$($1_RESOURCES_GENERATED:%=-C bin/gen/$1/classes %))
 
 # Create a jmod
-$(java_jmoddir)/$(1).jmod: bin/status/$(1).classes bin/status/$(1).data
+$(java_jmoddir)/$1.jmod: bin/status/$1.depmod
        rm -f $$@
        @install -d $$(@D)
        $$(JMOD) create \
@@ -231,113 +309,108 @@ $(java_jmoddir)/$(1).jmod: bin/status/$(1).classes bin/status/$(1).data
          $$@
 
 # Create an IDE source zip, paths have to match --module-source-path
-$(java_jardir)/$(1)-sources.zip: bin/status/$(1).classes
+$(java_jardir)/$1-sources.zip: bin/status/$1.depjar
        @install -d $$(@D)
-       jar -c -f $$@ -M \
-               $$(patsubst src/$(1)/classes/%,-C src/$(1)/classes %,$$(filter src/$(1)/classes/%,$$($(1)_JAVA))) \
-               $$(patsubst bin/gen/$(1)/classes/%,-C bin/gen/$(1)/classes %,$$(filter bin/gen/$(1)/classes/%,$$($(1)_JAVA)))
-
-endef
+       $(JAR) -c -f $$@ -M \
+               $$($1_JAVA:%=-C src/$1/classes %) \
+               $$($1_JAVA_GENERATED:%=-C bin/gen/$1/classes %)
 
-#$(foreach module,$(java_MODULES),$(info $(call java_targets,$(module))))
-$(foreach module,$(java_MODULES),$(eval $(call java_targets,$(module))))
+# resources
+bin/modules/$1/%: src/$1/classes/%
+       install -vD $$< $$@
 
-# ######################################################################
-# Global pattern rules
-
-# Stage resources
-bin/status/%.data:
-       @install -d $(@D)
-       for data in $(patsubst src/$*/classes/%,"%",$($*_RESOURCES)) ; do \
-               install -vDC "src/$*/classes/$$data" "bin/modules/$*/$$data" || exit 1 ; \
-       done
-       touch $@
-
-# Compile one module.  This only updates (javac -h) headers if they changed.
-bin/status/%.classes:
-       @install -d $(@D)
+# Compile module.
+bin/status/$1.classes: bin/status/$1.depjava
+       @install -d $$(@D)
        $(JAVAC) \
                --module-source-path "src/*/classes:bin/gen/*/classes" \
                $(if $(JAVAMODPATH),--module-path $(subst $(S),:,$(JAVAMODPATH))) \
-               $(JAVACFLAGS) $($*_JAVACFLAGS) \
-               -h bin/inc \
+               $(JAVACFLAGS) $($1_JAVACFLAGS) \
                -d bin/modules \
-               -m $* \
-               $($*_JAVA) $($*_JAVA_generated)
-       if [ -d bin/inc/$* ] ; then \
-               install -DC -t bin/include/$* bin/inc/$*/*.h ; \
-       fi
-       touch $@
+               -m $1 \
+               $$($1_JAVA:%=src/$1/classes/%) \
+               $$($1_JAVA_GENERATED:%=bin/gen/$1/classes/%)
+       touch $$@
+endef
+
+#$(foreach module,$(java_MODULES),$(info $(call java_targets,$(module))))
+$(foreach module,$(java_MODULES),$(eval $(call java_targets,$(module))))
 
-# ######################################################################
-# C stuff
 # ######################################################################
 
-SUFFIXES=.c .C .cc
-SO=$($(TARGET)_SO)
-LIB=$($(TARGET)_LIB)
+# setup run-* targets
+define run_targets=
+run-$1/$2: bin/status/$1.sdk $($1_JDEPMOD:%=bin/status/%.sdk)
+       LD_LIBRARY_PATH=$(FFMPEG_HOME)/lib \
+       $(JAVA) \
+               $(if $(strip $(JAVAMODPATH) $($1_JAVAMODPATH)),--module-path $(subst $(S),:,$(strip $(JAVAMODPATH) $($1_JAVAMODPATH)))) \
+               $(JMAINFLAGS) $($1_JMAINFLAGS) \
+               -m $1/$2 \
+               $(ARGV)
+.PHONY: run-$1/$2
+endef
 
-# functions to find cross-module stuff $(call library-path,modname,libname)
-library-path=$($(1)_libdir)/$(LIB)$(2)$(SO)
-library-dir=$($(1)_libdir)/
+#$(foreach module,$(java_MODULES),$(foreach main,$($(module)_JMAIN),$(info $(call run_targets,$(module),$(main)))))
+$(foreach module,$(java_MODULES),$(foreach main,$($(module)_JMAIN),$(eval $(call run_targets,$(module),$(main)))))
 
-define jni_library=
-# Rule for library $(2) in module $(1)
-$(2)_OBJS = $(foreach sx,$(SUFFIXES),$(patsubst %$(sx), $($(1)_objdir)/%.o, $(filter %$(sx),$($(2)_SOURCES))))
-$(2)_SRCS = $(addprefix src/$(1)/jni/,$($(2)_SOURCES))
-$(2)_SO = $($(1)_libdir)/$(LIB)$(2)$(SO)
+# ######################################################################
+# C and c++ native library support
+# ######################################################################
 
-$($(1)_libdir)/$(LIB)$(2)$(SO): $$($(2)_OBJS) $($(2)_LIBADD) $($(2)_DEPENDENCIES)
-       @install -d $$(@D)
-       $($(TARGET)_CC) -o $$@ -shared \
-               $($(TARGET)_LDFLAGS) $($(2)_LDFLAGS) $$($(2)_OBJS) $($(2)_LIBADD) $($(TARGET)_LDLIBS) $($(2)_LDLIBS)
+define native_library=
+# Rule for library $$2 in module $$1
+$2_OBJS = $(patsubst %.c, $($1_objdir)/%.o, $($2_SOURCES)) \
+       $(patsubst %.cc, $($1_objdir)/%.o, $($2_CXXSOURCES))
+$2_SRCS = $(addprefix src/$1/native/,$($2_SOURCES))
+$2_SO = $($1_libdir)/$(LIB)$2$(SO)
 
+# Copy anything from staging area for jmods bin/module/<target>/* to sdk area bin/<target>/*
 $(java_libdir)/%: $($(1)_libdir)/%
-       install -DC $$< $$@
+       @install -d $$(@D)
+       ln -fs $$(abspath $$<) $$@
 $(java_bindir)/%: $($(1)_bindir)/%
-       install -DC $$< $$@
+       @install -d $$(@D)
+       ln -fs $$(abspath $$<) $$@
 $(java_incdir)/%: $($(1)_incdir)/%
-       install -DC $$< $$@
+       @install -d $$(@D)
+       ln -fs $$(abspath $$<) $$@
 
-$($(1)_objdir)/%.o: src/$(1)/jni/%.c
+$($1_libdir)/$(LIB)$2$(SO): $$($2_OBJS) $($2_LIBADD) $($2_DEPENDENCIES)
        @install -d $$(@D)
-       $($(TARGET)_CC) -Isrc/$(1)/jni -Ibin/include/$(1) -I$($(1)_jnidir) \
-               $($(TARGET)_CPPFLAGS) $($(2)_CPPFLAGS) \
-               $($(TARGET)_CFLAGS) $($(2)_CFLAGS) -c -o $$@ $$<
+       $($(TARGET)_CC) -o $$@ -shared \
+               $($(TARGET)_LDFLAGS) $($2_LDFLAGS) $$($2_OBJS) $($2_LIBADD) $($(TARGET)_LDLIBS) $($2_LDLIBS)
 
-$($(1)_incdir)/%.h: src/$(1)/jni/%.h
-       install -DC $$< $$@
+$($1_objdir)/%.o: src/$1/native/%.c
+       @install -d $$(@D)
+       $($(TARGET)_CC) -Isrc/$1/native -Ibin/include/$1 \
+               $($(TARGET)_CPPFLAGS) $($2_CPPFLAGS) \
+               $($(TARGET)_CFLAGS) $($2_CFLAGS) -c -o $$@ $$<
+
+$($1_objdir)/%.o: src/$1/native/%.cc
+       @install -d $$(@D)
+       $($(TARGET)_CXX) -Isrc/$1/native -Ibin/include/$1 \
+               $($(TARGET)_CPPFLAGS) $($2_CPPFLAGS) \
+               $($(TARGET)_CXXFLAGS) $($2_CXXFLAGS) -c -o $$@ $$<
 
 # auto-dependencies for c files
-$($(1)_objdir)/%.d: src/$(1)/jni/%.c bin/status/$(1).classes
+$($1_objdir)/%.d: src/$1/native/%.c
        @install -d $$(@D)
        @rm -f $$@
-       @$($(TARGET)_CC) -MM -MT "bin/$(1)/$(TARGET)/obj/$$*.o" -Isrc/$(1)/jni -Ibin/include/$(1) \
-               $($(TARGET)_CPPFLAGS) $($(2)_CPPFLAGS) $$< -o $$@.d 2>/dev/null
-       @sed 's,\($$*\.o\) *:,\1 $$@ : ,g' $$@.d > $$@ ; rm $$@.d
+       @$($(TARGET)_CC) -MM -MT "$$(@:.d=.o) $$@" -Isrc/$1/jni -Ibin/include/$1 \
+               $($(TARGET)_CPPFLAGS) $($2_CPPFLAGS) $$< -o $$@ 2>/dev/null
 
-# .def files for nativez mapping
-$($(1)_jnidir)/%.h: src/$(1)/jni/%.def
+# auto-dependencies for c++ files
+$($1_objdir)/%.d: src/$1/native/%.cc
        @install -d $$(@D)
-       $(NATIVEZ_HOME)/bin/nativez-gen -J $($(2)_DEFSFLAGS) $$< > $$@ || ( rm $$@ ; exit 1)
-
-bin jni $(1) $(java_jmoddir)/$(1).jmod: \
-       $($(1)_libdir)/$(LIB)$(2)$(SO) \
-       $(java_libdir)/$(LIB)$(2)$(SO) \
-       $(addprefix $($(1)_incdir)/,$($(2)_HEADERS)) \
-       $(addprefix $(java_incdir)/,$($(2)_HEADERS)) \
-       $(addprefix $($(1)_bindir)/,$($(2)_COMMANDS)) \
-       $(addprefix $(java_bindir)/,$($(2)_COMMANDS)) \
-       $(addprefix $($(1)_libdir)/,$($(2)_LIBRARIES))
-
-$(if $(filter clean dist gen,$(MAKECMDGOALS)),,-include $$($(2)_OBJS:.o=.d))
-endef
+       @rm -f $$@
+       @$($(TARGET)_CXX) -MM -MT "$$(@:.d=.o) $$@" -Isrc/$1/jni -Ibin/include/$1 \
+               $($(TARGET)_CPPFLAGS) $($2_CPPFLAGS) $$< -o $$@ 2>/dev/null
 
-#$(foreach module,$(java_JMODS),$(foreach library,$($(module)_JNI_LIBRARIES),$(info $(call jni_library,$(module),$(library)))))
-$(foreach module,$(java_JMODS),$(foreach library,$($(module)_JNI_LIBRARIES),$(eval $(call jni_library,$(module),$(library)))))
+$(if $(filter clean dist gen,$(MAKECMDGOALS)),,-include $$($2_OBJS:.o=.d))
+endef
 
-#$(foreach module,$(java_JMODS),$(foreach library,$($(module)_JNI_LIBRARIES),$(foreach def,$($(library)_DEFS),$(info $($(module)_objdir)/$(def:.def=.o): $($(module)_jnidir)/$(def:.def=.h)))))
-$(foreach module,$(java_JMODS),$(foreach library,$($(module)_JNI_LIBRARIES),$(foreach def,$($(library)_DEFS),$(eval $($(module)_objdir)/$(def:.def=.o): $($(module)_jnidir)/$(def:.def=.h)))))
+#$(foreach module,$(all_MODULES),$(foreach library,$($(module)_NATIVE_LIBRARIES),$(info $(call native_library,$(module),$(library)))))
+$(foreach module,$(all_MODULES),$(foreach library,$($(module)_NATIVE_LIBRARIES),$(eval $(call native_library,$(module),$(library)))))
 
 # ######################################################################
 
@@ -345,6 +418,5 @@ dist:
        @install -d bin
        tar cfz bin/$(dist_NAME)-$(dist_VERSION).tar.gz \
         --transform=s,^,$(dist_NAME)-$(dist_VERSION)/, \
-        config.make java.make Makefile src             \
+        config.make.in java.make Makefile src          \
         $(dist_EXTRA)
-
index 81e958a..e20f7a8 100644 (file)
@@ -47,8 +47,8 @@ javac.modulepath=
 javac.processormodulepath=
 javac.processorpath=\
     ${javac.classpath}
-javac.source=13
-javac.target=13
+javac.source=18
+javac.target=18
 javac.test.classpath=\
     ${javac.classpath}
 javac.test.modulepath=\
diff --git a/src/notzed.nativez/bin/export-api b/src/notzed.nativez/bin/export-api
new file mode 100755 (executable)
index 0000000..6ecd12d
--- /dev/null
@@ -0,0 +1,159 @@
+#!/usr/bin/perl
+
+# meta script to run everything at once.
+
+# TODO: add an option that dumps out the make dependencies
+
+use strict;
+use File::Basename;
+use File::Spec::Functions qw(abs2rel);
+
+use FindBin;
+use lib "$FindBin::Bin/../lib";
+
+use Data::Dumper;
+use File::Path qw(make_path);
+use File::Basename;
+
+use config;
+
+my $apidef;
+my $apibase;
+my $apih;
+my $mode = 'generate';
+my $var = {
+       package => 'api',
+       output => 'bin',
+       workdir => 'bin',
+       verbose => 0,
+       include => [],
+};
+
+while (@ARGV) {
+       my $cmd = shift(@ARGV);
+
+       if ($cmd eq '-MT') {
+               $var->{'make-target'} = shift;
+       } elsif ($cmd eq '-MF') {
+               $var->{'make-file'} = shift;
+       } else {
+               if ($cmd =~ m/^(-[^-])(.+)/) {
+                       $cmd = $1;
+                       unshift @ARGV, $2;
+               }
+
+               if ($cmd eq "-t") {
+                       $var->{package} = shift;
+               } elsif ($cmd eq "-d") {
+                       $var->{output} = shift;
+               } elsif ($cmd eq "-w") {
+                       $var->{workdir} = shift;
+               } elsif ($cmd eq "-I") {
+                       push @{$var->{include}}, shift;
+               } elsif ($cmd eq "-v") {
+                       $var->{verbose}++;
+               } elsif ($cmd eq '-M') {
+                       $mode = 'make-rule';
+               } else {
+                       $apidef = $cmd;
+                       $apih = "$1.h" if ($apidef =~ m/^(.*).api$/);
+                       $apibase = basename($apidef, '.api');
+               }
+       }
+}
+
+push @{$var->{include}}, "$FindBin::Bin/../lib";
+
+#print Dumper($var);
+
+die ("Missing config argument") if !defined($apidef);
+die ("Unable to find config: $apidef") if !-f $apidef;
+die ("Unable to find matching header for: $apidef") if !-f $apih;
+
+my $api = new config($var, $apidef);
+my @includes = map { ('-I', $_ ) } @{$var->{include}};
+
+if ($mode eq 'make-rule') {
+       my @list = ();
+
+       $var->{'make-target'} = "$apibase.d" if !defined $var->{'make-target'};
+
+       my @args = (
+               '-M',
+               '-MT', $var->{'make-target'},
+               @includes,
+               $apih);
+
+       open (my $gcc, '-|', 'gcc', @args) // die("command failed $!");
+       while (<$gcc>) {
+               chop;
+               s/ *\\$//;
+               s/^ +//;
+               push @list, $_;
+       }
+       close($gcc);
+
+       foreach my $m (qw(export-api export-defines generate-api)) {
+               push @list, abs2rel("$FindBin::Bin/$m");
+       }
+       foreach my $m (qw(api.pm code.pm config.pm method.pm tokenise.pm)) {
+               push @list, abs2rel("$FindBin::Bin/../lib/$m");
+       }
+
+       push @list, $apidef;
+       push @list, map { abs2rel($_) } @{$api->{includes}};
+       push @list, map { dirname($apidef)."/$_->[1]" } grep { $_->[0] eq '%require' } @{$api->{pragmas}};
+
+       if (defined $var->{'make-file'}) {
+               open (my $f, ">", $var->{'make-file'}.'~') || die "writing $var->{'make-file'}";
+               print $f join(" \\\n ", @list)."\n";
+               close ($f) || die "writing $var->{'make-file'}";
+               rename($var->{'make-file'}.'~', $var->{'make-file'}) || die "writing $var->{'make-file'}";
+       } else {
+               print join(" \\\n ", @list)."\n";
+       }
+       exit 0;
+}
+
+my @cmd = (
+       'gcc',
+       "-fplugin=$FindBin::Bin/../lib/libexport.so",
+       "-fplugin-arg-libexport-output=$var->{workdir}/$apibase.pm",
+#      "-fplugin-arg-libexport-verbose=$var->{verbose}",
+       '-O0',
+       '-o', '/dev/null',
+       @includes,
+       $apih
+);
+
+print join " ", @cmd, "\n";
+system(@cmd) == 0 || die("command failed");
+
+my @defines = ();
+if (grep { $_->{type} eq 'define' } @{$api->{objects}}) {
+       @cmd = (
+               "$FindBin::Bin/export-defines",
+               '--hack-new-format-2',
+               $var->{verbose} ? '-v' : (),
+               '-o',
+               "$var->{workdir}/$apibase-defines.pm",
+               @includes,
+               $apidef
+       );
+
+       print join " ", @cmd, "\n";
+       system(@cmd) == 0 || die("command failed");
+       push @defines, '-a', "./$var->{workdir}/$apibase-defines.pm";
+}
+
+@cmd = (
+       "$FindBin::Bin/generate-api",
+       $var->{verbose} ? '-v' : (),
+       '-t', $var->{package},
+       '-d', $var->{output},
+       '-a', "./$var->{workdir}/$apibase.pm",
+       @defines,
+       $apidef
+);
+print join " ", @cmd, "\n";
+system(@cmd) == 0 || die("command failed");
diff --git a/src/notzed.nativez/bin/export-defines b/src/notzed.nativez/bin/export-defines
new file mode 100755 (executable)
index 0000000..7b986e0
--- /dev/null
@@ -0,0 +1,396 @@
+#!/usr/bin/perl
+
+use FindBin;
+use lib "$FindBin::Bin/../lib";
+
+use File::Basename;
+use Data::Dumper;
+
+#require genconfig;
+require config;
+
+my @includes = ();
+my $header;
+my $control = "api-defines.def";
+my $output;
+my $hackformat;
+
+while (@ARGV) {
+       my $cmd = shift;
+
+       if ($cmd =~ m/^(-[^-])(.+)/) {
+               $cmd = $1;
+               unshift @ARGV, $2;
+       }
+
+       if ($cmd eq "-t") {
+               $package = shift;
+    } elsif ($cmd eq "-o") {
+               $output = shift;
+    } elsif ($cmd eq "-v") {
+               $verbose++;
+       } elsif ($cmd eq "-I") {
+               push @includes, shift;
+       } elsif ($cmd eq "--hack-new-format") {
+               $hackformat = 1;
+       } elsif ($cmd eq "--hack-new-format-2") {
+               $hackformat = 2;
+       } else {
+               $control = $cmd;
+       }
+}
+
+die ("no output specified") if !$output;
+
+my $defs;
+my @xports;
+if ($hackformat == 1) {
+       #$defs = genconfig::loadControlFile($control);
+       #@exports = grep { $_->{type} eq 'define' } @{$defs};
+} elsif ($hackformat == 2) {
+       #push @includes, "$FindBin::Bin/../lib";
+       my $conf = new config({ include => \@includes }, $control);
+       $defs = $conf->{objects};
+       @exports = grep { $_->{type} eq 'define' } @{$defs};
+       foreach $export (@exports) {
+               $export->{import} = $conf->findInclude($export->{options}->[0]);
+       }
+} else {
+       $defs = loadControlFile($control);
+       @exports = @{$defs->{define}};
+}
+my %rawDefines = (); # indexed by header
+
+my $CPPFLAGS = "";
+
+foreach $i (@includes) {
+       $CPPFLAGS .= " '-I$i'";
+}
+
+# flatten the generic format to a more usable format, work out defaults (. and syntax check?)
+foreach $export (@exports) {
+       my $includes = 0, $excludes = 0;
+
+       foreach $inc (@{$export->{items}}) {
+               my @options = @{$inc->{options}};
+
+               next if ($inc->{match} =~ m/^(define|enum):/on);
+
+               $inc->{mode} = "include";
+               foreach $o (@{$inc->{options}}) {
+                       if ($o =~ m/^(exclude|include|file-include|file-exclude)$/) {
+                               $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 {
+                       $excludes += 1;
+               }
+       }
+
+       $export->{default} = 'all';
+       $export->{default} = 'none' if ($includes > 0);
+
+       my @options = @{$export->{options}};
+
+       $export->{header} = $options[0];
+
+       foreach $o (@options[1..$#options]) {
+               if ($o =~ m/default=(.*)/) {
+                       $export->{default} = $1;
+               } else {
+                       print STDERR "unknown defines option '$o'\n";
+               }
+       }
+
+       # insert ignore <built-in> thing
+       if ($export->{default} eq 'all') {
+               unshift @{$export->{items}}, { regex => qr/<built-in>/, mode => 'file-exclude' };
+       }
+
+       die ("no header for '$export->{name}'") if !$export->{header};
+}
+
+# load all defines once and link in
+# keep_comments is a bit broken
+foreach $export (@exports) {
+       my $header = $export->{import};
+
+       if (!defined($rawDefines{$header})) {
+               $rawDefines{$header} = scanDefines($header, { CPPFLAGS=>$CPPFLAGS, keep_comments=>0 });
+       }
+
+       $export->{rawDefines} = $rawDefines{$header};
+}
+
+foreach $export (@exports) {
+       # extract matching #defines
+       my @defines = ();
+
+       foreach $d (@{$export->{rawDefines}}) {
+               my $output = 0;
+               my $matches = 0;
+
+               print "? $d->{name} $d->{file} " if $verbose;
+               foreach $inc (@{$export->{items}}) {
+                       if ($inc->{mode} eq "include") {
+                               $matches = $d->{name} =~ m/$inc->{regex}/;
+                               $output = 1 if $matches;
+                       } elsif ($inc->{mode} eq "exclude") {
+                               $matches = $d->{name} =~ m/$inc->{regex}/;
+                       } elsif ($inc->{mode} eq "file-include") {
+                               $matches = $d->{file} =~ m/$inc->{regex}/;
+                               $output = 1 if $matches;
+                       } elsif ($inc->{mode} eq "file-exclude") {
+                               $matches = $d->{file} =~ m/$inc->{regex}/;
+                       }
+                       print " ($inc->{mode} '$inc->{match}' '$inc->{regex}' =$matches)" if $verbose;
+                       last if $matches;
+               }
+
+               $output = 1 if (!$matches && $export->{default} eq "all");
+
+               print " output=$output\n" if $verbose;
+
+               push (@{$export->{export}}, $d) if $output;
+       }
+
+}
+
+# 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-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;
+
+# ######################################################################
+
+sub uniq {
+  my %seen;
+  return grep { !$seen{$_}++ } @_;
+}
+
+sub export {
+       my $fp = shift;
+       my @exports = @{shift @_};
+
+       print $fp <<END;
+#include <stdio.h>
+#include <stdint.h>
+END
+       foreach $h (uniq map { $_->{header} } @exports) {
+               print $fp  "#include \"$h\"\n";
+       }
+
+       # 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 */
+#define FMT(x) \\
+       __builtin_choose_expr(__builtin_types_compatible_p(typeof(x), float),    "value=>'%a', type=>'f32'", \\
+       __builtin_choose_expr(__builtin_types_compatible_p(typeof(x), double),   "value=>'%a', type=>'f64'", \\
+       __builtin_choose_expr(__builtin_types_compatible_p(typeof(x), int32_t),  "value=>'%d', type=>'i32'", \\
+       __builtin_choose_expr(__builtin_types_compatible_p(typeof(x), uint32_t), "value=>'0x%08x', type=>'u32'", \\
+       __builtin_choose_expr(__builtin_types_compatible_p(typeof(x), int64_t),  "value=>'%ld', type=>'i64'", \\
+       __builtin_choose_expr(__builtin_types_compatible_p(typeof(x), uint64_t), "value=>'0x%016lx', type=>'u64'", \\
+       __builtin_choose_expr(__builtin_types_compatible_p(typeof(x), unsigned long long), "value=>'0x%016llx', type=>'u64'", \\
+       __builtin_choose_expr(__builtin_types_compatible_p(typeof(x), long long), "value=>'0x%016llx', type=>'i64'", \\
+       __builtin_choose_expr(__builtin_types_compatible_p(typeof(x), typeof(char[])),  "value=>'%s', type=>'string'", "type => undefined" )))))))))
+
+int main(int argc, char **argv) {
+ FILE *fp = fopen(argv[1], "w");
+
+ fputs("{\\n", fp);
+END
+
+       foreach $export (@exports) {
+               print $fp <<END;
+fputs("'define:$export->{name}' => { name => '$export->{name}', type => 'define', values => [\\n", fp);
+END
+               foreach $d (@{$export->{export}}) {
+                       my $docomment;
+
+                       if ($d->{comment}) {
+                               my $comment = $d->{comment};
+                               $comment =~ s@(['"])@\\\\\\$1@g;
+                               $comment =~ s@\n@\\n@g;
+                               $docomment = ", comment => '$comment' ";
+                       }
+
+                       print $fp <<END;
+ fputs("  { name => \\"$d->{name}\\", ", fp);
+ fprintf(fp, FMT($d->{name}), ($d->{name}));
+ fputs("$docomment},\\n", fp);
+END
+               }
+               print $fp <<END;
+ fputs(" ],\\n},\\n", fp);
+END
+       }
+       print $fp <<END;
+ fprintf(fp, "}\\n");
+ fclose(fp);
+}
+END
+}
+
+# args: filename, \@defines, \@defineList
+# export a c file generator
+sub export_generator {
+       my $filename = shift;
+       my @defines = @{shift @_};
+       my @defineList = @{shift @_};
+}
+
+# args: header, \%options
+# options = {
+#   CPPFLAGS => 'flags'
+#   keep_comments => 1 to keep comments
+# }
+# returns a list of
+# {
+#   name=>'name',
+#   comment='comment',
+#   file='filename',
+#   line=linenumber
+# }
+sub scanDefines {
+       my $header = shift;
+       my $o = shift;
+    my $lastc = "";
+       my $source;
+       my $sourceLine;
+
+    print "Scanning $header\n";
+
+    print "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+) \"([^\"]*)/) {
+                       $sourceLine = $1;
+                       $source = $2;
+                       next;
+               }
+               # accumulate comments
+               # single line comments override multi-line
+               if ($o->{keep_comments}) {
+                       if (m@/\*(.*)\*/@) {
+                               do {
+                                       $lastc = $1 if $lastc eq "";
+                                       s@/\*.*\*/@@;
+                               } while (m@/\*(.*)\*/@);
+                       } elsif (m@/\*(.*)@) {
+                               my $com = "$1\n" if $1;
+                               while (<$in>) {
+                                       chop;
+                                       if (m@(.*)\*/@) {
+                                               $com .= $1 if $1;
+                                               last;
+                                       } else {
+                                               $com .= "$_\n";
+                                       }
+                               }
+                               $lastc = $com if $com && $lastc eq "";
+                       } elsif (m@//(.*)@) {
+                               $lastc = $1 if $1;
+                               s@//.*@@;
+                       }
+               }
+
+               if (m/^\s*#define\s+(\w*)\(/) {
+                       # ignore macros
+                       $lastc = "";
+               } elsif (m/^\s*#define\s+(\w*)\s*$/) {
+                       # ignore empty defines
+                       $lastc = "";
+               } elsif (m/^\s*#define\s+(\w+)/) {
+                       my $name = $1;
+                       my %define = ();
+
+                       $define{name} = $name;
+                       $define{comment} = $lastc if $lastc ne "";
+                       $define{file} = $source;
+                       $define{line} = $sourceLine;
+
+                       push @defines, \%define;
+
+                       $lastc = "";
+               }
+
+               $sourceLine++;
+       }
+       close $in;
+
+       return \@defines;
+}
+
+# TODO: library
+sub loadControlFile {
+       my $path = shift @_;
+       my %def = ();
+       my $target;
+
+       open (my $d,"<",$path);
+
+       while (<$d>) {
+               next if /\s*\#/;
+
+               chop;
+
+               if ($target) {
+                       if (m/\s*\}\s*$/) {
+                               undef $target;
+                       } elsif (/^\s*(\S+)\s*(.*)/) {
+                               my @options = split(/\s+/,$2);
+                               push @{$target->{items}}, {
+                                       match => $1,
+                                       options => \@options
+                               };
+                       }
+               } elsif (/^(\w+)\s+(\S*)\s*(.*)\s+\{/) {
+                       my @options = split(/\s+/,$3);
+
+                       $target = {
+                               type => $1,
+                               name => $2,
+                               options => \@options,
+                               items => []
+                       };
+                       push @{$def{$1}}, $target;
+               } elsif (/\S/) {
+                       die("invalid line: '$_'");
+               }
+       }
+
+       close $d;
+
+       return \%def;
+}
diff --git a/src/notzed.nativez/bin/generate-api b/src/notzed.nativez/bin/generate-api
new file mode 100755 (executable)
index 0000000..f185237
--- /dev/null
@@ -0,0 +1,403 @@
+#!/usr/bin/perl
+
+# TODO: formatLibrary -  code:<inline> has no template applied, no vars available to do so
+
+# 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';
+use FindBin;
+use lib "$FindBin::Bin/../lib";
+
+use api;
+use code;
+use method;
+
+$SIG{__DIE__} = sub { Carp::confess( @_ ) };
+$SIG{'INT'} = sub { Carp::confess() };
+$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}}, "$FindBin::Bin/../lib";
+push @INC, dirname($apidef);
+
+print Dumper($vars) if $vars->{verbose};
+
+my $api = new api($apidef, $vars, @apilist);
+
+#print Dumper($api);
+
+exportLibraries($api);
+exportStructs($api);
+exportConstants($api);
+exit 0;
+
+sub formatFunction {
+       my $api = shift;
+       my $c = shift;
+       my $tname = shift;
+       my $template = shift;
+       my $info = new method($api, $c);
+
+       #print 'function='.Dumper($c);
+       #print 'members='.Dumper(\@members);
+       #print 'info='.Dumper($info);
+
+       my $code;
+
+       # allow per-function override
+       $template = $api->findTemplateName($c->{$tname}) if defined $c->{$tname};
+
+       if (defined($template)) {
+               if ($tname eq 'func:template') {
+                       foreach my $l (split /\n/,Dumper($c)) {
+                               $code .= "// $l\n";
+                       }
+               }
+
+               $code .= code::applyTemplate($template, $info->{vars});
+               $code =~ s/^\s*\n//osgm;
+
+               return $code;
+       } else {
+               return ();
+       }
+}
+
+sub formatCall {
+       my $api = shift;
+       my $c = shift;
+       my $template = $api->{index}->{'code:method'};
+       my $upcall = api::findItem($template, 'upcall');
+       my $downcall = api::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(api::findItem($api->{index}->{'code:class'}, 'call'), $info->{vars});
+}
+
+sub formatItems {
+       my $api = shift;
+       my $obj = shift;
+       my $inc = shift;
+       my $res = shift;
+       my @list;
+
+       @list = grep {
+               $api->{output}->{"$_->{type}:$_->{name}"}++;
+               $res->{seen}->{"$_->{type}:$_->{name}"}++ == 0
+       } $api->findMatches($inc, $res->{ctx});
+
+       if ($inc->{type} eq 'func' && $inc->{literal}) {
+               push @{$res->{func}}, $inc->{literal};
+       } elsif ($inc->{type} eq 'func') {
+               my $def = $api->{index}->{'func:<default>'};
+               my $func = api::optionValue('func:template', 'code:method=invoke', $inc, $res->{template}, $def);
+               my $init = api::optionValue('init:template', undef, $inc, $res->{template}, $def);
+               my $funct = $api->findTemplateName($func);
+               my $initt = $api->findTemplateName($init) if defined $init;
+
+               push @{$res->{func}}, map { formatFunction($api, $_, 'func:template', $funct) } @list;
+               push @{$res->{init}}, map { formatFunction($api, $_, 'init:template', $initt) } @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, $_, $res->{seen}) } @list;
+       } elsif ($inc->{type} eq 'call') {
+               push @{$res->{call}}, 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" if ($api->{vars}->{verbose} > 0);
+
+       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)$/no) {
+                       print "  $inc->{match}\n" if ($api->{vars}->{verbose} > 1);
+                       formatItems($api, $obj, $inc, $res);
+               } elsif ($inc->{match} eq 'code:<inline>') {
+                       print "  $inc->{match}\n" if ($api->{vars}->{verbose} > 1);
+                       push @{$res->{func}}, $inc->{literal};
+               } elsif ($inc->{type} eq 'code') {
+                       # apply template perhaps, or apply with set?
+                       push @{$res->{func}}, map {
+                               if (defined($inc->{options}->[0])) {
+                                       api::findItem($_, $inc->{options}->[0])->{literal};
+                               } else {
+                                       $_->{literal}
+                               }
+                       } grep { $_->{match} =~ m/$inc->{regex}/ } @{$api->{api}};
+               } elsif ($inc->{type} ne 'field') {
+                       print Dumper($inc);
+                       die;
+               }
+       }
+
+       if (defined $obj->{imports}) {
+               my @x = map { "import $_;" } @{$obj->{imports}};
+               $res->{import} = \@x;
+       }
+}
+
+# TODO: embedded structs
+sub formatStruct {
+       my $api = shift;
+       my $obj = shift;
+       my $s = shift;
+       my $data = $api->{data};
+       my $seen = {};
+       my $structTemplate = $api->findTemplate($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 $tname = $_;
+                       my $accessor = $api->{index}->{$tname};
+
+                       map { code::applyTemplate($_, $match) } grep { $_ } map { api::findItem($accessor, $_) } map {
+                               my @list = ();
+                               if ($_ =~ m/r/o || $tname eq 'code:getbyvalue') {
+                                       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(/,/,api::optionValue('template', undef, $type));
+       } @membersOutput;
+
+       my $res = {
+               ctx => $s,
+               library => [],
+               func => [],
+               init => [],
+               define => [],
+               enum => [],
+               call => [],
+               seen => {},
+               template => $structTemplate,
+       };
+
+       formatLibrary($api, $obj, $res);
+
+       # handle per-struct format functions
+       foreach my $fmt (@{$s->{postformat}}) {
+               $fmt->($api, $obj, $res, $s);
+       }
+
+       my $vars = {
+               %{$api->{vars}},
+               layout => code::formatStructLayout($api, $s, \@members),
+               rename => $s->{rename},
+               name => $s->{name},
+               varhandles => $varhandles,
+               accessors => $accessors,
+               init => join("\n", @{$res->{init}}),
+               methods => join("\n", @{$res->{func}}),
+               defines => join("\n", @{$res->{define}}),
+               enums => join("\n", @{$res->{enum}}),
+       };
+
+       my $code;
+       foreach my $l (split /\n/,Dumper({ template => $api->queryTemplate($obj, $s), struct => $s }, \@members)) {
+               $code .= "//$l\n";
+       }
+
+       $api->{output}->{"$s->{type}:$s->{name}"} = 1;
+
+       return $code.code::applyTemplate($structTemplate, $vars, 1);
+}
+
+# output all libraries
+sub exportLibraries {
+       my $api = shift;
+       my $data = $api->{data};
+       my $def = $api->{index}->{'library:<default>'};
+
+       foreach my $obj (grep { $_->{type} eq 'library' } @{$api->{api}}) {
+               next if $api->{output}->{"$obj->{type}:$obj->{name}"};
+               next if $obj->{output} != 1;
+
+               my $library = $api->findTemplateName(api::optionValue('template', 'code:class=library', $obj, $def));
+               my $res = {
+                       ctx => $obj,
+                       library => [],
+                       import => [],
+                       func => [],
+                       init => [],
+                       define => [],
+                       enum => [],
+                       call => [],
+                       seen => {},
+                       template => $library,
+               };
+
+               formatLibrary($api, $obj, $res);
+
+               my $vars = {
+                       %{$api->{vars}},
+                       name => $obj->{name},
+                       imports => join("\n", @{$res->{import}}),
+                       init => join("\n", @{$res->{init}}),
+                       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, 1));
+       }
+}
+
+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, $obj);
+
+               print "gen ".($#list+1)." $obj->{type} $obj->{name}\n" if ($api->{vars}->{verbose} > 0);
+               foreach my $s (@list) {
+                       next if !$s->{output};
+                       next if $api->{output}->{"$s->{type}:$s->{name}"};
+
+                       print "  $s->{name}\n" if ($api->{vars}->{verbose} > 1);
+
+                       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>"};
+               print "* $s->{name}\n" if ($api->{vars}->{verbose} > 1);
+               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 = api::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/notzed.nativez/classes/au/notzed/nativez/Array.java b/src/notzed.nativez/classes/au/notzed/nativez/Array.java
new file mode 100644 (file)
index 0000000..21635a2
--- /dev/null
@@ -0,0 +1,8 @@
+
+package au.notzed.nativez;
+
+public interface Array<T> {
+       long length();
+       T getAtIndex(long i);
+       boolean isEmpty();
+}
diff --git a/src/notzed.nativez/classes/au/notzed/nativez/ByteArray.java b/src/notzed.nativez/classes/au/notzed/nativez/ByteArray.java
new file mode 100644 (file)
index 0000000..0abf605
--- /dev/null
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2022 Michael Zucchi
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package au.notzed.nativez;
+
+import jdk.incubator.foreign.*;
+
+import java.util.AbstractList;
+import java.util.function.Function;
+import java.util.function.BiFunction;
+import java.util.List;
+
+public class ByteArray extends AbstractList<Byte> implements Pointer {
+       final MemorySegment segment;
+
+       private ByteArray(MemorySegment segment) {
+               this.segment = segment;
+       }
+
+       public static ByteArray create(MemorySegment segment) {
+               return new ByteArray(segment);
+       }
+
+       public static ByteArray createArray(MemoryAddress address, long length, ResourceScope scope) {
+               return create(MemorySegment.ofAddress(address, length, scope));
+       }
+
+       public static ByteArray createArray(MemoryAddress address, ResourceScope scope) {
+               return create(MemorySegment.ofAddress(address, Long.MAX_VALUE, scope));
+       }
+
+       public static ByteArray createArray(long length, SegmentAllocator alloc) {
+               return create(alloc.allocateArray(Memory.BYTE, length));
+       }
+
+       public static ByteArray create(String value, SegmentAllocator alloc) {
+               return create(alloc.allocateUtf8String(value));
+       }
+
+       public static ByteArray create(String value, ResourceScope scope) {
+               return create(SegmentAllocator.nativeAllocator(scope).allocateUtf8String(value));
+       }
+
+       public final MemoryAddress address() {
+               return segment.address();
+       }
+
+       public final ResourceScope scope() {
+               return segment.scope();
+       }
+
+       @Override
+       public int size() {
+               return (int)length();
+       }
+
+       @Override
+       public Byte get(int index) {
+               return getAtIndex(index);
+       }
+
+       @Override
+       public Byte set(int index, Byte value) {
+               byte old = getAtIndex(index);
+               setAtIndex(index, value);
+               return old;
+       }
+
+       public long length() {
+               return segment.byteSize() / Memory.BYTE.byteSize();
+       }
+
+       public byte getAtIndex(long index) {
+               return (byte)segment.get(Memory.BYTE, index);
+       }
+
+       public void setAtIndex(long index, byte value) {
+               segment.set(Memory.BYTE, index, value);
+       }
+
+       public String toUtf8String() {
+               return segment.getUtf8String(0L);
+       }
+}
diff --git a/src/notzed.nativez/classes/au/notzed/nativez/DoubleArray.java b/src/notzed.nativez/classes/au/notzed/nativez/DoubleArray.java
new file mode 100644 (file)
index 0000000..9a0bfce
--- /dev/null
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2022 Michael Zucchi
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package au.notzed.nativez;
+
+import jdk.incubator.foreign.*;
+
+import java.util.AbstractList;
+import java.util.function.Function;
+import java.util.function.BiFunction;
+import java.util.List;
+
+public class DoubleArray extends AbstractList<Double> implements Pointer {
+       final MemorySegment segment;
+
+       private DoubleArray(MemorySegment segment) {
+               this.segment = segment;
+       }
+
+       public static DoubleArray create(MemorySegment segment) {
+               return new DoubleArray(segment);
+       }
+
+       public static DoubleArray createArray(MemoryAddress address, long length, ResourceScope scope) {
+               return create(MemorySegment.ofAddress(address, length * Memory.DOUBLE.byteSize(), scope));
+       }
+
+       public static DoubleArray createArray(MemoryAddress address, ResourceScope scope) {
+               return create(MemorySegment.ofAddress(address, Long.MAX_VALUE, scope));
+       }
+
+       public static DoubleArray createArray(long length, SegmentAllocator alloc) {
+               return create(alloc.allocateArray(Memory.DOUBLE, length));
+       }
+
+       public static DoubleArray create(SegmentAllocator alloc, double... values) {
+               return create(alloc.allocateArray(Memory.DOUBLE, values));
+       }
+
+       public final MemoryAddress address() {
+               return segment.address();
+       }
+
+       public final ResourceScope scope() {
+               return segment.scope();
+       }
+
+       @Override
+       public int size() {
+               return (int)length();
+       }
+
+       @Override
+       public Double get(int index) {
+               return getAtIndex(index);
+       }
+
+       @Override
+       public Double set(int index, Double value) {
+               double old = getAtIndex(index);
+               setAtIndex(index, value);
+               return old;
+       }
+
+       public long length() {
+               return segment.byteSize() / Memory.DOUBLE.byteSize();
+       }
+
+       public double getAtIndex(long index) {
+               return segment.getAtIndex(Memory.DOUBLE, index);
+       }
+
+       public void setAtIndex(long index, double value) {
+               segment.setAtIndex(Memory.DOUBLE, index, value);
+       }
+}
diff --git a/src/notzed.nativez/classes/au/notzed/nativez/FloatArray.java b/src/notzed.nativez/classes/au/notzed/nativez/FloatArray.java
new file mode 100644 (file)
index 0000000..e81a57e
--- /dev/null
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2022 Michael Zucchi
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package au.notzed.nativez;
+
+import jdk.incubator.foreign.*;
+
+import java.util.AbstractList;
+import java.util.function.Function;
+import java.util.function.BiFunction;
+import java.util.List;
+
+public class FloatArray extends AbstractList<Float> implements Pointer {
+       final MemorySegment segment;
+
+       private FloatArray(MemorySegment segment) {
+               this.segment = segment;
+       }
+
+       public static FloatArray create(MemorySegment segment) {
+               return new FloatArray(segment);
+       }
+
+       public static FloatArray createArray(MemoryAddress address, long length, ResourceScope scope) {
+               return create(MemorySegment.ofAddress(address, length * Memory.FLOAT.byteSize(), scope));
+       }
+
+       public static FloatArray createArray(MemoryAddress address, ResourceScope scope) {
+               return create(MemorySegment.ofAddress(address, Long.MAX_VALUE, scope));
+       }
+
+       public static FloatArray createArray(long length, SegmentAllocator alloc) {
+               return create(alloc.allocateArray(Memory.FLOAT, length));
+       }
+
+       public static FloatArray create(SegmentAllocator alloc, float... values) {
+               return create(alloc.allocateArray(Memory.FLOAT, values));
+       }
+
+       public final MemoryAddress address() {
+               return segment.address();
+       }
+
+       public final ResourceScope scope() {
+               return segment.scope();
+       }
+
+       @Override
+       public int size() {
+               return (int)length();
+       }
+
+       @Override
+       public Float get(int index) {
+               return getAtIndex(index);
+       }
+
+       @Override
+       public Float set(int index, Float value) {
+               float old = getAtIndex(index);
+               setAtIndex(index, value);
+               return old;
+       }
+
+       public long length() {
+               return segment.byteSize() / Memory.FLOAT.byteSize();
+       }
+
+       public float getAtIndex(long index) {
+               return segment.getAtIndex(Memory.FLOAT, index);
+       }
+
+       public void setAtIndex(long index, float value) {
+               segment.setAtIndex(Memory.FLOAT, index, value);
+       }
+}
diff --git a/src/notzed.nativez/classes/au/notzed/nativez/Frame.java b/src/notzed.nativez/classes/au/notzed/nativez/Frame.java
new file mode 100644 (file)
index 0000000..7ce52b9
--- /dev/null
@@ -0,0 +1,249 @@
+/*
+ * Copyright (C) 2022 Michael Zucchi
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+package au.notzed.nativez;
+
+import jdk.incubator.foreign.*;
+import static jdk.incubator.foreign.ValueLayout.OfAddress;
+
+/**
+ * This is a per-thread stack-based allocator.
+ * <pre>
+ * try (Frame f = Memory.createFrame()) {
+ *             MemorySegment a = f.allocate(size);
+ * }
+ * </pre>
+ * Any memory allocated is freed when the frame is closed.
+ * <p>
+ * This is quite a bit faster than using an arena allocator ... but it also
+ * removes most scope guarantees.
+ */
+public class Frame implements AutoCloseable, SegmentAllocator {
+
+       private final ResourceScope scope;
+       private final SegmentAllocator alloc;
+
+       Frame() {
+               this.scope = ResourceScope.newConfinedScope();
+               this.alloc = SegmentAllocator.newNativeArena(scope);
+       }
+
+       public static Frame frame() {
+               return new Frame();
+       }
+
+       public ResourceScope scope() {
+               return scope;
+       }
+
+       @Override
+       public MemorySegment allocate(long size, long alignment) {
+               return alloc.allocate(size, alignment);
+       }
+
+       @Override
+       public void close() {
+               scope.close();
+       }
+
+       // private final long tos;
+       // private Stack stack;
+       // private ResourceScope overflow;
+
+       // Frame(long tos, Stack stack) {
+       //      this.tos = tos;
+       //      this.stack = stack;
+       // }
+
+       // private static final ResourceScope scope = ResourceScope.newSharedScope(Cleaner.create());
+       // private static final ThreadLocal<Stack> stacks = ThreadLocal.withInitial(() -> new Stack(scope));
+
+       // public static Frame frame() {
+       //      //return stacks.get().createFrame();
+       // }
+
+       // private static class Stack {
+       //      private final MemorySegment stack;
+       //      private long sp;
+       //      private Thread thread = Thread.currentThread();
+
+       //      Stack(ResourceScope scope) {
+       //              stack = MemorySegment.allocateNative(4096, 4096, scope);
+       //              sp = 4096;
+       //      }
+       //      Frame createFrame() {
+       //              return new Frame(sp, this);
+       //      }
+       // }
+
+       // @Override
+       // public MemorySegment allocate(long size, long alignment) {
+       //      if (stack.thread != Thread.currentThread())
+       //              throw new IllegalStateException();
+       //      if (alignment != Long.highestOneBit(alignment))
+       //              throw new IllegalArgumentException();
+       //      if (stack.sp >= size) {
+       //              stack.sp = (stack.sp - size) & ~(alignment - 1);
+       //              return stack.stack.asSlice(stack.sp, size).fill((byte)0);
+       //      } else {
+       //              // or arena allocator?
+       //              if (overflow == null)
+       //                      overflow = ResourceScope.newConfinedScope();
+       //              return MemorySegment.allocateNative(size, alignment, overflow);
+       //      }
+       // }
+
+       // @Override
+       // public void close() {
+       //      stack.sp = tos;
+       //      stack = null;
+       //      if (overflow != null) {
+       //              overflow.close();
+       //              overflow = null;
+       //      }
+       // }
+
+       public MemorySegment allocateInt() {
+               return allocate(Memory.INT);
+       }
+
+       public MemorySegment allocateInt(int count) {
+               return allocate(Memory.INT, count);
+       }
+
+       public MemorySegment allocateLong() {
+               return allocate(Memory.LONG);
+       }
+
+       public MemorySegment allocateLong(int count) {
+               return allocateArray(Memory.LONG, count);
+       }
+
+       public MemorySegment allocatePointer() {
+               return allocate(Memory.POINTER);
+       }
+
+       public MemorySegment allocatePointer(int count) {
+               return allocateArray(Memory.POINTER, count);
+       }
+
+       public MemorySegment allocateArray(OfAddress type, MemoryAddress[] value) {
+               MemorySegment m = allocateArray(type, value.length);
+               for (int i=0;i<value.length;i++)
+                       m.setAtIndex(type, i, value[i]);
+               return m;
+       }
+
+       public MemorySegment copy(byte value) {
+               return allocate(Memory.BYTE, value);
+       }
+
+       public MemorySegment copy(short value) {
+               return allocate(Memory.SHORT, value);
+       }
+
+       public MemorySegment copy(int value) {
+               return allocate(Memory.INT, value);
+       }
+
+       public MemorySegment copy(long value) {
+               return allocate(Memory.LONG, value);
+       }
+
+       public MemorySegment copy(float value) {
+               return allocate(Memory.FLOAT, value);
+       }
+
+       public MemorySegment copy(double value) {
+               return allocate(Memory.DOUBLE, value);
+       }
+
+       public MemorySegment copy(byte[] value) {
+               return allocateArray(Memory.BYTE, value);
+       }
+
+       public MemorySegment copy(int[] value) {
+               return allocateArray(Memory.INT, value);
+       }
+
+       public MemorySegment copy(long[] value) {
+               return allocateArray(Memory.LONG, value);
+       }
+
+       public MemorySegment copy(float[] value) {
+               return allocateArray(Memory.FLOAT, value);
+       }
+
+       public MemorySegment copy(String value) {
+               return allocateUtf8String(value);
+       }
+
+       /*
+       public <T extends Native> MemorySegment copy(T[] array) {
+               MemorySegment mem = allocateAddress(array.length);
+               for (int i = 0; i < array.length; i++)
+                       MemoryAccess.setAddressAtIndex(mem, i, array[i].address());
+               return mem;
+       }
+
+       public <T extends Native> MemorySegment copy(T value) {
+               return copy(value.address());
+       }
+
+       public <T extends Native> MemorySegment copy(MemoryAddress value) {
+               MemorySegment mem = allocateAddress();
+               MemoryAccess.setAddress(mem, value);
+               return mem;
+       }
+        */
+       // create an array pointing to strings
+       public MemorySegment copy(String[] array) {
+               if (array != null) {
+                       MemorySegment list = allocatePointer(array.length);
+                       for (int i = 0; i < array.length; i++) {
+                               list.setAtIndex(Memory.POINTER, i, copy(array[i]));
+                       }
+                       return list;
+               } else {
+                       return Memory.NULL;
+               }
+       }
+
+       public static MemorySegment copy(MemorySegment ctx, String string) {
+               if (string != null) {
+                       SegmentAllocator alloc = SegmentAllocator.nativeAllocator(ctx.scope());
+
+                       return alloc.allocateUtf8String(string);
+               } else {
+                       return Memory.NULL;
+               }
+       }
+
+       public static MemorySegment copy(MemorySegment ctx, String[] array) {
+               System.out.printf("copy array %s\n", array != null ? array.length : "");
+               if (array != null) {
+                       SegmentAllocator alloc = SegmentAllocator.nativeAllocator(ctx.scope());
+                       MemorySegment list = alloc.allocateArray(Memory.POINTER, array.length);
+                       for (int i = 0; i < array.length; i++) {
+                               System.out.printf(" [%d] '%s'\n", i, array[i]);
+                               list.setAtIndex(Memory.POINTER, i, alloc.allocateUtf8String(array[i]));
+                       }
+                       return list;
+               } else {
+                       return Memory.NULL;
+               }
+       }
+}
diff --git a/src/notzed.nativez/classes/au/notzed/nativez/FunctionPointer.java b/src/notzed.nativez/classes/au/notzed/nativez/FunctionPointer.java
new file mode 100644 (file)
index 0000000..dd36722
--- /dev/null
@@ -0,0 +1,7 @@
+
+package au.notzed.nativez;
+
+import jdk.incubator.foreign.NativeSymbol;
+
+public record FunctionPointer<T>(NativeSymbol symbol, T function) {
+}
diff --git a/src/notzed.nativez/classes/au/notzed/nativez/HandleArray.java b/src/notzed.nativez/classes/au/notzed/nativez/HandleArray.java
new file mode 100644 (file)
index 0000000..7434f65
--- /dev/null
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2022 Michael Zucchi
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package au.notzed.nativez;
+
+import jdk.incubator.foreign.*;
+
+import java.util.AbstractList;
+import java.util.function.Function;
+import java.util.function.BiFunction;
+import java.util.List;
+
+/**
+ * A HandleArray is a typed PointerArray
+ * It's flexible and can be used to represent multiple levels of typed arrays, etc.
+ *
+ * The supplied ResourceScope is used for any retrieved entries.
+ */
+public class HandleArray<T extends Pointer> extends AbstractList<T> implements Pointer {
+       public final MemorySegment segment;
+       final ResourceScope scope;
+       BiFunction<MemoryAddress,ResourceScope,T> create;
+
+       private HandleArray(MemorySegment segment, BiFunction<MemoryAddress,ResourceScope,T> create, ResourceScope scope) {
+               this.segment = segment;
+               this.create = create;
+               this.scope = scope;
+       }
+
+       public static <T extends Pointer> HandleArray<T> create(MemorySegment segment, BiFunction<MemoryAddress,ResourceScope,T> create) {
+               return new HandleArray<>(segment, create, segment.scope());
+       }
+
+       public static <T extends Pointer> HandleArray<T> create(MemorySegment segment, BiFunction<MemoryAddress,ResourceScope,T> create, ResourceScope scope) {
+               return new HandleArray<>(segment, create, scope);
+       }
+
+       public static <T extends Pointer> HandleArray<T> createArray(long size, SegmentAllocator alloc, BiFunction<MemoryAddress,ResourceScope,T> create) {
+               return create(alloc.allocateArray(Memory.POINTER, size), create);
+       }
+
+       public static <T extends Pointer> HandleArray<T> createArray(long size, SegmentAllocator alloc, BiFunction<MemoryAddress,ResourceScope,T> create, ResourceScope scope) {
+               return create(alloc.allocateArray(Memory.POINTER, size), create, scope);
+       }
+
+       public static <T extends Pointer> HandleArray<T> createArray(MemoryAddress address, long size, BiFunction<MemoryAddress,ResourceScope,T> create, ResourceScope scope) {
+               return create(MemorySegment.ofAddress(address, size * Memory.POINTER.byteSize(), scope), create);
+       }
+
+       public static <T extends Pointer> HandleArray<T> create(SegmentAllocator alloc, BiFunction<MemoryAddress,ResourceScope,T> create, T... values) {
+               HandleArray<T> array = create(alloc.allocateArray(Memory.POINTER, values.length), create);
+               for (int i=0;i<values.length;i++)
+                       array.setAtIndex(i, values[i]);
+               return array;
+       }
+
+       public HandleArray<T> asSlice(long offset) {
+               return create(segment.asSlice(offset * Memory.POINTER.byteSize()), create, scope);
+       }
+
+       public HandleArray<T> asSlice(long offset, long length) {
+               return create(segment.asSlice(offset * Memory.POINTER.byteSize(), length * Memory.POINTER.byteSize()), create, scope);
+       }
+
+       @Override
+       public final MemoryAddress address() {
+               return segment.address();
+       }
+
+       public final ResourceScope scope() {
+               return segment.scope();
+       }
+
+       @Override
+       public int size() {
+               return (int)length();
+       }
+
+       @Override
+       public T get(int index) {
+               return getAtIndex(index);
+       }
+
+       @Override
+       public T set(int index, T value) {
+               T old = getAtIndex(index);
+               setAtIndex(index, value);
+               return old;
+       }
+
+       public long length() {
+               return segment.byteSize() / Memory.POINTER.byteSize();
+       }
+
+       public T getAtIndex(long index) {
+               MemoryAddress ptr = segment.getAtIndex(Memory.POINTER, index);
+               return ptr != null ? create.apply(ptr, scope) : null;
+       }
+
+       public void setAtIndex(long index, T value) {
+               segment.setAtIndex(Memory.POINTER, index, value != null ? value.address() : MemoryAddress.NULL);
+       }
+}
diff --git a/src/notzed.nativez/classes/au/notzed/nativez/IntArray.java b/src/notzed.nativez/classes/au/notzed/nativez/IntArray.java
new file mode 100644 (file)
index 0000000..0f73c4b
--- /dev/null
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2022 Michael Zucchi
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+package au.notzed.nativez;
+
+import jdk.incubator.foreign.*;
+
+import java.util.AbstractList;
+import java.util.function.Function;
+import java.util.function.BiFunction;
+import java.util.List;
+
+public class IntArray extends AbstractList<Integer> implements Pointer {
+
+       final MemorySegment segment;
+
+       private IntArray(MemorySegment segment) {
+               this.segment = segment;
+       }
+
+       public static IntArray create(MemorySegment segment) {
+               return new IntArray(segment);
+       }
+
+       public static IntArray createArray(MemoryAddress address, long length, ResourceScope scope) {
+               return create(MemorySegment.ofAddress(address, length * Memory.INT.byteSize(), scope));
+       }
+
+       public static IntArray createArray(MemoryAddress address, ResourceScope scope) {
+               return create(MemorySegment.ofAddress(address, Long.MAX_VALUE, scope));
+       }
+
+       public static IntArray createArray(long length, SegmentAllocator alloc) {
+               return create(alloc.allocateArray(Memory.INT, length));
+       }
+
+       public static IntArray create(SegmentAllocator alloc, int... values) {
+               return create(alloc.allocateArray(Memory.INT, values));
+       }
+
+       public IntArray asSlice(long offset) {
+               return create(segment.asSlice(offset * Memory.INT.byteSize()));
+       }
+
+       public IntArray asSlice(long offset, long length) {
+               return create(segment.asSlice(offset * Memory.INT.byteSize(), length * Memory.INT.byteSize()));
+       }
+
+       public final MemorySegment segment() {
+               return segment;
+       }
+
+       public final MemoryAddress address() {
+               return segment.address();
+       }
+
+       public final ResourceScope scope() {
+               return segment.scope();
+       }
+
+       @Override
+       public int size() {
+               return (int)length();
+       }
+
+       @Override
+       public Integer get(int index) {
+               return getAtIndex(index);
+       }
+
+       @Override
+       public Integer set(int index, Integer value) {
+               int old = getAtIndex(index);
+               setAtIndex(index, value);
+               return old;
+       }
+
+       public long length() {
+               return segment.byteSize() / Memory.INT.byteSize();
+       }
+
+       public int getAtIndex(long index) {
+               return segment.getAtIndex(Memory.INT, index);
+       }
+
+       public void setAtIndex(long index, int value) {
+               segment.setAtIndex(Memory.INT, index, value);
+       }
+}
diff --git a/src/notzed.nativez/classes/au/notzed/nativez/LongArray.java b/src/notzed.nativez/classes/au/notzed/nativez/LongArray.java
new file mode 100644 (file)
index 0000000..9885ced
--- /dev/null
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2022 Michael Zucchi
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package au.notzed.nativez;
+
+import jdk.incubator.foreign.*;
+
+import java.util.AbstractList;
+import java.util.function.Function;
+import java.util.function.BiFunction;
+import java.util.List;
+
+public class LongArray extends AbstractList<Long> implements Pointer {
+       final MemorySegment segment;
+
+       public LongArray(MemorySegment segment) {
+               this.segment = segment;
+       }
+
+       public static LongArray create(MemorySegment segment) {
+               return new LongArray(segment);
+       }
+
+       public static LongArray createArray(MemoryAddress address, long length, ResourceScope scope) {
+               return create(MemorySegment.ofAddress(address, length * Memory.LONG.byteSize(), scope));
+       }
+
+       public static LongArray createArray(long length, SegmentAllocator alloc) {
+               return create(alloc.allocateArray(Memory.LONG, length));
+       }
+
+       public static LongArray create(SegmentAllocator alloc, long... values) {
+               return create(alloc.allocateArray(Memory.LONG, values));
+       }
+
+       public final MemoryAddress address() {
+               return segment.address();
+       }
+
+       public final ResourceScope scope() {
+               return segment.scope();
+       }
+
+       @Override
+       public int size() {
+               return (int)length();
+       }
+
+       @Override
+       public Long get(int index) {
+               return getAtIndex(index);
+       }
+
+       @Override
+       public Long set(int index, Long value) {
+               long old = getAtIndex(index);
+               setAtIndex(index, value);
+               return old;
+       }
+
+       public long length() {
+               return segment.byteSize() / Memory.LONG.byteSize();
+       }
+
+       public long getAtIndex(long index) {
+               return segment.getAtIndex(Memory.LONG, index);
+       }
+
+       public void setAtIndex(long index, long value) {
+               segment.setAtIndex(Memory.LONG, index, value);
+       }
+}
diff --git a/src/notzed.nativez/classes/au/notzed/nativez/Memory.java b/src/notzed.nativez/classes/au/notzed/nativez/Memory.java
new file mode 100644 (file)
index 0000000..b52412a
--- /dev/null
@@ -0,0 +1,251 @@
+/*
+ * Copyright (C) 2022 Michael Zucchi
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+package au.notzed.nativez;
+
+import java.lang.invoke.*;
+import java.lang.ref.Cleaner;
+import jdk.incubator.foreign.*;
+import static jdk.incubator.foreign.ValueLayout.*;
+
+import java.util.AbstractList;
+import java.util.function.Function;
+import java.util.function.BiFunction;
+import java.util.List;
+
+public class Memory {
+
+       // probably should be INT8 INT16, etc
+       public static final OfByte BYTE = JAVA_BYTE;
+       public static final OfShort SHORT = JAVA_SHORT.withBitAlignment(16);
+       public static final OfInt INT = JAVA_INT.withBitAlignment(32);
+       public static final OfLong LONG = JAVA_LONG.withBitAlignment(64);
+       public static final OfFloat FLOAT = JAVA_FLOAT.withBitAlignment(32);
+       public static final OfDouble DOUBLE = JAVA_DOUBLE.withBitAlignment(64);
+       public static final OfAddress POINTER = ADDRESS.withBitAlignment(64);
+
+       static final ResourceScope sharedScope = ResourceScope.newSharedScope(); // cleaner?
+       // Note: currently can't create zero-length segments
+       static final MemorySegment NULL = MemorySegment.ofAddress(MemoryAddress.NULL, 1, ResourceScope.globalScope());
+
+       public static ResourceScope sharedScope() {
+               return sharedScope;
+       }
+
+       public static MethodHandle downcall(FunctionDescriptor desc) {
+               return CLinker.systemCLinker().downcallHandle(desc);
+       }
+
+       public static MethodHandle downcall(String name, FunctionDescriptor desc) {
+               return SymbolLookup.loaderLookup().lookup(name)
+                       .map(sym -> CLinker.systemCLinker().downcallHandle(sym, desc))
+                       .orElse(null);
+       }
+
+       public static MethodHandle downcall(NativeSymbol sym, FunctionDescriptor desc) {
+               return CLinker.systemCLinker().downcallHandle(sym, desc);
+       }
+
+       public static MethodHandle downcall(String name, MemoryAddress sym, FunctionDescriptor desc, ResourceScope scope) {
+               return sym != MemoryAddress.NULL
+                       ? CLinker.systemCLinker().downcallHandle(NativeSymbol.ofAddress(name, sym, scope), desc)
+                       : null;
+       }
+
+       public static MethodHandle downcall(String name, FunctionDescriptor desc, Function<String, MemoryAddress> resolve, ResourceScope scope) {
+               MemoryAddress sym = resolve.apply(name);
+               return sym != MemoryAddress.NULL
+                       ? CLinker.systemCLinker().downcallHandle(NativeSymbol.ofAddress(name, sym, scope), desc)
+                       : null;
+       }
+
+       static final MethodHandles.Lookup lookup = MethodHandles.lookup();
+
+       public static NativeSymbol upcall(Object instance, FunctionDescriptor desc, ResourceScope scope) {
+               try {
+                       java.lang.reflect.Method m = instance.getClass().getMethods()[0];
+                       MethodHandle handle = lookup.findVirtual(instance.getClass(), "call", MethodType.methodType(m.getReturnType(), m.getParameterTypes()))
+                               .bindTo(instance);
+                       return CLinker.systemCLinker().upcallStub(handle, desc, scope);
+               } catch (Throwable t) {
+                       throw new AssertionError(t);
+               }
+       }
+
+       public static NativeSymbol upcall(MethodHandles.Lookup lookup, Object instance, String signature, FunctionDescriptor desc, ResourceScope scope) {
+               try {
+                       java.lang.reflect.Method m = instance.getClass().getMethods()[0];
+                       MethodHandle handle = lookup.findVirtual(instance.getClass(), m.getName(), MethodType.fromMethodDescriptorString(signature, Memory.class.getClassLoader()))
+                               .bindTo(instance);
+                       return CLinker.systemCLinker().upcallStub(handle, desc, scope);
+               } catch (Throwable t) {
+                       throw new AssertionError(t);
+               }
+       }
+
+       public static NativeSymbol upcall(MethodHandles.Lookup lookup, Object instance, String method, String signature, FunctionDescriptor desc, ResourceScope scope) {
+               try {
+                       MethodHandle handle = lookup.findVirtual(instance.getClass(), method, MethodType.fromMethodDescriptorString(signature, Memory.class.getClassLoader()))
+                               .bindTo(instance);
+                       return CLinker.systemCLinker().upcallStub(handle, desc, scope);
+               } catch (Throwable t) {
+                       throw new AssertionError(t);
+               }
+       }
+
+       public static Addressable address(Addressable v) {
+               return v != null ? v.address() : MemoryAddress.NULL;
+       }
+
+       public static Addressable address(Pointer v) {
+               return v != null ? v.address() : MemoryAddress.NULL;
+       }
+
+       public static <T> Addressable address(FunctionPointer<T> v) {
+               return v != null ? v.symbol().address() : MemoryAddress.NULL;
+       }
+
+       //public static long length(Pointer v) {
+       //      return v != null ? v.length() : 0;
+       //}
+       public static long length(List<?> list) {
+               return list != null ? list.size() : 0;
+       }
+
+       public static long length(Array<?> list) {
+               return list != null ? list.length() : 0;
+       }
+
+       public static long length(MemorySegment list) {
+               return list != null ? list.byteSize() : 0;
+       }
+
+       public static long length(Object[] list) {
+               return list != null ? list.length : 0;
+       }
+
+       public static long size(MemorySegment s) {
+               return s != null ? s.byteSize() : 0;
+       }
+
+       public static String copyString(MemoryAddress string) {
+               return string != MemoryAddress.NULL ? string.getUtf8String(0) : null;
+       }
+
+       public static MemorySegment copyString(String string, SegmentAllocator alloc) {
+               return (string != null) ? alloc.allocateUtf8String(string) : Memory.NULL;
+       }
+
+       public static String[] copyStringArray(MemoryAddress list, long len) {
+               if (list != MemoryAddress.NULL) {
+                       String[] array = new String[(int)len];
+                       for (int i = 0; i < array.length; i++) {
+                               MemoryAddress fu = list.getAtIndex(Memory.POINTER, i);
+                               array[i] = fu != MemoryAddress.NULL ? fu.getUtf8String(0) : null;
+                       }
+                       return array;
+               } else {
+                       return null;
+               }
+       }
+
+       public static MemorySegment copyStringArray(String[] array, SegmentAllocator alloc) {
+               if (array != null) {
+                       MemorySegment list = alloc.allocateArray(Memory.POINTER, array.length);
+                       for (int i = 0; i < array.length; i++) {
+                               list.setAtIndex(Memory.POINTER, i, array[i] != null ? alloc.allocateUtf8String(array[i]) : MemoryAddress.NULL);
+                       }
+                       return list;
+               } else {
+                       return Memory.NULL;
+               }
+       }
+
+       static void format(StringBuilder sb, GroupLayout layout, MemorySegment s, String prefix) {
+               try {
+                       sb.append(prefix).append(layout.name().orElse("?")).append(" {\n");
+                       for (MemoryLayout m: layout.memberLayouts()) {
+                               if (m instanceof ValueLayout l) {
+                                       var vh = layout.varHandle(MemoryLayout.PathElement.groupElement(l.name().get()));
+                                       sb.append(prefix).append("  ").append(l.name().get()).append(": ").append(vh.get(s)).append("\n");
+                               } else if (m instanceof GroupLayout l) {
+                                       var sh = layout.sliceHandle(MemoryLayout.PathElement.groupElement(l.name().get()));
+                                       var g = (MemorySegment)sh.invokeExact(s);
+
+                                       //sb.append(prefix).append(l.name().get()).append(": {\n");
+                                       format(sb, l, g, prefix + "  ");
+                                       //sb.append(prefix).append("}\n");
+                               } else if (m instanceof jdk.incubator.foreign.SequenceLayout l) {
+                                       var sh = layout.sliceHandle(MemoryLayout.PathElement.groupElement(l.name().get()));
+                                       var g = (MemorySegment)sh.invokeExact(s);
+
+                                       //sb.append(prefix).append(l.name().get()).append(": {\n");
+                                       format(sb, l, g, prefix + "  ");
+                                       //sb.append(prefix).append("}\n");
+                               }
+                       }
+                       sb.append(prefix).append("}\n");
+               } catch (Throwable ex) {
+                       ex.printStackTrace();
+               }
+       }
+
+       static void format(StringBuilder sb, SequenceLayout layout, MemorySegment s, String prefix) {
+               sb.append(prefix).append(layout.name().orElse("?")).append(" [\n");
+               layout.elementCount().ifPresent(len -> {
+                       try {
+                               MemoryLayout el = layout.elementLayout();
+
+                               if (el instanceof ValueLayout l) {
+                                       var vh = layout.varHandle(MemoryLayout.PathElement.sequenceElement());
+
+                                       for (long i = 0; i < len; i++) {
+                                               sb.append(prefix).append("  ").append(vh.get(s, i)).append("\n");
+                                       }
+                               } else if (el instanceof GroupLayout l) {
+                                       MethodHandle eh = layout.sliceHandle(MemoryLayout.PathElement.sequenceElement());
+
+                                       for (long i = 0; i < len; i++) {
+                                               MemorySegment e = (MemorySegment)eh.invokeExact(s, i);
+                                               format(sb, l, e, prefix + "  ");
+                                       }
+                               } else if (el instanceof SequenceLayout l) {
+                                       MethodHandle eh = layout.sliceHandle(MemoryLayout.PathElement.sequenceElement());
+
+                                       for (long i = 0; i < len; i++) {
+                                               MemorySegment e = (MemorySegment)eh.invokeExact(s, i);
+                                               format(sb, l, e, prefix + "  ");
+                                       }
+                               }
+                       } catch (Throwable ex) {
+                               ex.printStackTrace();
+                       }
+               });
+               sb.append(prefix).append("]\n");
+       }
+
+       public static String toString(MemorySegment s, GroupLayout layout) {
+               StringBuilder sb = new StringBuilder();
+
+               long len = s.byteSize() / layout.byteSize();
+               if (len > 1) {
+                       format(sb, MemoryLayout.sequenceLayout(len, layout).withName("array[]"), s, "");
+               } else {
+                       format(sb, layout, s, "");
+               }
+               return sb.toString();
+       }
+}
diff --git a/src/notzed.nativez/classes/au/notzed/nativez/Native.java b/src/notzed.nativez/classes/au/notzed/nativez/Native.java
new file mode 100644 (file)
index 0000000..191728b
--- /dev/null
@@ -0,0 +1,436 @@
+/*
+ * Copyright (C) 2021 Michael Zucchi
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+package au.notzed.nativez;
+
+import java.io.StringReader;
+import java.lang.invoke.*;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.function.Function;
+import java.util.function.IntFunction;
+import jdk.incubator.foreign.*;
+import java.lang.ref.ReferenceQueue;
+import java.lang.ref.WeakReference;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.System.Logger.Level;
+
+/**
+ * Base class for all native objects.
+ * <p>
+ * Handles instantiation and provides helper functions for native access.
+ * <p>
+ * Work in progress.
+ * <p>
+ * For better safety the 'p' field should be the CHandle, and addr() would
+ * call get(). Otherwise one must not release ANY object which might ever
+ * be used again - including any objects returned by the getInfo(). However ...
+ * it's a trade-off and it's a lot of code to change.
+ * <p>
+ * FIXME: there are MemorySegment based accessors for primitive types now, use those
+ */
+public class Native implements Pointer {
+
+       private final MemoryAddress p;
+
+       private final static boolean dolog = true;
+
+       protected Native(MemoryAddress p) {
+               this.p = p;
+       }
+
+       static System.Logger log() {
+               return System.getLogger("notzed.native");
+       }
+
+       public MemoryAddress address() {
+               return p;
+       }
+
+       public ResourceScope scope() {
+               return ResourceScope.globalScope();
+       }
+
+       /* ********************************************************************** */
+       /* GC handling */
+       /* ********************************************************************** */
+       /**
+        * Resource index.
+        */
+       static private final PointerTable map = new PointerTable();
+
+       /**
+        * Reference queue for stale objects.
+        */
+       static private final ReferenceQueue<Native> references = new ReferenceQueue<>();
+
+       private static <T extends Native> T createInstance(Class<T> jtype, MemoryAddress p) {
+               cleanerStep();
+               try {
+                       Class[] params = {MemoryAddress.class};
+                       Constructor<T> cc = jtype.getDeclaredConstructor(params);
+
+                       cc.setAccessible(true);
+
+                       return cc.newInstance(p);
+               } catch (NoSuchMethodException | SecurityException | InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) {
+                       log().log(Level.ERROR, "createInstance", ex);
+                       throw new RuntimeException(ex);
+               }
+       }
+
+       /*
+       public static <T extends Native> T resolve(Class<T> jtype, MemoryAddress p) {
+               T o;
+
+               //if (dolog)
+               log().log(Level.DEBUG, () -> String.format("  resolve $%016x %s", p.offset(), jtype.getName()));
+
+               // Instantiation needs to be synchronized for obvious reasons.
+               synchronized (map) {
+                       CHandle h = (CHandle) map.get(p);
+
+                       if (h == null || (o = jtype.cast(h.get())) == null) {
+                               o = createInstance(jtype, p);
+                               h = new CHandle(o, references, p);
+                               map.putAlways(h);
+                       }
+               }
+               return o;
+               }*/
+       public static <T extends Native> T resolve(Class<T> jtype, MemoryAddress p, Function<MemoryAddress, T> create) {
+               T o;
+               boolean step = false;
+
+               //if (dolog)
+               //      log().log(Level.DEBUG, () -> String.format("  resolv $%016x %s", Memory.toLong(p), create));
+               if (p.toRawLongValue() == 0)
+                       return null;
+
+               // Instantiation needs to be synchronized for obvious reasons.
+               synchronized (map) {
+                       CHandle h = (CHandle)map.get(p);
+
+                       String fmt;
+
+                       if (h == null || (o = jtype.cast(h.get())) == null) {
+                               o = create.apply(p);
+
+                               fmt = h == null ? "  create $%016x %s" : "  replac $%016x %s";
+
+                               h = new CHandle(o, references, p);
+                               map.put(h);
+                               step = true;
+                       } else {
+                               fmt = "  exists $%016x %s";
+                       }
+                       {
+                               T x = o;
+                               log().log(Level.DEBUG, () -> String.format(fmt, p.toRawLongValue(), x.getClass().getName()));
+                       }
+               }
+
+               if (step)
+                       cleanerStep();
+
+               return o;
+       }
+
+       /*
+       public static <T extends Native> void register(T o) {
+               T o;
+               boolean step = false;
+
+               if (dolog)
+                       log().log(Level.DEBUG, () -> String.format("  regist $%016x %s", o.addr().offset(), o.getClass().getName()));
+
+               CHandle h = new CHandle(o, references, o.addr());
+
+               synchronized (map) {
+                       map.put(h);
+                       step = true;
+               }
+
+               if (step)
+                       cleanerStep();
+
+               return o;
+               }*/
+       public void release() {
+               WeakReference<? extends Native> ref;
+
+               synchronized (map) {
+                       ref = map.remove(p);
+               }
+
+               if (ref != null) {
+                       if (dolog)
+                               log().log(Level.DEBUG, () -> String.format("  force  $%016x %s", p.toRawLongValue(), getClass().getName()));
+
+                       ref.enqueue();
+               }
+       }
+
+       public static <T extends Native> void release(T a) {
+               if (a != null)
+                       a.release();
+       }
+
+       public static void release(Native... list) {
+               for (Native o: list)
+                       release(o);
+       }
+
+       static {
+               Thread cleanup = new Thread(Native::cleaner, "Native cleaner");
+               cleanup.setPriority(Thread.MAX_PRIORITY);
+               cleanup.setDaemon(true);
+               cleanup.start();
+       }
+
+       private static void cleanerStep() {
+               try {
+                       CHandle stale = (CHandle)references.poll();
+                       if (stale != null) {
+                               synchronized (map) {
+                                       map.remove(stale.p);
+                               }
+                               stale.release();
+                       }
+               } catch (Throwable ex) {
+               }
+       }
+
+       /**
+        * Cleaner thread.
+        * <p>
+        * This polls the reference queue and releases objects via
+        * their static release method.
+        */
+       private static void cleaner() {
+               if (dolog)
+                       log().log(Level.DEBUG, "Native finaliser started");
+               try {
+                       while (true) {
+                               CHandle stale = (CHandle)references.remove();
+                               do {
+                                       try {
+                                               synchronized (map) {
+                                                       map.remove(stale.p);
+                                               }
+                                               stale.release();
+                                       } catch (Throwable ex) {
+                                       }
+                                       stale = (CHandle)references.poll();
+                               } while (stale != null);
+                       }
+               } catch (InterruptedException ex) {
+               }
+       }
+
+       private static class CHandle extends WeakReference<Native> {
+
+               protected MemoryAddress p;
+               final Class<? extends Native> jtype;
+               CHandle next;
+
+               CHandle(Native referent, ReferenceQueue<Native> references, MemoryAddress p) {
+                       super(referent, references);
+                       this.p = p;
+                       this.jtype = referent.getClass();
+               }
+
+               void release() {
+                       try {
+                               if (p != null) {
+                                       if (dolog)
+                                               log().log(Level.DEBUG, () -> String.format("  releas $%016x %s", p.toRawLongValue(), jtype.getName()));
+
+                                       Method mm = jtype.getDeclaredMethod("release", MemoryAddress.class);
+                                       mm.setAccessible(true);
+                                       mm.invoke(null, p);
+                               }
+                       } catch (NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) {
+                               log().log(Level.ERROR, jtype.getName(), ex);
+                       } finally {
+                               p = null;
+                       }
+               }
+
+               @Override
+               public boolean equals(Object obj) {
+                       return (obj instanceof CHandle) && ((CHandle)obj).p == p;
+               }
+
+               @Override
+               public int hashCode() {
+                       //return p.hashCode();
+                       return hashCode(p);
+               }
+
+               /**
+                * Simple hashcode for native pointers.
+                * <p>
+                * This simply strips the bottom 4 bits from the pointer as
+                * on a 64-bit system the low 3 bits are typically zero and the 4th
+                * isn't very well distributed.
+                *
+                * @param p
+                * @return
+                */
+               public static final int hashCode(long p) {
+                       return (int)p >>> 4;
+               }
+
+               /**
+                * Sigh, memoryaddress has a miserable hashCode(), it's even worse than Long.hashCode()
+                */
+               public static final int hashCode(MemoryAddress p) {
+                       return p.hashCode() >>> 5;
+               }
+       }
+
+       public static void debugFlushAll() {
+               for (int i = 0; i < 3; i++) {
+                       try {
+                               System.gc();
+                               Thread.sleep(100);
+                       } catch (InterruptedException x) {
+                       }
+                       CHandle stale = (CHandle)references.poll();
+                       while (stale != null) {
+                               try {
+                                       synchronized (map) {
+                                               map.remove(stale.p);
+                                       }
+                                       stale.release();
+                               } catch (Throwable ex) {
+                               }
+                               stale = (CHandle)references.poll();
+                       }
+               }
+       }
+
+       public static void debugDumpReachable(String title) {
+               synchronized (map) {
+                       System.out.println(title);
+                       for (CHandle h: map.table) {
+                               while (h != null) {
+                                       Native o = h.get();
+                                       System.out.printf(" $%016x: %s %-40s %s\n",
+                                               h.p.toRawLongValue(),
+                                               o == null ? "dead" : "live",
+                                               h.jtype.getName(),
+                                               o);
+                                       h = h.next;
+                               }
+                       }
+               }
+       }
+
+       /**
+        * Lightweight pointer hashtable.
+        * <p>
+        * This serves two purposes:
+        * <ol>
+        * <li>Track and resolve unique objects based on memory address;
+        * <li>Hold hard references to the WeakReference as required by the gc system.
+        * </ol>
+        * <p>
+        * CHandle's are chained directly from the index table, the p field
+        * is used as a key directly, and hash values are not cached. This combines
+        * to save significant memory per node.
+        */
+       private static class PointerTable {
+
+               int mask = 63;
+               int size = 0;
+               CHandle[] table = new CHandle[64];
+
+               private void resize(int length) {
+                       CHandle[] ntable = new CHandle[length];
+                       int nmask = length - 1;
+
+                       for (int i = 0; i < table.length; i++) {
+                               CHandle h = table[i];
+
+                               while (h != null) {
+                                       CHandle n = h.next;
+                                       int k = h.hashCode() & nmask;
+
+                                       h.next = ntable[k];
+                                       ntable[k] = h;
+
+                                       h = n;
+                               }
+                       }
+
+                       table = ntable;
+                       mask = nmask;
+               }
+
+               public CHandle put(CHandle h) {
+                       CHandle o = remove(h.p);
+
+                       putAlways(h);
+
+                       return o;
+               }
+
+               public void putAlways(CHandle h) {
+                       if (size > table.length)
+                               resize(table.length * 2);
+
+                       int i = h.hashCode() & mask;
+
+                       h.next = table[i];
+                       table[i] = h;
+                       size += 1;
+               }
+
+               public CHandle get(MemoryAddress p) {
+                       int i = CHandle.hashCode(p) & mask;
+                       CHandle h = table[i];
+
+                       while (h != null && !h.p.equals(p))
+                               h = h.next;
+                       return h;
+               }
+
+               public CHandle remove(MemoryAddress p) {
+                       int i = CHandle.hashCode(p) & mask;
+                       CHandle h = table[i];
+                       CHandle a = null;
+
+                       while (h != null && !h.p.equals(p)) {
+                               a = h;
+                               h = h.next;
+                       }
+                       if (h != null) {
+                               if (a != null)
+                                       a.next = h.next;
+                               else
+                                       table[i] = h.next;
+                               size -= 1;
+                       }
+
+                       return h;
+               }
+       }
+}
diff --git a/src/notzed.nativez/classes/au/notzed/nativez/NativeZ.java b/src/notzed.nativez/classes/au/notzed/nativez/NativeZ.java
deleted file mode 100644 (file)
index 7b3c266..0000000
+++ /dev/null
@@ -1,552 +0,0 @@
-/*
- * Copyright (C) 2018 Michael Zucchi
- *
- * This file is part of nativez <https://www.zedzone.space/software/nativez.html>
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions are
- * met:
- *
- *     (1) Redistributions of source code must retain the above copyright
- *     notice, this list of conditions and the following disclaimer.
- *
- *     (2) Redistributions in binary form must reproduce the above copyright
- *     notice, this list of conditions and the following disclaimer in
- *     the documentation and/or other materials provided with the
- *     distribution.
- *
- *     (3)The name of the author may not be used to
- *     endorse or promote products derived from this software without
- *     specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
- * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
- * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
- * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
- * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
- * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
- * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
- * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
- * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
- * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
- * POSSIBILITY OF SUCH DAMAGE.
- *
- */
-package au.notzed.nativez;
-
-import java.lang.ref.ReferenceQueue;
-import java.lang.ref.WeakReference;
-import java.lang.reflect.Constructor;
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
-import java.util.logging.Level;
-import java.util.logging.Logger;
-
-/**
- * Native object manager.
- * <p>
- */
-public abstract class NativeZ {
-
-       private final static boolean dolog = false;
-
-       private static Logger log() {
-               return Logger.getLogger("notzed.nativez");
-       }
-
-       /**
-        * Globally system unique key to a native resource.
-        * <p>
-        * This will typically be a C pointer.
-        */
-       final long p;
-
-       /**
-        * Resource index.
-        */
-       static private final PointerTable map = new PointerTable();
-
-       /**
-        * Reference queue for stale objects.
-        */
-       static private final ReferenceQueue<NativeZ> references = new ReferenceQueue<>();
-
-       static {
-               Thread cleanup = new Thread(NativeZ::cleaner, "NativeZ cleaner");
-               cleanup.setPriority(Thread.MAX_PRIORITY);
-               cleanup.setDaemon(true);
-               cleanup.start();
-       }
-
-       /**
-        * Create a new object.
-        *
-        * @param p
-        */
-       protected NativeZ(long p) {
-               this.p = p;
-       }
-
-       /**
-        * Retrieve the resource key.
-        *
-        * @return
-        */
-       public long getP() {
-               return p;
-       }
-
-       /**
-        * Natives are considered equal if their p values are equal.
-        *
-        * @param obj
-        * @return
-        */
-       @Override
-       public boolean equals(Object obj) {
-               return (obj instanceof NativeZ) && p == ((NativeZ) obj).p;
-       }
-
-       @Override
-       public int hashCode() {
-               return CHandle.hashCode(p);
-       }
-
-       /**
-        * Internally create a new instance.
-        * <p>
-        * The module must be open to notzed.nativez for this to work.
-        *
-        * @param <T>
-        * @param jtype Type of object to create.
-        * @param p The reference handle.
-        * @return A newly created object.
-        * @throws RuntimeException if the object cannot be instantiated.
-        */
-       private static <T extends NativeZ> T createInstance(Class<T> jtype, long p) {
-               cleanerStep();
-               try {
-                       Class[] params = {Long.TYPE};
-                       Constructor<T> cc = jtype.getDeclaredConstructor(params);
-
-                       cc.setAccessible(true);
-
-                       return cc.newInstance(p);
-               } catch (NoSuchMethodException | SecurityException | InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) {
-                       log().log(Level.SEVERE, null, ex);
-                       throw new RuntimeException(ex);
-               }
-       }
-
-       /**
-        * Register a new managed resource.
-        * <p>
-        * Use this function if the object does not use the default constructor
-        * or has been created separately.
-        * <p>
-        * Do not use this for internally reference counted resources.
-        *
-        * @param <T>
-        * @param o The object to register.
-        * @return o is returned.
-        */
-       public static <T extends NativeZ> T register(T o) {
-               CHandle h = new CResourceHandle(o, references, o.p);
-
-               synchronized (map) {
-                       map.putAlways(h);
-               }
-
-               return o;
-       }
-
-       /**
-        * Create a new managed object for a resource.
-        * <p>
-        * Use this function when possible if using the default constructor
-        * as it saves a native to managed transition and also allows
-        * for concurrent instantiation.
-        * <p>
-        * Do not call this if the resource may have already been registered or
-        * if it is internally reference counted, use {@link resolve} instead.
-        *
-        * @param jtype managed object type.
-        * @param p unique resource key.
-        * @return A newly created object.
-        * @throws RuntimeException if the object cannot be instantiated.
-        */
-       public static <T extends NativeZ> T create(Class<T> jtype, long p) {
-               T o;
-               CHandle h;
-
-               if (dolog)
-                       log().fine(() -> String.format("  create $%016x %s", p, jtype.getName()));
-
-               o = createInstance(jtype, p);
-               h = new CResourceHandle(o, references, p);
-
-               synchronized (map) {
-                       map.putAlways(h);
-               }
-
-               return o;
-       }
-
-       /**
-        * Create a new or resolve an existing managed object for a resource.
-        * <p>
-        * If the object already exists it is simply returned otherwise
-        * a new instance is created and registered. It is safe to call this
-        * instead of {@link create} for all cases.
-        * <p>
-        * If the resources are managed natively via reference counting then this
-        * function must be used to ensure a unique instance is created.
-        * <p>
-        * This may be called on the same resource key via multiple threads and
-        * is internally synchronised.
-        *
-        * @param jtype type of object to createInstance.
-        * @param p native pointer (or other native key).
-        * @return A unique Java object which manages the native resource.
-        */
-       public static <T extends NativeZ> T resolve(Class<T> jtype, long p) {
-               T o;
-
-               if (dolog)
-                       log().fine(() -> String.format("  resolve $%016x %s", p, jtype.getName()));
-
-               // Instantiation needs to be synchronized for obvious reasons.
-               synchronized (map) {
-                       CHandle h = (CHandle) map.get(p);
-
-                       if (h == null || (o = jtype.cast(h.get())) == null) {
-                               o = createInstance(jtype, p);
-                               h = new CResourceHandle(o, references, p);
-                               map.putAlways(h);
-                       }
-               }
-               return o;
-       }
-
-       /**
-        * Create a non-managed object for a resource.
-        * <p>
-        * This is a way to access internal members and other data which is
-        * not allocated explicitly.
-        * <p>
-        * It is up to the caller to ensure the reference remains valid while in
-        * use, for example by retaining a reference to it's container.
-        * <p>
-        * This may be called on the same resource key via multiple threads and
-        * is internally synchronised.
-        *
-        * @param jtype object type
-        * @param p unqiue resource key. As this must be globaly unique it
-        * cannot reference embedded structures that begin at the start of
-        * a container. A workaround is to offset the value and account for
-        * that in the implementation.
-        * @return A unique Java object for accessing the native resource.
-        */
-       public static <T extends NativeZ> T refer(Class<T> jtype, long p) {
-               T o;
-
-               if (dolog)
-                       log().fine(() -> String.format("  refer  $%016x %s", p, jtype.getName()));
-
-               synchronized (map) {
-                       CHandle h = (CHandle) map.get(p);
-
-                       if (h == null || (o = jtype.cast(h.get())) == null) {
-                               o = createInstance(jtype, p);
-                               h = new CReferenceHandle(o, references, p);
-                               map.putAlways(h);
-                       }
-               }
-
-               return o;
-       }
-
-       /**
-        * Explicitly release a resource.
-        * <p>
-        * The object is advanced to the refernce queue immediately.
-        * <p>
-        * Applications MUST NOT access this object afterwards, even if
-        * they retain references to it.
-        */
-       public void release() {
-               WeakReference<? extends NativeZ> ref;
-
-               synchronized (map) {
-                       ref = map.remove(p);
-               }
-
-               if (ref != null) {
-                       if (dolog)
-                               log().fine(() -> String.format("  force  $%016x %s", p, getClass().getName()));
-
-                       ref.enqueue();
-               }
-       }
-
-       /**
-        * Explicitly release multiple resources.
-        * <p>
-        * A convenience function to release multiple resources explicitly.
-        * <p>
-        * null objects are safely ignored.
-        *
-        * @param list
-        */
-       public static void release(NativeZ... list) {
-               for (NativeZ o : list) {
-                       if (o != null) {
-                               o.release();
-                       }
-               }
-       }
-
-       @Override
-       public String toString() {
-               return String.format("[$%016x: %s]", p, getClass().getSimpleName());
-       }
-
-       private static void cleanerStep() {
-               try {
-                       CHandle stale = (CHandle) references.poll();
-                       if (stale != null) {
-                               synchronized (map) {
-                                       map.remove(stale.p);
-                               }
-                               stale.release();
-                       }
-               } catch (Throwable ex) {
-               }
-       }
-
-       /**
-        * Cleaner thread.
-        * <p>
-        * This polls the reference queue and releases objects via
-        * their static release method.
-        */
-       private static void cleaner() {
-               if (dolog)
-                       log().info("Native finaliser started");
-               try {
-                       while (true) {
-                               CHandle stale = (CHandle) references.remove();
-                               do {
-                                       try {
-                                               synchronized (map) {
-                                                       map.remove(stale.p);
-                                               }
-                                               stale.release();
-                                       } catch (Throwable ex) {
-                                       }
-                                       stale = (CHandle) references.poll();
-                               } while (stale != null);
-                       }
-               } catch (InterruptedException ex) {
-               }
-       }
-
-       /**
-        * Base class for a reference handle.
-        * <p>
-        * This is a {@link WeakReference} that maintains the hashtable bucket
-        * chain and enough information to release the resource without referencing
-        * it.
-        */
-       abstract private static class CHandle extends WeakReference<NativeZ> {
-
-               protected long p;
-               final Class<? extends NativeZ> jtype;
-               CHandle next;
-
-               public CHandle(NativeZ referent, ReferenceQueue<NativeZ> references, long p) {
-                       super(referent, references);
-                       this.p = p;
-                       this.jtype = referent.getClass();
-               }
-
-               /**
-                * Actually release the native resource.
-                */
-               abstract void release();
-
-               @Override
-               public boolean equals(Object obj) {
-                       return (obj instanceof CHandle) && ((CHandle) obj).p == p;
-               }
-
-               @Override
-               public int hashCode() {
-                       return hashCode(p);
-               }
-
-               /**
-                * Simple hashcode for native pointers.
-                * <p>
-                * This simply strips the bottom 4 bits from the pointer as
-                * on a 64-bit system the low 3 bits are typically zero and the 4th
-                * isn't very well distributed.
-                *
-                * @param p
-                * @return
-                */
-               public static final int hashCode(long p) {
-                       return (int) p >>> 4;
-               }
-       }
-
-       /**
-        * A dummy reference handle.
-        * <p>
-        * The {@link release} function simply clears the pointer.
-        *
-        * @todo This may not be needed as it's only purpose is to allow
-        * unique global references across managed and unmanaged resources and
-        * multiple instances of unmanaged objects are safe. However, jjmpeg has
-        * some strange cases so it needs confirming.
-        */
-       private static class CReferenceHandle extends CHandle {
-
-               public CReferenceHandle(NativeZ referent, ReferenceQueue<NativeZ> references, long p) {
-                       super(referent, references, p);
-               }
-
-               @Override
-               void release() {
-                       if (dolog)
-                               log().fine(() -> String.format(" %016x: Ignoring release %s", p, jtype.getSimpleName()));
-                       p = 0;
-               }
-       }
-
-       /**
-        * A CResourceHandle is a weak reference that maintains enough
-        * non-object-referring data to be able to release native resources.
-        * <p>
-        * It does this by saving the native pointer `p' and the
-        * object class which manages it. A static method `void release(long)'
-        * on the specific class is used to release the native resource when
-        * it is no longer reachable or has been explicitly released.
-        */
-       private static class CResourceHandle extends CHandle {
-
-               public CResourceHandle(NativeZ referent, ReferenceQueue<NativeZ> references, long p) {
-                       super(referent, references, p);
-               }
-
-               @Override
-               void release() {
-                       try {
-                               if (p != 0) {
-                                       if (dolog)
-                                               log().fine(() -> String.format("  reles  $%016x %s", p, jtype.getName()));
-
-                                       Method mm = jtype.getDeclaredMethod("release", Long.TYPE);
-                                       mm.setAccessible(true);
-                                       mm.invoke(null, p);
-                               }
-                       } catch (NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) {
-                               if (dolog)
-                                       log().log(Level.SEVERE, jtype.getName(), ex);
-                               ex.printStackTrace(System.out);
-                       } finally {
-                               p = 0;
-                       }
-               }
-       }
-
-       /**
-        * Lightweight pointer hashtable.
-        * <p>
-        * This serves two purposes:
-        * <ol>
-        * <li>Track and resolve unique objects based on memory address;
-        * <li>Hold hard references to the WeakReference as required by the gc system.
-        * </ol>
-        * <p>
-        * CHandle's are chained directly from the index table, the p field
-        * is used as a key directly, and hash values are not cached. This combines
-        * to save significant memory per node.
-        */
-       private static class PointerTable {
-
-               int mask = 63;
-               int size = 0;
-               CHandle[] table = new CHandle[64];
-
-               private void resize(int length) {
-                       CHandle[] ntable = new CHandle[length];
-                       int nmask = length - 1;
-
-                       for (int i = 0; i < table.length; i++) {
-                               CHandle h = table[i];
-
-                               while (h != null) {
-                                       CHandle n = h.next;
-                                       int k = h.hashCode() & nmask;
-
-                                       h.next = ntable[k];
-                                       ntable[k] = h;
-
-                                       h = n;
-                               }
-                       }
-
-                       table = ntable;
-                       mask = nmask;
-               }
-
-               public CHandle put(CHandle h) {
-                       CHandle o = remove(h.p);
-
-                       putAlways(h);
-
-                       return o;
-               }
-
-               public void putAlways(CHandle h) {
-                       if (size > table.length * 2)
-                               resize(table.length * 2);
-
-                       int i = h.hashCode() & mask;
-
-                       h.next = table[i];
-                       table[i] = h;
-                       size += 1;
-               }
-
-               public CHandle get(long p) {
-                       int i = CHandle.hashCode(p) & mask;
-                       CHandle h = table[i];
-
-                       while (h != null && h.p != p)
-                               h = h.next;
-                       return h;
-               }
-
-               public CHandle remove(long p) {
-                       int i = CHandle.hashCode(p) & mask;
-                       CHandle h = table[i];
-                       CHandle a = null;
-
-                       while (h != null && h.p != p) {
-                               a = h;
-                               h = h.next;
-                       }
-                       if (h != null) {
-                               if (a != null)
-                                       a.next = h.next;
-                               else
-                                       table[i] = h.next;
-                               size -= 1;
-                       }
-
-                       return h;
-               }
-       }
-}
diff --git a/src/notzed.nativez/classes/au/notzed/nativez/Pointer.java b/src/notzed.nativez/classes/au/notzed/nativez/Pointer.java
new file mode 100644 (file)
index 0000000..5b003dc
--- /dev/null
@@ -0,0 +1,18 @@
+
+package au.notzed.nativez;
+
+import jdk.incubator.foreign.MemoryAddress;
+import jdk.incubator.foreign.ResourceScope;
+
+/**
+ * Because you can't implement foriegn.Addressable for some silly reason
+ */
+public interface Pointer {
+       MemoryAddress address();
+       default ResourceScope scope() {
+               return ResourceScope.globalScope();
+       }
+       //default long length() {
+       //      return 1;
+       //}
+}
diff --git a/src/notzed.nativez/classes/au/notzed/nativez/PointerArray.java b/src/notzed.nativez/classes/au/notzed/nativez/PointerArray.java
new file mode 100644 (file)
index 0000000..cdc4189
--- /dev/null
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2022 Michael Zucchi
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package au.notzed.nativez;
+
+import jdk.incubator.foreign.*;
+
+import java.util.AbstractList;
+import java.util.function.Function;
+import java.util.function.BiFunction;
+import java.util.List;
+
+public class PointerArray extends AbstractList<MemoryAddress> implements Pointer {
+       final MemorySegment segment;
+
+       private PointerArray(MemorySegment segment) {
+               this.segment = segment;
+       }
+
+       public static PointerArray create(MemorySegment segment) {
+               return new PointerArray(segment);
+       }
+
+       public static PointerArray createArray(MemoryAddress address, long length, ResourceScope scope) {
+               return create(MemorySegment.ofAddress(address, length, scope));
+       }
+
+       public static PointerArray createArray(long length, SegmentAllocator alloc) {
+               return create(alloc.allocateArray(Memory.POINTER, length));
+       }
+
+       public static PointerArray create(Frame alloc, MemoryAddress... values) {
+               return create(alloc.allocateArray(Memory.POINTER, values));
+       }
+
+       public final MemoryAddress address() {
+               return segment.address();
+       }
+
+       public final ResourceScope scope() {
+               return segment.scope();
+       }
+
+       @Override
+       public int size() {
+               return (int)length();
+       }
+
+       @Override
+       public MemoryAddress get(int index) {
+               return getAtIndex(index);
+       }
+
+       @Override
+       public MemoryAddress set(int index, MemoryAddress value) {
+               MemoryAddress old = getAtIndex(index);
+               setAtIndex(index, value);
+               return old;
+       }
+
+       public long length() {
+               return segment.byteSize() / Memory.POINTER.byteSize();
+       }
+
+       public MemoryAddress getAtIndex(long index) {
+               return segment.getAtIndex(Memory.POINTER, index);
+       }
+
+       public void setAtIndex(long index, MemoryAddress value) {
+               segment.setAtIndex(Memory.POINTER, index, value);
+       }
+}
diff --git a/src/notzed.nativez/classes/au/notzed/nativez/ShortArray.java b/src/notzed.nativez/classes/au/notzed/nativez/ShortArray.java
new file mode 100644 (file)
index 0000000..f93599d
--- /dev/null
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2022 Michael Zucchi
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package au.notzed.nativez;
+
+import jdk.incubator.foreign.*;
+
+import java.util.AbstractList;
+import java.util.function.Function;
+import java.util.function.BiFunction;
+import java.util.List;
+
+public class ShortArray extends AbstractList<Short> implements Pointer {
+       final MemorySegment segment;
+
+       private ShortArray(MemorySegment segment) {
+               this.segment = segment;
+       }
+
+       public static ShortArray create(MemorySegment segment) {
+               return new ShortArray(segment);
+       }
+
+       public static ShortArray createArray(MemoryAddress address, long length, ResourceScope scope) {
+               return create(MemorySegment.ofAddress(address, length * Memory.SHORT.byteSize(), scope));
+       }
+
+       public static ShortArray createArray(MemoryAddress address, ResourceScope scope) {
+               return create(MemorySegment.ofAddress(address, Long.MAX_VALUE, scope));
+       }
+
+       public static ShortArray createArray(long length, SegmentAllocator alloc) {
+               return create(alloc.allocateArray(Memory.SHORT, length));
+       }
+
+       public static ShortArray create(SegmentAllocator alloc, short... values) {
+               return create(alloc.allocateArray(Memory.SHORT, values));
+       }
+
+       public final MemoryAddress address() {
+               return segment.address();
+       }
+
+       public final ResourceScope scope() {
+               return segment.scope();
+       }
+
+       @Override
+       public int size() {
+               return (int)length();
+       }
+
+       @Override
+       public Short get(int index) {
+               return getAtIndex(index);
+       }
+
+       @Override
+       public Short set(int index, Short value) {
+               short old = getAtIndex(index);
+               setAtIndex(index, value);
+               return old;
+       }
+
+       public long length() {
+               return segment.byteSize() / Memory.SHORT.byteSize();
+       }
+
+       public short getAtIndex(long index) {
+               return segment.getAtIndex(Memory.SHORT, index);
+       }
+
+       public void setAtIndex(long index, short value) {
+               segment.setAtIndex(Memory.SHORT, index, value);
+       }
+}
index 84a8720..7edcf0a 100644 (file)
@@ -1,6 +1,5 @@
 
 module notzed.nativez {
-       requires java.logging;
-
+       requires transitive jdk.incubator.foreign;
        exports au.notzed.nativez;
 }
diff --git a/src/notzed.nativez/jni/jni.make b/src/notzed.nativez/jni/jni.make
deleted file mode 100644 (file)
index f2cad7d..0000000
+++ /dev/null
@@ -1,21 +0,0 @@
-
-notzed.nativez_JNI_LIBRARIES = nativez
-
-nativez_SOURCES = nativez-jni.c nativez-$(TARGET:-amd64=).c
-nativez_CPPFLAGS = -I$(notzed.nativez_jnidir)
-nativez_CFLAGS = -Wmissing-prototypes
-nativez_HEADERS = nativez.h
-nativez_DEFS = nativez-jni.def
-
-nativez_makedep=$(notzed.nativez_objdir)/$(1).o: $(notzed.nativez_jnidir)/$(1).h
-
-$(foreach def,$(nativez_DEFS),$(eval $(call nativez_makedep,$(def:.def=))))
-
-$(notzed.nativez_jnidir)/%.h: src/notzed.nativez/jni/%.def src/notzed.nativez/jni/nativez-gen
-       @install -d $(@D)
-       src/notzed.nativez/jni/nativez-gen -J $< > $@ || ( rm $@ ; exit 1)
-
-# include tool in jmod
-nativez_COMMANDS=nativez-gen
-$(notzed.nativez_bindir)/nativez-gen: src/notzed.nativez/jni/nativez-gen
-       install -DC $< $@
diff --git a/src/notzed.nativez/jni/nativez-gen b/src/notzed.nativez/jni/nativez-gen
deleted file mode 100755 (executable)
index 5a6a176..0000000
+++ /dev/null
@@ -1,199 +0,0 @@
-#!/usr/bin/perl
-
-# -*- Mode:perl; perl-indent-level:4;tab-width:4; -*-
-
-# usage [ options ]* def-file.def
-
-#  -b basedir  Base directory for any 'header' sections.  It is added to the include path.
-#  --func-name  Function table variable name.  Default is `fn'.
-#  --java-name  Java table variable name.  Default is 'java'.
-#  -J          Create #defines to map java names to the variable name.
-#  --java-long  Create long java names for object types.  Sort of like mangled jni names.
-
-# All other options and arguments are passed to cproto.
-
-$args = "$0 ".join " ", @ARGV;
-$java_name = "java";
-$func_name = "fn";
-$doJ = 0;
-$doLong = 0;
-$cprotoArgs = "";
-
-while ($#ARGV > 0) {
-       my $cmd = shift;
-       if ($cmd eq "-b") {
-               $basedir = shift;
-               $cprotoArgs .= " -I '${basedir}'";
-       } elsif ($cmd eq "--func-name") {
-               $func_name = shift;
-       } elsif ($cmd eq "--java-name") {
-               $java_name = shift;
-       } elsif ($cmd eq "-J") {
-               $doJ = 1;
-       } elsif ($cmd eq "--java-long") {
-               $doLong = 1;
-       } else {
-               $cprotoArgs .= " '$cmd'";
-       }
-}
-$def = shift;
-
-$header = "";
-$librart = "";
-@functions = ();
-@headers = ();
-%proto = ();
-$last_library = "";
-$mode = "";
-
-open (IN,"<$def") || die ("opening $def");
-
-while (<IN>) {
-       chop;
-       s/#.*$//;
-       if (m/^header (.*) (.*) \{/) {
-               $library = $1;
-               $header = $2;
-               push @headers, $header;
-
-               print STDERR "cproto -q -x ${cprotoArgs} $basedir/$header\n";
-               %proto = ();
-               open (PROTO, "cproto -q -x ${cprotoArgs} $basedir/$header|") || die("cproto failed");
-
-               while (<PROTO>) {
-                       chop;
-                       $cproto = $_;
-                       if (m/([a-zA-Z0-9_]*)\(/) {
-                               $func = $1;
-                               $proto{$func} = $cproto;
-                       }
-               }
-               close PROTO;
-
-               if ($library ne $last_library) {
-                       push @functions, "#$library";
-                       $last_library = $library;
-               }
-               $mode = "proto";
-       } elsif (m/^java (.*) (.*) \{/) {
-               $name = $1;
-               $class = $2;
-               $mode = "java";
-               push @classes,"#$name:$class";
-       } elsif (m/^}$/) {
-               $mode = "";
-       } elsif ($mode eq "proto") {
-               if (m/\s*([a-zA-Z0-9_]+)/) {
-                       my $func= $1;
-                       my $cproto = $proto{$func};
-
-                       die ("No function $func in $header") if !defined($cproto);
-
-                       push @functions, $cproto;
-               }
-       } elsif ($mode eq "java") {
-               # maps to strict "[static|],name,(arguments)"
-               if (m/(static)? *([\w<>]*) *, *([\[\w\$<>\(\)\/;]*)/) {
-                       push @classes,"$1,$2,$3";
-               }
-       }
-}
-close IN;
-
-print "/* This file was autogenerated by: */\n";
-print "/* $args */\n";
-
-if ($#headers >= 0) {
-       # Handle C prototype mappings
-       foreach $h (@headers) {
-               print "#include <$h>\n";
-       }
-
-       print "static struct functable {\n";
-       foreach $func (@functions) {
-               if ($func =~ m/^\#(.+)/) {
-                       print "\t/* lib$1 */\n";
-               } else {
-                       $dfunc = $func;
-                       $dfunc =~ s/([a-zA-Z0-9_]*)(\(.*;)/(*\1)\2/;
-                       print "\t$dfunc\n";
-               }
-       }
-       print "} ${func_name};\n";
-       print "static const char *${func_name}_names =\n";
-       foreach $func (@functions) {
-               if ($func =~ m/^(\#.+)/) {
-                       print "\t\"$func\\0\"\n";
-               } else {
-                       $func =~ m/([a-zA-Z0-9_]*)\(/;
-                       print "\t\"$1\\0\"\n";
-               }
-       }
-       print "\t;\n";
-}
-
-if ($#classes >= 0) {
-       # Handle java defines
-       $name = "";
-       $class = "";
-       print "static struct {\n";
-       foreach $func (@classes) {
-               if ($func =~ m/^#(.+):(.+)/) {
-                       $name = $1;
-                       $class = $2;
-                       print "\t// $class\n";
-                       print "\tjclass $name"."_classid;\n";
-                       printf "#define $name"."_classid ${java_name}.$name"."_classid\n" if $doJ;
-               } elsif ($func =~ m/(.*),(.+),\((.*)\).*/) {
-                       my $method = $2;
-                       my $args = $3;
-
-                       if ($doLong) {
-                               while ($args =~ m/(.*)L([^;]*);(.*)/) {
-                                       my $a = $1;
-                                       my $b = $2;
-                                       my $c = $3;
-
-                                       $b =~ s,/,_,g;
-                                       $args = $a."_".$b."_".$c;
-                               }
-                       } else {
-                               $args =~ s/L[^;]*;/l/g;
-                               $args =~ tr/A-Z/a-z/;
-                       }
-                       $args =~ s/\[/_/g;
-
-                       $method =~ s/<init>/new/;
-
-                       print "\tjmethodID $name"."_$method"."_$args;\n";
-                       print "#define $name"."_$method"."_$args ${java_name}.$name"."_$method"."_$args\n" if $doJ;
-               } elsif ($func =~ m/(.*),(.+),(.+)/) {
-                       my $field = $2;
-                       my $type = $3;
-
-                       print "\tjfieldID $name"."_$field;\n";
-                       print "#define $name"."_$field ${java_name}.$name"."_$field\n" if $doJ;
-               } else {
-                       die("can't parse java signature $func");
-               }
-       }
-       print "} ${java_name};\n";
-       print "static const char *${java_name}_names =\n";
-       foreach $func (@classes) {
-               if ($func =~ m/^#(.+):(.+)/) {
-                       $name = $1;
-                       $class = $2;
-
-                       print "\t\"#$class\\0\"\n";
-               } elsif ($func =~ m/static,(.*),(.*\(.*\).*)/) {
-                       print "\t\":$1\\0$2\\0\"\n";
-               } elsif ($func =~ m/,(.*),(.*\(.*\).*)/) {
-                       print "\t\".$1\\0$2\\0\"\n";
-               } elsif ($func =~ m/static,(.*),(.*)/) {
-                       print "\t\";$1\\0$2\\0\"\n";
-               } elsif ($func =~ m/,(.*),(.*)/) {
-                       print "\t\",$1\\0$2\\0\"\n";
-               }
-       }
-       print "\t;\n";
-}
diff --git a/src/notzed.nativez/jni/nativez-jni.c b/src/notzed.nativez/jni/nativez-jni.c
deleted file mode 100644 (file)
index 77c553c..0000000
+++ /dev/null
@@ -1,368 +0,0 @@
-/*
- * Copyright (C) 2017,2019 Michael Zucchi
- *
- * This file is part of nativez <https://www.zedzone.space/software/nativez.html>
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions are
- * met:
- *
- *     (1) Redistributions of source code must retain the above copyright
- *     notice, this list of conditions and the following disclaimer. 
- *
- *     (2) Redistributions in binary form must reproduce the above copyright
- *     notice, this list of conditions and the following disclaimer in
- *     the documentation and/or other materials provided with the
- *     distribution.  
- *     
- *     (3)The name of the author may not be used to
- *     endorse or promote products derived from this software without
- *     specific prior written permission.
- * 
- * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
- * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
- * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
- * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
- * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
- * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
- * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
- * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
- * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
- * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
- * POSSIBILITY OF SUCH DAMAGE. 
- * 
- */
-
-#include <stdlib.h>
-#include <stdint.h>
-#include <string.h>
-
-#include "nativez.h"
-#include "nativez-jni.h"
-
-static JavaVM *vm;
-
-static jobject ByteOrder_nativeOrder; // ByteOrder.nativeOrder()
-
-static int fail(const char *ctx, const char *what) __attribute__ ((noinline));
-static int fail(const char *ctx, const char *what) {
-       fprintf(stderr, "%s: %s\n", ctx, what);
-       perror(ctx);
-       fflush(stderr);
-       return -1;
-}
-
-jint nativez_OnLoad(JavaVM *vmi, JNIEnv *env) {
-       /* Save VM - required for callbacks from threads */
-       vm = vmi;
-
-       if (nativez_ResolveReferences(env, java_names, &java) != 0)
-               return -1;
-
-       jclass jc = (*env)->FindClass(env, "java/nio/ByteOrder");
-       if (jc == NULL)
-               return -1;
-       jmethodID ByteOrder_nativeOrder_ = (*env)->GetStaticMethodID(env, jc, "nativeOrder", "()Ljava/nio/ByteOrder;");
-       ByteOrder_nativeOrder = (*env)->NewGlobalRef(env, (*env)->CallStaticObjectMethodA(env, jc, ByteOrder_nativeOrder_, NULL));
-
-       return 0;
-}
-
-void *nativez_AllocAligned(size_t align, size_t size) {
-       void *mem;
-#ifdef HAVE_ALIGNED_MALLOC
-       mem = _aligned_malloc(size, align);
-       return mem;
-#else
-       if (posix_memalign(&mem, align, size) == 0)
-               return mem;
-       return NULL;
-#endif
-}
-
-void nativez_FreeAligned(void *mem) {
-#ifdef HAVE_ALIGNED_MALLOC
-       _aligned_free(mem);
-#else
-       free(mem);
-#endif
-}
-
-JNIEnv *nativez_AttachCurrentThread(void) {
-       JNIEnv *env = NULL;
-       
-       (*vm)->AttachCurrentThread(vm, (void **)&env, NULL);
-
-       // one assumes this is null if error return?
-       return env;
-}
-
-JNIEnv *nativez_AttachCurrentThreadAsDaemon(void) {
-       JNIEnv *env = NULL;
-       
-       (*vm)->AttachCurrentThreadAsDaemon(vm, (void **)&env, NULL);
-
-       // one assumes this is null if error return?
-       return env;
-}
-
-/* ********************************************************************** */
-
-void nativez_ThrowException(JNIEnv *env, const char *type, const char *msg) {
-       jclass jc = (*env)->FindClass(env, type);
-
-       if (jc)
-               (*env)->ThrowNew(env, jc, msg);
-       else {
-               fprintf(stderr, "Unable to throw exception `%s': `%s'\n", type, msg);
-               fflush(stderr);
-       }
-}
-
-void nativez_ThrowOutOfMemoryError(JNIEnv *env, const char *msg) {
-       nativez_ThrowException(env, "java/lang/OutOfMemoryError", msg);
-}
-
-/* ********************************************************************** */
-
-jobject nativez_NewDirectBuffer(JNIEnv *env, void *data, jlong size) {
-       jobject jo;
-
-       jo = (*env)->NewDirectByteBuffer(env, data, size);
-       if (jo) {
-               jvalue arg = { .l = ByteOrder_nativeOrder };
-
-               (*env)->CallObjectMethodA(env, jo, ByteBuffer_order_l, &arg);
-       }
-
-       return jo;
-}
-
-jboolean nativez_BufferHasArray(JNIEnv *env, jobject jbuffer) {
-       return (*env)->CallBooleanMethodA(env, jbuffer, Buffer_hasArray_, NULL);
-}
-
-jboolean nativez_BufferIsDirect(JNIEnv *env, jobject jbuffer) {
-       return (*env)->CallBooleanMethodA(env, jbuffer, Buffer_isDirect_, NULL);
-}
-
-jarray nativez_BufferArray(JNIEnv *env, jobject jbuffer) {
-       return (*env)->CallObjectMethodA(env, jbuffer, Buffer_array_, NULL);
-}
-
-jint nativez_BufferArrayOffset(JNIEnv *env, jobject jbuffer) {
-       return (*env)->CallIntMethodA(env, jbuffer, Buffer_arrayOffset_, NULL);
-}
-
-jint nativez_BufferPosition(JNIEnv *env, jobject jbuffer) {
-       return (*env)->CallIntMethodA(env, jbuffer, Buffer_position_, NULL);
-}
-
-jint nativez_BufferLimit(JNIEnv *env, jobject jbuffer) {
-       return (*env)->CallIntMethodA(env, jbuffer, Buffer_limit_, NULL);
-}
-
-void nativez_BufferSetPosition(JNIEnv *env, jobject jbuffer, jint jposition) {
-       jvalue arg = { .i = jposition };
-       (void)(*env)->CallObjectMethodA(env, jbuffer, Buffer_position_i, &arg);
-}
-                                                                      
-void nativez_BufferSetLimit(JNIEnv *env, jobject jbuffer, jint jlimit) {
-       jvalue arg = { .i = jlimit };
-       (void)(*env)->CallObjectMethodA(env, jbuffer, Buffer_limit_i, &arg);
-}
-
-const char *nativez_GetString(JNIEnv *env, jstring js) {
-       return js ? (*env)->GetStringUTFChars(env, js, NULL) : NULL;
-}
-
-void nativez_ReleaseString(JNIEnv *env, jstring js, const char *s) {
-       if (js)
-               (*env)->ReleaseStringUTFChars(env, js, s);
-}
-
-jstring nativez_NewString(JNIEnv *env, const char *s) {
-       return s ? (*env)->NewStringUTF(env, s) : NULL;
-}
-
-/* ********************************************************************** */
-
-int nativez_ResolveReferences(JNIEnv *env, const char *jn_names, void *jnp) {
-       jclass jc = NULL;
-
-       const char *name = jn_names;
-       int index = 0;
-       void **jn = jnp;
-       const char *cname = "?";
-
-       while (*name) {
-               const char *next = name + strlen(name) + 1;
-
-               switch (*name) {
-               case '#': // class
-                       jc = (*env)->FindClass(env, name + 1);
-                       jn[index] = jc = (*env)->NewGlobalRef(env, jc);
-                       cname = name + 1;
-                       break;
-               case ',': // field
-                       jn[index] = (*env)->GetFieldID(env, jc, name+1, next);
-                       break;
-               case ';': // static field
-                       jn[index] = (*env)->GetStaticFieldID(env, jc, name+1, next);
-                       break;
-               case '.': // method
-                       jn[index] = (*env)->GetMethodID(env, jc, name+1, next);
-                       break;
-               case ':': // static method
-                       jn[index] = (*env)->GetStaticMethodID(env, jc, name+1, next);
-                       break;
-               default:
-                       return fail("Invalid table", name);
-               }
-
-               if (!jn[index])
-                       return fail(cname, name+1);
-
-               if (*name != '#')
-                       next = next + strlen(next) + 1;
-               name = next;
-               index += 1;
-       }
-
-       return 0;
-}
-
-/* ********************************************************************** */
-
-int nativez_NonNull(JNIEnv *env, const char *what, void *ptr) {
-       if (ptr)
-               return 1;
-
-       nativez_ThrowException(env, "java/lang/NullPointerException", what);
-       return 0;
-}
-
-/* ********************************************************************** */
-
-/**
- * Retrieve pointer arguments.
- *
- * MUST always call ReleasePointers() with the same arguments regardles of return value.
- */
-jboolean nativez_GetPointers(JNIEnv *env, const char *desc, nzpointer * __restrict info) {
-       char c;
-       while ((c = *desc++)) {
-               switch (c) {
-               case 'P':
-                       if (!info->object)
-                               goto nullPointer;
-               case 'p':
-                       if (info->object && !(info->p.v = (*env)->GetPrimitiveArrayCritical(env, info->object, NULL)))
-                               return 0;
-                       break;
-               case 'U':
-                       if (!info->object)
-                               goto nullPointer;
-               case 'u':
-                       if (info->object && !(info->p.U = (*env)->GetStringUTFChars(env, info->object, NULL)))
-                               return 0;
-                       break;
-               case 'N':
-                       if (!info->object)
-                               goto nullPointer;
-               case 'n':
-                       if (info->object)
-                               info->p.N = (void *)(uintptr_t)(*env)->GetLongField(env, info->object, NativeZ_p);
-                       break;
-               default:
-                       return 0;
-               }
-               info++;
-       }
-       return 1;
-
- nullPointer:
-       nativez_ThrowException(env, "java/lang/NullPointerException", "Argument missing");
-       return 0;
-}
-
-/**
- * Release pointer arguments.
- */
-void nativez_ReleasePointers(JNIEnv *env, const char *desc, nzpointer * __restrict info) {
-       char c;
-       while ((c = *desc++)) {
-               if (info->p.v) {
-                       switch (c) {
-                       case 'P':
-                       case 'p':
-                               (*env)->ReleasePrimitiveArrayCritical(env, info->object, info->p.v, 0);
-                               break;
-                       case 'U':
-                       case 'u':
-                               (*env)->ReleaseStringUTFChars(env, info->object, info->p.U);
-                               break;
-                       case 'N':
-                       case 'n':
-                               break;
-                       }
-               }
-               info++;
-       }
-}
-
-/* ********************************************************************** */
-
-jobject NativeZ_create(JNIEnv *env, jclass jc, void *p) {
-       if (p) {
-               jvalue jargs[] = {
-                       { .l = jc },
-                       { .j = (uintptr_t)p }
-               };
-
-               return (*env)->CallStaticObjectMethodA(env, NativeZ_classid, NativeZ_create_lj, jargs);
-       } else
-               return NULL;
-}
-
-jobject NativeZ_register(JNIEnv *env, jobject jo) {
-       if (jo) {
-               jvalue jargs[] = {
-                       { .l = jo }
-               };
-
-               return (*env)->CallStaticObjectMethodA(env, NativeZ_classid, NativeZ_register_l, jargs);
-       } else
-               return NULL;
-}
-
-jobject NativeZ_resolve(JNIEnv *env, jclass jc, void *p) {
-       if (p) {
-               jvalue jargs[] = {
-                       { .l = jc },
-                       { .j = (uintptr_t)p }
-               };
-
-               return (*env)->CallStaticObjectMethodA(env, NativeZ_classid, NativeZ_resolve_lj, jargs);
-       } else
-               return NULL;
-}
-
-jobject NativeZ_refer(JNIEnv *env, jclass jc, void *p) {
-       if (p) {
-               jvalue jargs[] = {
-                       { .l = jc },
-                       { .j = (uintptr_t)p }
-               };
-
-               return (*env)->CallStaticObjectMethodA(env, NativeZ_classid, NativeZ_refer_lj, jargs);
-       } else
-               return NULL;
-}
-
-void *NativeZ_getP(JNIEnv *env, jobject jo) {
-       if (jo)
-               return (void *)(uintptr_t)(*env)->GetLongField(env, jo, NativeZ_p);
-       else
-               return NULL;
-}
diff --git a/src/notzed.nativez/jni/nativez-jni.def b/src/notzed.nativez/jni/nativez-jni.def
deleted file mode 100644 (file)
index 3fa29af..0000000
+++ /dev/null
@@ -1,22 +0,0 @@
-java Buffer java/nio/Buffer {
-     hasArray, ()Z
-     isDirect, ()Z
-     array, ()Ljava/lang/Object;
-     arrayOffset, ()I
-     position, ()I
-     position, (I)Ljava/nio/Buffer;
-     limit, ()I
-     limit, (I)Ljava/nio/Buffer;
-}
-
-java ByteBuffer java/nio/ByteBuffer {
-     order, (Ljava/nio/ByteOrder;)Ljava/nio/ByteBuffer;
-}
-
-java NativeZ au/notzed/nativez/NativeZ {
-     static create, (Ljava/lang/Class;J)Lau/notzed/nativez/NativeZ;
-     static register, (Lau/notzed/nativez/NativeZ;)Lau/notzed/nativez/NativeZ;
-     static resolve, (Ljava/lang/Class;J)Lau/notzed/nativez/NativeZ;
-     static refer, (Ljava/lang/Class;J)Lau/notzed/nativez/NativeZ;
-     p, J
-}
diff --git a/src/notzed.nativez/jni/nativez-linux.c b/src/notzed.nativez/jni/nativez-linux.c
deleted file mode 100644 (file)
index 36aae60..0000000
+++ /dev/null
@@ -1,94 +0,0 @@
-/*
- * Copyright (C) 2019 Michael Zucchi
- *
- * This file is part of nativez <https://www.zedzone.space/software/nativez.html>
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions are
- * met:
- *
- *     (1) Redistributions of source code must retain the above copyright
- *     notice, this list of conditions and the following disclaimer. 
- *
- *     (2) Redistributions in binary form must reproduce the above copyright
- *     notice, this list of conditions and the following disclaimer in
- *     the documentation and/or other materials provided with the
- *     distribution.  
- *     
- *     (3)The name of the author may not be used to
- *     endorse or promote products derived from this software without
- *     specific prior written permission.
- * 
- * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
- * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
- * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
- * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
- * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
- * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
- * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
- * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
- * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
- * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
- * POSSIBILITY OF SUCH DAMAGE. 
- * 
- */
-
-#include <string.h>
-#include <stdio.h>
-#include <dlfcn.h>
-
-#include "nativez.h"
-
-static int fail(const char *ctx, const char *what) {
-       fprintf(stderr, "%s: %s\n", ctx, what);
-       perror(ctx);
-       fflush(stderr);
-       return -1;
-}
-
-int nativez_ResolveLibraries(JNIEnv *env, NZLibTable *table) {
-       for (int i=0;table[i].name;i++) {
-               table[i].lib = dlopen(table[i].path, RTLD_LAZY | RTLD_GLOBAL);
-               if (!table[i].lib && (table[i].flags & NZSO_NONCORE) == 0)
-                       return fail("open library", table[i].path);
-       }
-       return 0;
-}
-
-int nativez_ResolveFunctions(JNIEnv *env, const NZLibTable *table, const char *fn_names, void *fnp) {
-       void *lib = NULL;
-       const char *name = fn_names;
-       int index = 0;
-       const char *lib_name = "";
-       void **fn = fnp;
-       int lib_flags = 0;
-       
-       while (*name) {
-               const char *next = name + strlen(name) + 1;
-
-               if (*name == '#') {
-                       lib = NULL;
-                       lib_flags = 0;
-                       lib_name = name+1;
-                       for (int i=0;table[i].name;i++) {
-                               if (strcmp(table[i].name, lib_name) == 0) {
-                                       lib = table[i].lib;
-                                       lib_flags = table[i].flags;
-                                       break;
-                               }
-                       }
-               } else if (lib) {
-                       void *entry = dlsym(lib, name);
-
-                       fn[index++] = entry;
-                       if (!entry)
-                               return fail("resolve function", name);
-               } else if ((lib_flags & NZSO_NONCORE) == 0) {
-                       return fail(lib_name, name);
-               }
-
-               name = next;
-       }
-
-       return 0;
-}
diff --git a/src/notzed.nativez/jni/nativez-windows.c b/src/notzed.nativez/jni/nativez-windows.c
deleted file mode 100644 (file)
index 1602c98..0000000
+++ /dev/null
@@ -1,93 +0,0 @@
-/*
- * Copyright (C) 2019 Michael Zucchi
- *
- * This file is part of nativez <https://www.zedzone.space/software/nativez.html>
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions are
- * met:
- *
- *     (1) Redistributions of source code must retain the above copyright
- *     notice, this list of conditions and the following disclaimer. 
- *
- *     (2) Redistributions in binary form must reproduce the above copyright
- *     notice, this list of conditions and the following disclaimer in
- *     the documentation and/or other materials provided with the
- *     distribution.  
- *     
- *     (3)The name of the author may not be used to
- *     endorse or promote products derived from this software without
- *     specific prior written permission.
- * 
- * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
- * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
- * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
- * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
- * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
- * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
- * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
- * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
- * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
- * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
- * POSSIBILITY OF SUCH DAMAGE. 
- * 
- */
-
-#include <windows.h>
-
-#include "nativez.h"
-
-static int fail(const char *ctx, const char *what) {
-       fprintf(stderr, "%s: %s\n", ctx, what);
-       perror(ctx);
-       fflush(stderr);
-       return -1;
-}
-
-int nativez_ResolveLibraries(JNIEnv *env, NZLibTable *table) {
-       for (int i=0;table[i].name;i++) {
-               table[i].lib = LoadLibrary(table[i].path);
-               if (!table[i].lib && (table[i].flags & NZSO_NONCORE) == 0) {
-                       return fail("open library", table[i].path);
-               }
-       }
-       return 0;
-}
-
-int nativez_ResolveFunctions(JNIEnv *env, const NZLibTable *table, const char *fn_names, void *fnp) {
-       void *lib = NULL;
-       const char *name = fn_names;
-       int index = 0;
-       const char *lib_name = "";
-       void **fn = fnp;
-       int lib_flags = 0;
-       
-       while (*name) {
-               const char *next = name + strlen(name) + 1;
-
-               if (*name == '#') {
-                       lib = NULL;
-                       lib_flags = 0;
-                       lib_name = name+1;
-                       for (int i=0;table[i].name;i++) {
-                               if (strcmp(table[i].name, lib_name) == 0) {
-                                       lib = table[i].lib;
-                                       lib_flags = table[i].flags;
-                                       break;
-                               }
-                       }
-               } else if (lib) {
-                       void *entry = GetProcAddress(lib, name);
-
-                       fn[index++] = entry;
-                       if (!entry)
-                               return fail("resolve function", name);
-               } else if ((lib_flags & NZSO_NONCORE) == 0) {
-                       return fail(lib_name, name);
-               }
-
-               name = next;
-       }
-
-       return 0;
-}
diff --git a/src/notzed.nativez/jni/nativez.h b/src/notzed.nativez/jni/nativez.h
deleted file mode 100644 (file)
index 51d862c..0000000
+++ /dev/null
@@ -1,203 +0,0 @@
-/*
- * Copyright (C) 2018 Michael Zucchi
- *
- * This file is part of nativez <https://www.zedzone.space/software/nativez.html>
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions are
- * met:
- *
- *     (1) Redistributions of source code must retain the above copyright
- *     notice, this list of conditions and the following disclaimer. 
- *
- *     (2) Redistributions in binary form must reproduce the above copyright
- *     notice, this list of conditions and the following disclaimer in
- *     the documentation and/or other materials provided with the
- *     distribution.  
- *     
- *     (3)The name of the author may not be used to
- *     endorse or promote products derived from this software without
- *     specific prior written permission.
- * 
- * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
- * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
- * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
- * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
- * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
- * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
- * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
- * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
- * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
- * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
- * POSSIBILITY OF SUCH DAMAGE. 
- * 
- */
-
-#ifndef _NATIVEZ_H
-#define _NATIVEZ_H
-
-#include <jni.h>
-
-/**
- * Resolves a java table created by nativez-gen 
- */
-int nativez_ResolveReferences(JNIEnv *env, const char *jn_names, void *jnp);
-
-/**
- * Support for dynamic library loading.
- *
- * It happens in two parts:
- *
- * 1. load the libraries using nativez_ResolveLibraries;
- * 2. resolve the function pointers using nativez_ResolveFunctions.
- *
- */
-typedef struct NZLibTable {
-       const char *name;       /* portable name of library */
-       const char *path;       /* platform-specific name of library file */
-       const int flags;        /* NZSO_* flags */
-       void *lib;              /* pointer to library handle */
-} NZLibTable;
-
-// Define NZLibTable entry.
-#define NZSO(name, path, flags) { name, path, flags }
-#define NZSO_END { 0 }
-
-// non-core library, non-existance is not a failure
-#define NZSO_NONCORE 1
-
-/**
- * ResolveLibraries loads libraires via a table of NZLibTable entries.
- * The library name is platform specific.  Also see NZSO_* flags.
- * The opened library is stored in the table[].lib.
- * The table is terminated with an empty entry or NZSO_END.
- */
-int nativez_ResolveLibraries(JNIEnv *env, NZLibTable *table);
-
-/**
- * ResolveFunctions uses the output of nativez-gen to fill out
- * a table.
- */
-int nativez_ResolveFunctions(JNIEnv *env, const NZLibTable *table, const char *fn_names, void *fnp);
-
-// Check if function exists, return value if not and set an exception
-#define NZCHECK_RET(env, x, ret) do { if (!nativez_NonNull(env, #x, (void *)fn.x)) { return ret; } } while (0)
-// Call a function pointer, or just use the struct directly.
-#define NZCALL(x) (fn.x)
-
-/**
- * Checks if ptr is non-null.  If it is null throws a NullPointer
- * exception with the given description and returns false.
- */
-int nativez_NonNull(JNIEnv *env, const char *what, void *ptr);
-
-/**
- * For converting array or string arguments to native pointers.
- */
-typedef struct nzpointer nzpointer;
-
-struct nzpointer {
-       const jobject object;
-       union {
-               void *v;
-               jint *i;
-               jfloat *f;
-               jdouble *d;
-               const char *U;
-               void *N;
-       } p;
-};
-
-/**
- * Retrieve pointers from java objects.
- *
- * desc is a string which defines the types of the arguments.
- *
- *   p   j*Array as GetPrimitiveArrayCritical
- *   u   String as UTF8
- *   n   Native.Z p pointer
- *   P   j*Array that may not be null
- *   U   String that may not be null
- *   N   NativeZ that may not be null
- *
- * info is an array of initialsed nzpointer objects with the .object field set to the
- * source object and the p field nullified.  Each element corresponds to the
- * same index desc.
- *
- * Note: MUST always call ReleasePointers() with the same arguments regardles of return value.
- *
- * Returns true if all pointers were resolved successfully.
- */
-jboolean nativez_GetPointers(JNIEnv *env, const char *desc, nzpointer * __restrict info);
-void nativez_ReleasePointers(JNIEnv *env, const char *desc, nzpointer *info);
-
-/***
- * Call GetStringUTFChars but handles null js
- */
-const char *nativez_GetString(JNIEnv *env, jstring js);
-
-/**
- * Call ReleaseStringUTFChars but handles null js
- */
-void nativez_ReleaseString(JNIEnv *env, jstring js, const char *s);
-
-/**
- * Call NewStringUTF but handle NULL s
- */
-jstring nativez_NewString(JNIEnv *env, const char *s);
-
-/**
- * Throw a basic named exception with a simple message.
- */
-void nativez_ThrowException(JNIEnv *env, const char *cname, const char *msg);
-void nativez_ThrowOutOfMemoryError(JNIEnv *env, const char *msg);
-
-/**
- * (Byte)Buffer methods
- */
-jobject nativez_NewDirectBuffer(JNIEnv *env, void *data, jlong size);
-jboolean nativez_BufferHasArray(JNIEnv *env, jobject jbuffer);
-jboolean nativez_BufferIsDirect(JNIEnv *env, jobject jbuffer);
-jarray nativez_BufferArray(JNIEnv *env, jobject jbuffer);
-jint nativez_BufferArrayOffset(JNIEnv *env, jobject jbuffer);
-jint nativez_BufferPosition(JNIEnv *env, jobject jbuffer);
-jint nativez_BufferLimit(JNIEnv *env, jobject jbuffer);
-void nativez_BufferSetPosition(JNIEnv *env, jobject jbuffer, jint jposition);
-void nativez_BufferSetLimit(JNIEnv *env, jobject jbuffer, jint jlimit);
-
-/**
- * Allocate aligned memory, throws an exception on error.
- */
-void *nativez_AllocAligned(size_t align, size_t size);
-void nativez_FreeAligned(void *mem);
-
-/*
-  Call this in the OnLoad method
-*/
-jint nativez_OnLoad(JavaVM *vmi, JNIEnv *env);
-
-/*
-  Attach current thread, so it can be used in callbacks.
- */
-JNIEnv *nativez_AttachCurrentThread(void);
-JNIEnv *nativez_AttachCurrentThreadAsDaemon(void);
-
-/*
-  It is always safe to call resolve() for the freeable object case,
-  create() just implements an optimised path.
-
-  resolve() is required for objects which are refcounted on the C side.
- */
-
-/* Create a new object, e.g. one that has just been allocated and can't possibly already exist in java */
-jobject NativeZ_create(JNIEnv *env, jclass jc, void *p);
-/* Register a newly created object */
-jobject NativeZ_register(JNIEnv *env, jobject object);
-/* Turn a pointer into a freeable object.  If it already exists the same object will be returned */
-jobject NativeZ_resolve(JNIEnv *env, jclass jc, void *p);
-/* Turn a pointer into a non-freeable object.  If it already exists the same object will be returned */
-jobject NativeZ_refer(JNIEnv *env, jclass jc, void *p);
-/* Retreive the native pointer stored in NativeZ subclass instance jo */
-void *NativeZ_getP(JNIEnv *env, jobject jo);
-
-#endif
diff --git a/src/notzed.nativez/lib/api.pm b/src/notzed.nativez/lib/api.pm
new file mode 100644 (file)
index 0000000..6b46c70
--- /dev/null
@@ -0,0 +1,1107 @@
+
+# TODO: define more & consistent stage processing hooks, currently implicit require init() and stage.postprocess
+# TODO: code:<inline> etc should probably instead be code:<type> where <type> is one of the template fields - methods, init, etc.
+# TODO: calls should probably use dynamic invocation - downcall is not bound to address
+
+package api;
+
+use strict;
+
+use File::Path qw(make_path);
+use File::Basename;
+use Data::Dumper;
+use List::Util qw(first);
+use Carp 'verbose';
+
+use config;
+
+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', 'field:rename=studly-caps' ],
+               regex => qr/^struct:<default>$/,
+               type => 'struct'
+       },
+       'union:<default>' => {
+               name => '<default>',
+               items => [],
+               options => [ 'default=all', 'access=rw', 'field:rename=studly-caps' ],
+               regex => qr/^union:<default>$/,
+               type => 'union'
+       },
+       'call:<default>' => {
+               name => '<default>',
+               items => [],
+               options => [ 'call:rename=call', 'access=r' ],
+               regex => qr/^call:<default>$/,
+               type => 'call'
+       },
+       'func:<default>' => {
+               name => '<default>',
+               items => [],
+               options => [],
+               regex => qr/^func:<default>$/,
+               type => 'func',
+               'func:template' => 'code:method=invoke'
+       },
+       'enum:<default>' => {
+               name => '<default>',
+               items => [],
+               options => [],
+               regex => qr/^enum:<default>$/,
+               type => 'enum'
+       },
+);
+
+sub new {
+       my $class = shift;
+       my $file = shift;
+       my $vars = shift;
+       my $self = {
+               vars => $vars,
+               index => {},
+               data => {},
+               types => [],
+       };
+
+       my $conf = new config($vars, $file);
+       $self->{api} = $conf->{objects};
+       foreach my $obj (@{$self->{api}}) {
+               $self->{index}->{"$obj->{type}:$obj->{name}"} = $obj;
+       }
+       foreach my $k (keys %defaultTable) {
+               if (!defined($self->{index}->{$k})) {
+                       $self->{index}->{$k} = $defaultTable{$k};
+                       push @{$self->{api}}, $defaultTable{$k};
+               }
+       }
+
+       while ($#_ >= 0) {
+               my $name = shift;
+               my $info = loadAPIFile($name);
+
+               $self->{data} = { %{$self->{data}}, %{$info} };
+       }
+
+       # FIXME: fix the list names in export.cc and export-defines instead
+       # set all the member list names to be consistent
+       foreach my $s (values %{$self->{data}}) {
+               foreach my $n ('fields', 'arguments', 'values') {
+                       if ($s->{$n}) {
+                               $s->{items} = $s->{$n};
+                               delete $s->{$n};
+                       }
+               }
+       }
+
+       bless $self, $class;
+
+       # handle requires and init modules
+       foreach my $p (@{$conf->{pragmas}}) {
+               if ($p->[0] eq '%require') {
+                       require $p->[1];
+                       my $n = $p->[1];
+                       $n =~ s/.pm$/::init(\$self)/;
+                       print "$n\n";
+                       eval $n || die "module init failed: $! $@";
+               }
+       }
+
+       # create type indices
+       {
+               my $index = {};
+               map { $index->{$_->{type}}->{$_->{name}} = $_ } values %{$self->{data}};
+               $self->{databytype} = $index;
+       }
+
+       analyseAPI($self);
+       preprocess($self);
+
+       # add phantom 'api' entries for anything else required
+       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 = {
+                       %$def,
+                       match => "$s->{type}:$s->{name}",
+                       name => $s->{name},
+                       regex => qr/^$n$/,
+               };
+               $obj->{rename} = $obj->{"$obj->{type}:rename"} ? $obj->{"$obj->{type}:rename"}->($obj->{name}) : $obj->{name};
+
+               print " implicit $n\n";
+               push @{$self->{api}}, $obj;
+               $self->{index}->{$n} = $obj;
+       }
+
+       postprocess($self);
+
+       foreach my $p (@{$conf->{pragmas}}) {
+               if ($p->[0] eq '%stage.postprocess') {
+                       eval "$p->[1](\$self)" || die "$p->[0] $p->[1] failed: $! $@";
+               }
+       }
+
+       # form the types
+       # TODO: could match the regexes to every possible type in the api and create a direct index
+       my $copyIndex = {};
+       foreach my $obj (grep { $_->{type} eq 'type' && $_->{name} =~ m@<.*>@ } @{$self->{api}}) {
+               $copyIndex->{$obj->{name}} = $obj;
+       }
+
+       foreach my $obj (grep { $_->{type} eq 'type' && $_->{name} =~ m@^/.*/$@ } @{$self->{api}}) {
+               push @{$self->{types}}, initType($self, $copyIndex, $obj);
+       }
+
+       # handle external references - rename and supress output
+       # Not sure of the best place to put this
+       foreach my $obj (grep { $_->{type} eq 'extern' } @{$self->{api}}) {
+               foreach my $inc (@{$obj->{items}}) {
+                       my @list = findMatches($self, $inc, $obj);
+
+                       foreach my $s (@list) {
+                               $s->{rename} = $obj->{name}.'.'.$inc->{"$s->{type}:rename"}->($s->{name}, $s);
+                               $s->{output} = 0;
+                       }
+               }
+       }
+
+       return $self;
+}
+
+# class.name to class/name.java
+sub classToPath {
+       my $api = shift;
+       my $name = shift;
+
+       $name = $api->{vars}->{package}.'.'.$name;
+       $name =~ s@\.@/@g;
+       $name = $api->{vars}->{output}.'/'.$name.'.java';
+       $name;
+}
+
+sub closeOutput {
+       my $api = shift;
+       my $name = shift;
+       my $f = shift;
+       my $path = $api->classToPath($name);
+
+       close($f) || die;
+       rename($path.'~', $path) || die ("rename failed: $!");
+}
+
+sub openOutput {
+       my $api = shift;
+       my $name = shift;
+       my $path = $api->classToPath($name);
+       my $dir = dirname($path);
+
+       make_path($dir) if (!-d $dir);
+
+       open(my $f, ">", $path.'~') || die ("Cannot open '$path' for writing");
+       #print "writing '$path'\n";
+       $f;
+}
+
+sub renameFunction {
+       my $api = shift;
+       my $name = shift;
+       $renameTable{$name};
+}
+
+sub initType {
+       my $api = shift;
+       my $index = shift;
+       my $obj = shift;
+       my $type = {};
+
+       $type->{options} = [ @{$obj->{options}} ];
+       $type->{file} = $obj->{file};
+
+       # FIXME: per-item options, set etc?
+       foreach my $inc (grep { defined $_->{literal} } @{$obj->{items}}) {
+               $type->{items}->{$inc->{match}} = $inc->{literal};
+       }
+
+       my $v = optionValue('select', undef, $obj);
+       $type->{select} = $v if defined($v);
+
+       #print "init $obj->{name}\n";
+       foreach my $c (split /,/,optionValue('copy', undef, $obj)) {
+               my $copy = $index->{$c};
+
+               if ($copy) {
+                       #print " copy $c\n";
+                       my $proto = initType($api, $index, $copy);
+
+                       foreach my $k (keys %{$proto->{items}}) {
+                               $type->{items}->{$k} = $proto->{items}->{$k} if !defined($type->{items}->{$k});
+                       }
+
+                       push @{$type->{options}}, @{$proto->{options}};
+               } else {
+                       die ("type copy target $c not found");
+               }
+       }
+
+       $type->{regex} = qr/$1/ if $obj->{name} =~ m@/(.*)/@;
+
+       return $type;
+}
+
+# returns { type=>$type, match=>$match)
+#  match is named groups from regex (%+)
+sub findType {
+       my $api = shift;
+       my $m = shift;
+       my $deref = $m->{deref};
+       my @list;
+
+       foreach my $type (@{$api->{types}}) {
+               my $select = !defined($type->{select}) || defined($m->{select}->{$type->{select}});
+
+               if ($select && ($deref =~ m/$type->{regex}/)) {
+                       my %match = %+;
+
+                       return { deref=>$deref, type=>$type, match=>\%match };
+               }
+       }
+       die ("cannot find matching type '$deref' for member $m->{name}");
+       undef;
+}
+
+sub findTemplateName {
+       my $api = shift;
+       my $name = shift;
+
+       if ($name =~ m/^(.+)=(.+)$/) {
+               my $template = findItem($api->{index}->{$1}, $2);
+               return $template if defined $template;
+       }
+       die "can't find template '$name'\n";
+}
+
+sub queryTemplate {
+       my $api = shift;
+       my $obj = shift;
+       my $s = shift;
+       my $sname = $s->{"$s->{type}:template"};
+       my $def = $api->{index}->{"$s->{type}:<default>"};
+       my $name = $sname ? $sname : api::optionValue('template', $s->{size} == 0 ? 'code:class=handle' : 'code:class=struct', $obj, $def);
+
+       return $name;
+}
+
+sub findTemplate {
+       my $api = shift;
+       my $obj = shift;
+       my $s = shift;
+       my $sname = $s->{"$s->{type}:template"};
+       my $def = $api->{index}->{"$s->{type}:<default>"};
+       my $name = $sname ? $sname : api::optionValue('template', $s->{size} == 0 ? 'code:class=handle' : 'code:class=struct', $obj, $def);
+
+       print "template: $s->{name} -> $name\n" if ($api->{vars}->{verbose} > 1);
+
+       return findTemplateName($api, $name);
+}
+
+# find value of first option of the given name
+# TBD: moved to config.pm
+sub optionValue {
+       my $name = shift;
+       my $or = shift;
+
+       #my $match = sub { $_ =~ m/^\Q$name\E=(.*)$/on ? $1 : undef };
+
+       #print "optionValue $name\n";
+       foreach my $obj (@_) {
+               foreach my $opt (@{$obj->{options}}) {
+                       #print "? $name $opt = ".($opt =~m/^\Q$name\E=(.*)$/)."\n";
+                       #print " = $opt\n" if ($opt =~m/^\Q$name\E=(.*)$/);
+                       return $1 if ($opt =~m/^\Q$name\E=(.*)$/);
+                       #my $x =  $match->($opt);
+                       #return $x if defined($x);
+               }
+       }
+       $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;
+}
+
+# same as above but doesn't short-circuit
+sub optionValuesAll {
+       my $name = shift;
+       my $rx = qr/$name/;
+       my @list;
+
+       foreach my $obj (@_) {
+               foreach my $opt (@{$obj->{options}}) {
+                       push @list, $1 if ($opt =~m/^$rx=(.*)$/);
+               }
+       }
+       @list;
+}
+
+# find first occurance of a flag
+# TBD: moved to config.pm
+sub optionFlag {
+       my $name = shift;
+
+       foreach my $obj (@_) {
+               foreach my $opt (@{$obj->{options}}) {
+                       return 1 if ($opt eq $name);
+               }
+       }
+       undef;
+}
+
+sub findItem {
+       my $s = shift;
+       my $name = shift;
+
+       foreach my $i (@{$s->{items}}) {
+               return $i if $i->{match} eq $name;
+       }
+       undef;
+}
+
+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', undef, $obj, $api->{index}->{'struct:<default>'}) 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 findField {
+       my $s = shift;
+       my $name = shift;
+
+       foreach my $i (@{$s->{items}}) {
+               return $i if $i->{name} eq $name;
+       }
+       undef;
+}
+
+# ######################################################################
+
+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)$/n) {
+               # 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):/);
+                               # HACK: enum types are integers but ctype includes the actual type
+                               $add->("enum:$1") if ($d->{ctype} =~ m/^enum (.+)/);
+                       }
+               }
+       } elsif ($s->{type} =~ m/^(call|func)/n) {
+               # 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});
+               }
+       }
+}
+
+# use either {match} or {regex} or {matcher} to get all matches for a data type
+sub findMatches {
+       my $api = shift;
+       my $inc = shift;
+       my $ctx = shift;
+       my $type = $inc->{type};
+       my $data = $api->{databytype}->{$type};
+
+       if ($inc->{matcher}) {
+               #grep { $inc->{matcher}->($_, $ctx) } grep { $_->{type} eq $inc->{type} } values %$data;
+               grep { $inc->{matcher}->($_, $ctx) } values %$data;
+       } else {
+               my $s = $api->{data}->{$inc->{match}};
+
+               if (defined($s)) {
+                       $s;
+               } else {
+                       #map { $data->{$_} } grep { $_ =~ m/$inc->{regex}/ } keys %$data;
+                       #map { $data->{$_} } grep { "$type:$_" =~ m/$inc->{regex}/ } keys %$data;
+                       grep { "$_->{type}:$_->{name}" =~ m/$inc->{regex}/ } values %$data;
+                       #grep { "$_->{type}:$_->{name}" =~ m/$inc->{regex}/ } values %{$api->{data}};
+               }
+       }
+}
+
+# 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; };
+
+       print "Root types\n";
+       foreach my $obj (@{$api->{api}}) {
+               if ($obj->{type} eq 'library') {
+                       foreach my $inc (@{$obj->{items}}) {
+                               next if ($inc->{type} eq 'library');
+
+                               print "? $inc->{match}\n";
+                               foreach my $s (findMatches($api, $inc, $obj)) {
+                                       my $n = "$s->{type}:$s->{name}";
+
+                                       print "+ $n\n";
+
+                                       $seen{$n}++;
+                                       $s->{output} = 1;
+                                       addDependencies($api, $obj, $s, $setdeps);
+                               }
+                       }
+               } elsif ($obj->{type} =~ m/^(struct|union|call|func|enum|define)$/) {
+                       #foreach my $n (grep { $_ =~ m/$obj->{regex}/ } keys %data) {
+
+                       print "? $obj->{name}\n";
+
+
+                       foreach my $s (findMatches($api, $obj, $obj)) {
+                               my $n = "$s->{type}:$s->{name}";
+
+                               print "+ $n\n";
+
+                               $seen{$n}++;
+                               $s->{output} = 1;
+                               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";
+                       $s->{output} = 1;
+                       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 => [],
+                               output => 1,
+                       };
+                       $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 $api = shift;
+       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 ($n =~ m@^func:(.*)$@) {
+                       $rename = eval "sub { $1(\@_) }";
+                       if (!defined($rename)) {
+                               die "Unable to parse rename function '$1'";
+                       }
+               } elsif ($new) {
+                       $rename = sub { my $s=shift; $s = $old->($s); return $new->($s); };
+               } else {
+                       my $x = $n;
+                       $rename = sub { return $x; };
+               }
+       }
+       $rename;
+}
+
+# pre-process {data}
+sub preprocess {
+       my $api = shift;
+
+       # Find any anonymous types and add them in
+       my %anonymous = ();
+       foreach my $s (values %{$api->{data}}) {
+
+               foreach my $m (grep { $_->{type} =~ m/struct:|union:/} @{$s->{items}}) {
+                       $anonymous{$m->{type}} = 1 if !defined($api->{data}->{$m->{type}});
+               }
+
+               # add 'result' name
+               $s->{result}->{name} = 'result$' if $s->{type} =~ m/func|call/;
+
+               # add a canonical deref for all types
+               if ($s->{type} =~ m/func|call|struct|union/) {
+                       foreach my $m (grep { !defined($_->{deref}) } @{$s->{items}}, ($s->{type} =~ m/func|call/) ? $s->{result} : ()) {
+                               if ($m->{type} =~ m/^(union|struct):(.*)/) {
+                                       $m->{deref} = "\${$2}";
+                               } elsif ($m->{ctype} eq 'bitfield') {
+                                       $m->{deref} = "bitfield";
+                               } else {
+                                       $m->{deref} = $m->{type};
+                               }
+                       }
+               }
+
+               # all 'defines' are output by default
+               $s->{output} = 1 if $s->{type} eq 'define';
+       }
+
+       foreach my $k (sort keys %anonymous) {
+               print " anon $k\n";
+               if ($k =~ m/^(.*):(.*)$/) {
+                       $api->{data}->{$k} = {
+                               name => $2,
+                               type => $1,
+                               size => 0,
+                               items => [],
+                       };
+               }
+       }
+}
+
+# preprocess {api}
+sub analyseAPI {
+       my $api = shift;
+
+       # Note that type:name regexes always start at the beginning
+
+       foreach my $obj (@{$api->{api}}) {
+               $obj->{match} = "$obj->{type}:$obj->{name}";
+
+               if ($obj->{name} =~ m@^/(.*)/$@) {
+                       $obj->{regex} = qr/^$obj->{type}:$1/;
+               } elsif ($obj->{name} =~ m@^<invoke=(.*)>$@) {
+                       $obj->{matcher} = eval "sub { $1(\@_) }";
+                       die "unable to parse match function $obj->{name} $! $@" if !defined($obj->{matcher});
+               } else {
+                       $obj->{regex} = qr/^$obj->{type}:$obj->{name}$/;
+               }
+
+               foreach my $opt (@{$obj->{options}}) {
+                       if ($opt =~ m/^(.+:rename)=(.*)$/) {
+                               $obj->{$1} = parseRename($api, $2);
+                       } elsif ($opt =~ m/^rename=(.*)$/) {
+                               $obj->{"$obj->{type}:rename"} = parseRename($api, $1);
+                       }
+               }
+
+               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->{type} = $mode;
+                       if ($match =~ m@^/(.*)/$@) {
+                               $inc->{regex} = $mode ne 'field' ? qr/$mode:$1/ : qr/$1/;
+                       } elsif ($match =~ m@^<invoke>$@) {
+                               $inc->{matcher} = eval "sub { $inc->{literal} }";
+                               die "unable to parse match function $inc->{literal} $! $@" if !defined($inc->{matcher});
+                       } elsif ($match =~ m@^<invoke=(.*)>$@) {
+                               $inc->{matcher} = eval "sub { $1(\@_) }";
+                               die "unable to parse match function $inc->{match} $! $@" if !defined($inc->{matcher});
+                       } 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($api, $1);
+                               }
+                       }
+
+                       $inc->{"$mode:rename"} = $obj->{"$mode:rename"} if (!(defined($inc->{"$mode:rename"})) && defined($obj->{"$mode:rename"}));
+               }
+       }
+}
+
+#
+# 'lib/struct/func' level    'lib.inc' level            'func/struct.inc' level  setting
+#  array:name1                array:name1                array                    char* is array, void* is a Segment
+#  array-size:name1=name2     array-size:name1=name2     array-size=name2         implied array size from name2
+#  implied:name1=expr         implied:name1=expr         implied=expr             value of name1 is calculated from other arguments
+#  instance:name1             instance:name1             instance                 parameter/return is instance, implies non-static function
+
+#  scope:name1=scope[,close]  scope:name1=scope[,close]  scope=scope[,close]      parameter/return is a new instance with given scope
+#  success:name1=values       success:name1=values       success=values           which parameter and values indicate integer success
+#                                                                                 pointer types are considered in/out and implied
+#  success:name1=!null        success:name1=!null        success=!null            which parameter and values indicate non-null success
+#  return:name1               return:name1               return                   return this output argument instead of the return code
+
+#  access:name1=rwi           access:name1=rwi           access=rwi               set access type, read, write, indexed
+#  access=rwi                 access=rwi                 access=rwi                - can also be set as default
+
+#  onsuccess=blah             onsuccess=blah             onsuccess=blah
+
+# 'name' is parameter name, or parameter index, or result$ for return value
+# scope is instance for instance-scope, explicit for explicit scope, global for global scope.  default is global.
+
+# err, maybe this format makes more sense:
+#   name1:success=values   name1:success=values  success=values
+#   name1:instance         name1:instance        instance
+
+my @itemFlags = (
+       'array',
+       'segment',
+       'instance',
+       'return',
+       'raw',
+       'raw-in',
+);
+
+my @itemOptions = (
+       'array-size',
+       'scope',
+       'success',
+       'implied',
+       'tonative',
+);
+
+my @itemAnyOptions = (
+       'access',
+);
+
+# process struct->field
+#  api, obj (struct, func), inc (field entry), s (struct), m (struct field)
+# Note: keep in sync with processFunc
+sub processField {
+       my $api = shift;
+       my $obj = shift;
+       my $inc = shift;
+       my $s = shift;
+       my $m = shift;
+       my $def = $api->{index}->{"$s->{type}:<default>"};
+
+       #print "process $s->{type}:$s->{name}.$m->{name}\n";
+       print "  $m->{name}\n" if ($api->{vars}->{verbose} > 1);
+
+       foreach my $flag (@itemFlags) {
+               my $value = optionFlag("$flag", $inc);
+               $value = optionFlag("$flag:$m->{name}", $obj, $def) if !defined($value);
+               $m->{$flag} = $value if (defined($value));
+               $m->{select}->{$flag} = 1 if (defined($value));
+       }
+       foreach my $option (@itemOptions) {
+               my $value = optionValue("$option", undef, $inc);
+               $value = optionValue("$option:$m->{name}", undef, $obj, $def) if !defined($value);
+               $m->{$option} = $value if (defined($value));
+               $m->{select}->{$option} = 1 if (defined($value));
+       }
+       foreach my $option (@itemAnyOptions) {
+               my $value = optionValue("$option", undef, $inc);
+               $value = optionValue("$option:$m->{name}", undef, $obj, $def) if !defined($value);
+               $value = optionValue("$option", undef, $obj, $def) if !defined($value);
+               $m->{$option} = $value if (defined($value));
+       }
+
+       $m->{output} = 1;
+       $m->{rename} = (first { defined $_ } $inc->{'field:rename'}, $obj->{'field:rename'}, $def->{'field:rename'}, $renameTable{identity})->($m->{name}, $m, $s);
+}
+
+# process func or call main type
+sub processTypeFunc {
+       my $api = shift;
+       my $seen = shift;
+       my $obj = shift;
+       my $def = $api->{index}->{"$obj->{type}:<default>"};
+
+       foreach my $s (@_) {
+               my $v;
+
+               if ($seen->{"$s->{type}:$s->{name}"}++) { print "warning: seen $s->{type}:$s->{name}\n"; next; }
+
+               print " $s->{name}\n" if ($api->{vars}->{verbose} > 1);
+
+               foreach my $m ($s->{result}, @{$s->{items}}) {
+                       my $inc = findItem($obj, $m->{name});
+
+                       processField($api, $obj, $inc, $s, $m);
+               }
+
+               $s->{rename} = (first { defined $_ } $obj->{"$s->{type}:rename"}, $renameTable{identity})->($s->{name}, $s);
+               $s->{access} = optionValue('access', '', $obj, $def);
+               $v = optionValue('onsuccess', undef, $obj, $def);
+               $s->{onsuccess} = $v if defined $v;
+
+               postProcessType($s);
+       }
+}
+
+# process library->func
+# process struct->func
+#  api, obj (library, struct), inc (func entry), s ($data->{func:name})
+# Note: keep in sync with processField
+sub processFunc {
+       my $api = shift;
+       my $obj = shift;
+       my $inc = shift;
+       my $s = shift;
+       my @params = (defined($s->{result}) ? $s->{result} : (), @{$s->{items}});
+       my $index = -1;
+       my $lindex = -2 - $#params;
+       my $def = $api->{index}->{"$s->{type}:<default>"};
+       my $v;
+
+       print "process $s->{type}:$s->{name}\n" if ($api->{vars}->{verbose} > 1);
+
+       foreach my $m (defined($s->{result}) ? $s->{result} : (), @{$s->{items}}) {
+               #print "[$index] [$lindex] $m->{name}\n";
+
+               foreach my $flag (@itemFlags) {
+                       my $value = optionFlag("$flag:$m->{name}", $inc, $obj, $def);
+                       $value = optionFlag("$flag:$index", $inc, $obj, $def) if !defined($value);
+                       $value = optionFlag("$flag:$lindex", $inc, $obj, $def) if !defined($value);
+                       $m->{$flag} = $value if (defined($value));
+                       $m->{select}->{$flag} = 1 if (defined($value));
+               }
+               foreach my $option (@itemOptions) {
+                       my $value = optionValue("$option:$m->{name}", undef, $inc, $obj, $def);
+                       $value = optionValue("$option:$index", undef, $inc, $obj, $def) if !defined($value);
+                       $value = optionValue("$option:$lindex", undef, $inc, $obj, $def) if !defined($value);
+                       $m->{$option} = $value if (defined($value));
+                       $m->{select}->{$option} = 1 if (defined($value));
+               }
+               foreach my $option (@itemAnyOptions) {
+                       my $value = optionValue("$option:$m->{name}", undef, $inc, $obj, $def);
+                       $value = optionValue("$option:$index", undef, $inc, $obj, $def) if !defined($value);
+                       $value = optionValue("$option:$lindex", undef, $inc, $obj, $def) if !defined($value);
+                       $value = optionValue("$option", undef, $inc, $obj, $def) if !defined($value);
+                       $m->{$option} = $value if (defined($value));
+               }
+               $m->{output} = 1;
+               $index++;
+               $lindex++;
+       }
+
+       $s->{rename} = (first { defined $_ } $inc->{"$s->{type}:rename"}, $obj->{"$s->{type}:rename"}, $renameTable{identity})->($s->{name}, $s);
+       $s->{access} = optionValue('access', '', $inc, $obj, $def);
+       $v = optionValue('onsuccess', undef, $inc, $obj, $def);
+       $s->{onsuccess} = $v if defined ($v);
+
+       postProcessType($s);
+}
+
+# finally link up array sizes and work out what is output or not
+# TODO: struct field array-size should be output but not included in constructors?
+sub postProcessType {
+       my $s = shift;
+       my $static = 1;
+
+       foreach my $m (defined($s->{result}) ? $s->{result} : (), @{$s->{items}}) {
+               $static = 0 if ($m->{instance});
+
+               # FIXME: collect multiple scopes here
+               $s->{scope} = 'explicit' if ($m->{scope} =~ m/^explicit$|^explicit,(.*)$/);
+               $s->{scope} = 'global' if ($m->{scope} eq 'global');
+               $s->{scope} = 'object' if ($m->{scope} eq 'object');
+
+               if ($m->{'array-size'}) {
+                       my $size = findField($s, $m->{'array-size'});
+
+                       print Dumper($s) if (!defined($size));
+                       die "can't find array-size=$m->{'array-size'}" if !defined($size);
+
+                       $size->{output} = 0 if ($s->{type} =~ m/func|call/);
+                       $size->{'array-size-source'} = $m;
+                       $m->{'array-size'} = $size;
+               }
+
+               # no java arg for instance parameter
+               $m->{output} = 0 if ($m->{instance});
+               # don't generate java args for values calculated
+               $m->{output} = 0 if (defined($m->{implied}));
+               # don't generate java args for return statuss unless they're also the constructed value
+               $m->{output} = 0 if (defined($m->{success}) && !$m->{scope});
+               # don't generate java args for output arguments
+               $m->{output} = 0 if ($m->{return} && $m->{name} ne 'result$');
+
+               # link success/return fields to struct/func
+               $s->{success} = $m if defined($m->{success});
+               $s->{return} = $m if ($m->{return});
+       }
+
+       $s->{static} = $static;
+
+       # TODO: default scope?  or from struct:<default> etc?
+}
+
+# transfer info from {api} to {data}
+sub processFields {
+       my $api = shift;
+       my $seen = shift;
+       my $obj = shift;
+       my $inc = shift;
+       my $s = shift;
+
+       foreach my $m (@_) {
+               next if $seen->{$m->{name}}++;
+
+               processField($api, $obj, $inc, $s, $m);
+       }
+}
+
+# process a struct/union
+# these have fields as well as potentially other included types
+sub processType {
+       my $api = shift;
+       my $seen = shift;
+       my $obj = shift;
+       my $def = $api->{index}->{"$obj->{type}:<default>"};
+
+       foreach my $s (@_) {
+               my $memberseen = {};
+
+               next if ($seen->{"$s->{type}:$s->{name}"}++);
+
+               print "process type $obj->{match} $s->{type}:$s->{name}\n" if ($api->{vars}->{verbose} > 1);
+
+               # process the struct/union fields first
+               foreach my $inc (grep { $_->{type} eq 'field' } @{$obj->{items}}) {
+                       my @list = grep { $_->{name} =~ m/$inc->{regex}/ } @{$s->{items}};
+
+                       processFields($api, $memberseen, $obj, $inc, $s, @list);
+               }
+
+               if (optionValue('default', undef, $obj, $def) eq 'all') {
+                       print " + adding all fields\n" if ($api->{vars}->{verbose} > 1);
+                       processFields($api, $memberseen, $obj, undef, $s, @{$s->{items}});
+               }
+
+               $s->{rename} = (first { defined $_ } $obj->{"$s->{type}:rename"}, $def->{"$s->{type}:rename"}, $renameTable{identity})->($s->{name}, $s);
+
+               # finish off
+               postProcessType($s);
+
+               # handle other types included/mark them no-output
+               #$seen->{"$s->{type}:$s->{name}"} = 0;
+               processLibrary($api, $seen, $obj, $s);
+       }
+
+}
+
+sub processLibrary {
+       my $api = shift;
+       my $seen = shift;
+       my $lib = shift;
+       my $ctx = shift;
+       my $data = $api->{data};
+
+       #return if ($seen->{"$ctx->{type}:$ctx->{name}"}++);
+
+       print "process library $ctx->{type}:$ctx->{name}\n" if ($api->{vars}->{verbose} > 0);
+       #print 'lib='.Dumper($lib);
+       #print Dumper($api->{api}); die;
+       #print 'lib.options='.Dumper($lib->{options});
+
+       # TODO: embedded types
+
+       foreach my $inc (@{$lib->{items}}) {
+               print " $inc->{match}\n" if ($api->{vars}->{verbose} > 1);
+
+               if ($inc->{type} =~ m/func|call/on) {
+                       my @list = findMatches($api, $inc, $ctx);
+
+                       #print 'inc='.Dumper($inc);
+                       #print "match $inc->{regex} .options=".Dumper($inc->{options});
+
+                       foreach my $s (@list) {
+                               if ($seen->{"$s->{type}:$s->{name}"}++) { print "warning: seen $s->{type}:$s->{name}\n"; next; }
+
+                               #print " $s->{name}\n";
+                               processFunc($api, $lib, $inc, $s);
+
+                               #print 'func='.Dumper($s);
+                       }
+               } elsif ($inc->{type} eq 'library') {
+                       foreach my $l (grep { "$_->{type}:$_->{name}" =~ m/$inc->{regex}/ } @{$api->{api}}) {
+                               print " -> $l->{name}\n";
+                               # included libraries are never output directly
+                               $l->{output} = 0;
+                               processLibrary($api, $seen, $l, $ctx);
+                       }
+               } elsif ($inc->{type} =~ m/define|enum/on) {
+                       # suppress direct output of anything included
+                       foreach my $c (findMatches($api, $inc, $ctx)) {
+                               $c->{output} = 0;
+                       }
+               }
+       }
+
+       if (defined optionValue('import', undef, $lib)) {
+               my @x = map { split /,/,$_ } optionValues('import', $lib);
+               $lib->{imports} = \@x;
+       }
+}
+
+sub postprocess {
+       my $api = shift;
+       my $seen = {};
+       my %data = %{$api->{data}};
+
+       # apply requested options to specific objects (not defaults)
+       foreach my $obj (grep {$_->{type} =~ m/^(func|call|struct|union)$/} @{$api->{api}}) {
+               my @list = findMatches($api, $obj, $obj);
+
+               if ($obj->{type} =~ m/func|call/) {
+                       processTypeFunc($api, $seen, $obj, @list);
+               } else {
+                       processType($api, $seen, $obj, @list);
+               }
+       }
+
+       # handle libraries
+       foreach my $lib (grep {$_->{type} eq 'library'} @{$api->{api}}) {
+               next if defined($lib->{output});
+               $lib->{output} = 1;
+               processLibrary($api, $seen, $lib, $lib);
+       }
+
+       # apply options for default object types
+       foreach my $obj (grep {$_->{name} eq '<default>'} @{$api->{api}}) {
+               my @list;
+
+               @list = grep { !defined($seen->{"$_->{type}:$_->{name}"}) && $_->{type} eq $obj->{type} } values %data;
+
+               print "apply $obj->{type}:$obj->{name} to ".($#list + 1)." objects\n" if ($#list >= 0);
+
+               if ($obj->{type} =~ m/func|call/) {
+                       processTypeFunc($api, $seen, $obj, @list);
+               } else {
+                       processType($api, $seen, $obj, @list);
+               }
+       }
+}
+
+1;
diff --git a/src/notzed.nativez/lib/code.api b/src/notzed.nativez/lib/code.api
new file mode 100644 (file)
index 0000000..651dd55
--- /dev/null
@@ -0,0 +1,439 @@
+# -*- 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}
+       }
+       }}
+
+       invoke-dynamic-init {{
+               {name}$FH = Memory.downcall("{name}", {function-descriptor}, resolve, scope);
+       }}
+
+       invoke-dynamic {{
+       final MethodHandle {name}$FH;
+       public {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 FunctionPointer<{rename}> downcall(MemoryAddress addr$, ResourceScope scope$) {
+               NativeSymbol symbol$ = NativeSymbol.ofAddress("{rename}", addr$, scope$);
+               MethodHandle {rename}$FH = Memory.downcall(symbol$, descriptor());
+               return new 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 FunctionPointer<{rename}> upcall({rename} target$, ResourceScope scope$) {
+               interface Trampoline {
+                       {java-result} call({native-arguments});
+               }
+               Trampoline trampoline = ({native-arguments}) -> {
+                       // frame?  scope?
+                       try (ResourceScope upcallScope$ = ResourceScope.newConfinedScope()) {
+                               {trampoline-result-define}target$.call({java-call});
+                               {trampoline-result-return}
+                       }
+               };
+               return new FunctionPointer<>(
+                       Memory.upcall(
+                               MethodHandles.lookup(),
+                               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 au.notzed.nativez.*;
+{imports}
+
+public class {name} {
+{defines}
+{enums}
+{funcs}
+}
+       }}
+       library-dynamic
+               func:template=code:method=invoke-dynamic
+               init:template=code:method=invoke-dynamic-init {{
+package {package};
+import jdk.incubator.foreign.*;
+import java.lang.invoke.*;
+import java.util.function.Function;
+import au.notzed.nativez.*;
+{imports}
+
+public class {name} {
+       {name}(Function<String,MemoryAddress> resolve, ResourceScope scope) {
+{init}
+       }
+       public static {name} create(Function<String,MemoryAddress> resolve, ResourceScope scope) {
+               return new {name}(resolve, scope);
+       }
+{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 au.notzed.nativez.*;
+
+public class {rename} implements Pointer {
+
+       public final MemorySegment segment;
+
+       private {rename}(MemorySegment segment) {
+               this.segment = segment;
+{init}
+       }
+
+       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}
+{layout}
+{accessors}
+{methods}
+{varhandles}
+}
+}}
+       struct-array {{
+package {package};
+import jdk.incubator.foreign.*;
+import jdk.incubator.foreign.MemoryLayout.*;
+import java.lang.invoke.*;
+import au.notzed.nativez.*;
+
+public class {rename} implements Pointer, Array<{rename}> {
+
+       public final MemorySegment segment;
+
+       private {rename}(MemorySegment segment) {
+               this.segment = segment;
+{init}
+       }
+
+       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;
+       }
+
+       public static {rename} createArray(MemoryAddress address, long length, ResourceScope scope) {
+               return MemoryAddress.NULL != address ? create(MemorySegment.ofAddress(address, LAYOUT.byteSize() * length, scope)) : null;
+       }
+
+       public static {rename} createArray(long length, SegmentAllocator alloc) {
+               return create(alloc.allocateArray(LAYOUT, length));
+       }
+
+       @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 boolean isEmpty() {
+               return segment.byteSize() == 0;
+       }
+
+       @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}
+{layout}
+{accessors}
+{methods}
+       final static MethodHandle {name}$SH = MemoryLayout.sequenceLayout(LAYOUT).sliceHandle(PathElement.sequenceElement());
+{varhandles}
+}
+}}
+       handle {{
+package {package};
+import jdk.incubator.foreign.*;
+import java.lang.invoke.*;
+import au.notzed.nativez.*;
+
+public class {rename} implements Pointer {
+
+       MemoryAddress address;
+       ResourceScope scope;
+
+       private {rename}(MemoryAddress address, ResourceScope scope) {
+               this.address = address;
+               this.scope = scope;
+{init}
+       }
+
+       public static {rename} create(MemoryAddress address, ResourceScope scope) {
+               return MemoryAddress.NULL != address ? new {rename}(address, scope) : null;
+       }
+
+       public static HandleArray<{rename}> createArray(long count, SegmentAllocator alloc) {
+               return HandleArray.createArray(count, alloc, {rename}::create);
+       }
+
+       @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 au.notzed.nativez.*;
+
+@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 {{
+       # TODO: final static VarHandle {name}$VI = MemoryLayout.sequenceLayout(LAYOUT).varHandle(PathElement.sequenceElement(), PathElement.groupElement("{name}"));
+       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};
+       }
+       }}
+}
+
+# set requires allocations, not sure if it should be Frame or Scope?
+code getset-frame {
+       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}(Frame frame$, {type} value) {
+               {setnative};
+       }
+       }}
+       seti set=value=value set=segment=segment {{
+       public void set{rename}AtIndex(Frame frame$, 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/notzed.nativez/lib/code.pm b/src/notzed.nativez/lib/code.pm
new file mode 100644 (file)
index 0000000..9383602
--- /dev/null
@@ -0,0 +1,320 @@
+package code;
+
+use strict;
+
+use File::Path qw(make_path);
+use File::Basename;
+use Data::Dumper;
+use List::Util qw(first);
+
+require api;
+
+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 = api::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 $seen = 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};";
+       } grep {
+               $seen->{"value:$_->{name}"}++ == 0
+       } @{$s->{items}};
+
+       return "\t$d\n";
+}
+
+# somewhat faster
+# dunno how to ignore comments tho
+sub formatTemplate {
+       my $template = shift;
+       my $vars = shift;
+       my $prefix = shift;
+       my $result;
+
+       while ($template =~ m/^(.*?)\{([\w-]+)\}/spo) {
+               $result .= $1;
+
+               if (defined $vars->{$2}) {
+                       $template = $vars->{$2}.${^POSTMATCH};
+               } else {
+                       $result .= "{$2}";
+                       $template = ${^POSTMATCH};
+               }
+       }
+       $result .= $template;
+       $result =~ s/^/$prefix/gm;
+       $result;
+}
+
+# Format a template without recursive application
+sub formatTemplateStream {
+       my $template = shift;
+       my $vars = shift;
+       my $prefix = shift;
+       my $result;
+
+       while ($template =~ m/^(.*?)\{([\w-]+)\}/spo) {
+               $result .= $1;
+
+               if (defined $vars->{$2}) {
+                       $result .= $vars->{$2};
+               } else {
+                       $result .= "{$2}";
+               }
+               $template = ${^POSTMATCH};
+       }
+       $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 $stream = shift;
+       my $vars = \%{$match};
+
+       #print "template: $code->{match}\n";
+
+       foreach my $set (api::optionValuesAll('set', $code)) {
+               $vars->{$1} = $2 if ($set =~ m/^(\w+)=(.*)/);
+       }
+
+       $stream ? formatTemplateStream($code->{literal}, $vars) : 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;
diff --git a/src/notzed.nativez/lib/config.pm b/src/notzed.nativez/lib/config.pm
new file mode 100644 (file)
index 0000000..21a3973
--- /dev/null
@@ -0,0 +1,183 @@
+package config;
+
+use File::Basename;
+use strict;
+
+require tokenise;
+
+#
+
+# parser for control file
+
+# format:
+# type name (token) * {
+#  ( match (params) *
+#    ( {{ literal }} | ; )
+#  )*
+# }
+# or
+# type name (token *) {{
+#   literal
+# }}
+
+# tokens are separated by lwsp or ';'
+
+sub new {
+       my $class = shift;
+       my $options = shift;
+       my $self = {
+               options => $options,
+               objects => [],
+               includes => [],
+               pragmas => [],
+       };
+
+       foreach my $path (@_) {
+               loadControlFile($self, $path);
+       }
+
+       bless $self, $class;
+       return $self;
+}
+
+# find first occurance of flag
+# name, ...objects
+sub optionFlag {
+       my $name = shift;
+
+       foreach my $obj (@_) {
+               foreach my $opt (@{$obj->{options}}) {
+                       return 1 if ($opt eq $name);
+               }
+       }
+       undef;
+}
+
+# find value of first option of the given name
+# name, ...objects
+sub optionValue {
+       my $name = shift;
+       my $or = shift;
+
+       foreach my $obj (@_) {
+               foreach my $opt (@{$obj->{options}}) {
+                       return $1 if ($opt =~m/^\Q$name\E=(.*)$/);
+               }
+       }
+       $or;
+}
+
+sub findInclude {
+       my $self = shift;
+       my $file = shift;
+
+       foreach my $dir (@{$self->{options}->{include}}) {
+               my $path = "$dir/$file";
+               return $path if -e $path;
+       }
+
+       return $file;
+}
+
+sub loadControlFile {
+       my $self = shift;
+       my $path = shift;
+       my $list = $self->{objects};
+       my $target;
+       my $item;
+       my $literal;
+       my $state = 0;
+       my @states;
+       my $tokeniser = new tokenise($path);
+
+       while (my $t = $tokeniser->next()) {
+               #print " $state $t\n";
+               if ($state == 0) {
+                       if ($t =~ m/^%/) {
+                               my @pragma = ( $t );
+                               my $a = $tokeniser->next();
+
+                               while (defined($a) && $a ne ';') {
+                                       push @pragma, $a;
+                                       $a = $tokeniser->next();
+                               }
+
+                               if ($t eq '%include') {
+                                       my $file = findInclude($self, $pragma[1]);
+
+                                       print "including $file\n";
+
+                                       push @{$self->{includes}}, $file;
+                                       $tokeniser->include($file);
+                               } else {
+                                       push @{$self->{pragmas}}, \@pragma;
+                               }
+                       } elsif ($tokeniser->{type} eq 'token') {
+                               $target = { type => $t, options => [], items => [], file => "$tokeniser->{file}->{path}:$tokeniser->{file}->{lineno}" };
+                               push @$list, $target;
+                               $state = 1;
+                       } else {
+                               die("$tokeniser->{file}->{path}:$tokeniser->{file}->{lineno}:$tokeniser->{colno}: expected type token");
+                       }
+               } elsif ($state == 1) {
+                       # token [token]
+                       if ($tokeniser->{type} eq 'token') {
+                               $target->{name} = $t;
+                               $state = 2;
+                       } else {
+                               die("$tokeniser->{file}->{path}:$tokeniser->{file}->{lineno}:$tokeniser->{colno}: expected match token");
+                       }
+               } elsif ($state == 2) {
+                       # token token token* [ '{' | literal | ';' ]
+                       if ($t eq '{') {
+                               $state = 3;
+                       } elsif ($t eq ';') {
+                               $state = 0;
+                       } elsif ($tokeniser->{type} eq 'literal') {
+                               $target->{literal} = stripLiteral($t);
+                               $state = 0;
+                       } elsif ($tokeniser->{type} eq 'token') {
+                               push @{$target->{options}}, $t;
+                       } else {
+                               die("$tokeniser->{file}->{path}:$tokeniser->{file}->{lineno}:$tokeniser->{colno}: expecting {, ;, or {{literal}} or option: not '$t'");
+                       }
+               } elsif ($state == 3) {
+                       if ($t eq '}') {
+                               $state = 0;
+                       } elsif ($tokeniser->{type} eq 'token') {
+                               $item = { match => $t, options => [] };
+                               push @{$target->{items}}, $item;
+                               $state = 4;
+                       } else {
+                               die("$tokeniser->{file}->{path}:$tokeniser->{file}->{lineno}:$tokeniser->{colno}: expecting } or match token");
+                       }
+               } elsif ($state == 4) {
+                       if ($t eq ';') {
+                               $state = 3;
+                       } elsif ($tokeniser->{type} eq 'token') {
+                               push @{$item->{options}}, $t;
+                       } elsif ($tokeniser->{type} eq 'literal') {
+                               $item->{literal} .= stripLiteral($t);
+                               $state = 3;
+                       } else {
+                               die("$tokeniser->{file}->{path}:$tokeniser->{file}->{lineno}:$tokeniser->{colno}: not expecting type=$tokeniser->{type} value=$t");
+                       }
+               }
+       }
+       die "parse error in '$path' state=$state" if $state;
+}
+
+sub stripLiteral {
+       my $t = shift;
+
+       if ($t =~ m/\n/s) {
+               $t =~ s/^\s*\n//s;
+               $t =~ s/\n\s+$/\n/s;
+       } else {
+               $t =~ s/^\s+//;
+               $t =~ s/\s+$//;
+       }
+       $t;
+}
+
+1;
diff --git a/src/notzed.nativez/lib/method.pm b/src/notzed.nativez/lib/method.pm
new file mode 100644 (file)
index 0000000..608cb78
--- /dev/null
@@ -0,0 +1,175 @@
+
+package method;
+
+use strict;
+use Data::Dumper;
+
+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;
+
+       # TBH this just isn't going to work, scopes are so limited in use
+       return ();
+
+       if ($m->{field}->{scope} =~ m/^explicit,(.*)$/) {
+               return code::formatTemplate("scope\$.addCloseAction(() -> $1;", { %{$m->{match}}, value => 'res$' });
+       } elsif ($m->{field}->{scope} =~ m/^explicit$/) {
+               return code::formatTemplate('scope$.addCloseAction(() -> close({name}));', $m->{match});
+       } 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()";
+               } elsif ($m->{implied}) {
+                       $m->{implied};
+               } else {
+                       my $name = $_->{match}->{name};
+
+                       if ($m->{output} == 0 && ($m->{deref} =~ m/^u64:/)) {
+                               $name .= '$h';
+                       } elsif (defined($m->{'array-size-source'})) {
+                               # or some function?
+                               $name = "Memory.size($m->{'array-size-source'}->{name})";
+                       }
+                       code::formatTemplate("{tonative}", { %{$_->{match}}, value=>$name })
+               }
+       } @members;
+
+       if ($result->{field}->{deref} =~ m/^\$\{\w+\}$/) {
+               # non-primitive return, args requires a segmentallocator
+               $info->{'native-call'} = 'alloc$, '.$info->{'native-call'};
+               $info->{'java-arguments'} = $info->{'java-arguments'} ?
+                       $info->{'java-arguments'}.', SegmentAllocator alloc$'
+                       :'SegmentAllocator alloc$';
+       }
+
+       # for java upcalls, they have an explicit scope always
+       $info->{'java-call'} = join ', ', map { code::formatTemplate('{tojava}', { %{$_->{match}}, value=>'{name}', scope=>'upcallScope$' }) } 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}->{implied}
+               && $_->{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' || $_->{field}->{'raw-in'};
+       } @members) ? '(Frame frame$ = Frame.frame()) ' : '';
+
+       # 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 ({type}){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/notzed.nativez/lib/tokenise.pm b/src/notzed.nativez/lib/tokenise.pm
new file mode 100644 (file)
index 0000000..478ed73
--- /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/notzed.nativez/lib/types.api b/src/notzed.nativez/lib/types.api
new file mode 100644 (file)
index 0000000..9c3c91a
--- /dev/null
@@ -0,0 +1,283 @@
+# -*- Mode:text; tab-width:4; electric-indent-mode: nil; indent-line-function:insert-tab; -*-
+
+#
+# TODO: use select=array rather than inline logic to select alternatives? maybe?
+
+# 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}, (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+)\}\]$/ template=code:getbyvalue {
+       type            {{ "$data->{$m->{type}}->{rename}" }}
+       layout          {{ "MemoryLayout.sequenceLayout({length}, {type}.LAYOUT)" }}
+
+       getsegment      {{ '(MemorySegment){name}$SH.invokeExact({segment})' }}
+       getnative       {{ '{type}.create({getsegment})' }}
+
+       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"
+       }}
+
+}
+
+# 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        {{ "(Addressable)Memory.address({value})" }}
+       lea                     {{ "(MemorySegment)$m->{name}\$SH.invokeExact({segment})" }}
+       getnative       {{ "{type}.create({lea})" }}
+       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   {{ "(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(), MemoryLayout.PathElement.sequenceElement());\n"
+       }}
+}
+
+# any pointer with 'raw' option
+type /^u64:/ select=raw copy=<pointer> {
+}
+
+# pointer is an 'in' only pointer to pointer
+type /^u64:u64:/ select=raw-in copy=<pointer> {
+       tonative        {{ '(Addressable)frame$.allocate(Memory.POINTER, {value})' }}
+}
+
+# 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> {
+       length          {{ $m->{'array-size'} ? 'get'.($m->{'array-size'}->{rename}).'()' : 'Long.MAX_VALUE' }}
+       type            {{ !$m->{array} ? 'PointerArray' : 'HandleArray<PointerArray>' }}
+       tojava          {{
+               !$m->{array}
+               ? 'PointerArray.createArray({value}, {length}, {scope})'
+               : 'HandleArray.createArray({value}, {length}, PointerArray::create, {scope})'
+       }}
+}
+
+# *primitive, or string for [iNNi8]
+# TODO: length, multiple flags?
+type /^u64:(?<ctype>[ui]8)$/ 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>[ui]8)$/ 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        {{ '(Addressable)({value} != null ? frame$.copy({value}) : MemoryAddress.NULL)' }}
+
+       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        {{ '(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? with size
+# maybe should be handled by getbyvalue
+type /^u64:\$\{(\w+)\}$/ select=array-size copy=<pointer> {
+       type            {{ "$data->{$m->{type}}->{rename}" }}
+       length          {{ 'get'.($m->{'array-size'}->{rename}).'()' }}
+       tojava          {{ "$data->{$m->{type}}->{rename}.createArray({value}, {length}, {scope})" }}
+}
+
+# *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' }}
+       length          {{ 'get'.($m->{'array-size'}->{rename}).'()' }}
+       tojava          {{ "MemorySegment.ofAddress({value}, {length}, {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         {{ 'MemorySegment' }}
+       type            {{ "$data->{$m->{type}}->{rename}" }}
+       tojava          {{ '{type}.create({value})' }}
+       tonative        {{ '({value}).segment()' }}
+       getnative       {{ '{type}.create((MemorySegment){name}$SH.invokeExact({segment}))' }}
+       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        {{ '(Addressable)Memory.address({value})' }}
+}
diff --git a/src/notzed.nativez/native/export.cc b/src/notzed.nativez/native/export.cc
new file mode 100644 (file)
index 0000000..d8b183b
--- /dev/null
@@ -0,0 +1,1227 @@
+/*
+ * export c types and prototypes to perl file.
+ *
+ * Copyright (c) 2019 Yonatan Goldschmidt
+ * Copyright (c) 2020,2021 Michael Zucchi
+ *
+ * The MIT License (MIT)
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+/*
+   skeleton taken from structsizes.cc plugin by Richard W.M. Jones
+   https://rwmj.wordpress.com/2016/02/24/playing-with-gcc-plugins/
+   some other bits from
+   https://blog.adacore.com/bindings-gcc-plugins
+ */
+
+/*
+  TODO: get header name from tree
+ */
+
+/*
+
+function declarations, i think
+
+                  chain            param type                 name              size
+TYPE_DECL         TYPE_ARG_TYPES   TYPE_VALUE(item):TREE_LIST  <empty>           TYPE_SIZE(item_type)
+ /POINTER
+ /FUNCTION_TYPE
+FUNCTION_DECL     DECL_ARGUENTS    TREE_TYPE(item):PARM_DECL   DECL_NAME(item)   DECL_SIZE(item)
+                                                                                 TYPE_SIZE(item_type)
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <string.h>
+#include <gcc-plugin.h>
+#include <tree.h>
+#include <print-tree.h>
+#include <tree-pretty-print.h>
+
+#include "list.h"
+
+#define D(x) do { x; } while(0)
+
+extern const char *tree_codes[];
+#define ZTREE_CODE(x) tree_codes[TREE_CODE(x)]
+
+//Enums have a type, otherwise they're just integers
+//#define TYPED_ENUMS
+
+// remove some of the debug
+//#define NDEBUG
+
+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;
+
+static void debug_tree_helper(tree t, const char *msg) {
+#ifndef NDEBUG
+       if (debug_level > 2) {
+               fflush(stdout);
+               fprintf(stderr, "dumping tree: '%s'\n", msg);
+               debug_tree(t);
+               fprintf(stderr, "\n\n");
+               fflush(stdout);
+       }
+#endif
+}
+
+static struct hash dumped;
+static struct list todump;
+static struct list context_stack;
+static struct list parameters; // last list of params
+
+static struct hash forward_types;
+
+static int generate(const char *fmt, ...) {
+       int res = 0;
+       if (output_enabled) {
+               va_list ap;
+               va_start(ap, fmt);
+               res = vfprintf(output_file, fmt, ap);
+               va_end(ap);
+       }
+       return res;
+}
+
+/*
+  Join all names in the stack, in reverse order.
+*/
+static char *stack_path(struct list *stack, const char *sep) {
+       size_t total = 1;
+
+       for (struct node *n = stack->tail; n; n=n->link)
+               total += strlen(n->name)+strlen(sep);
+
+       char *data = (char *)xmalloc(total);
+       char *p = data;
+
+       // FIXME: some other context name
+       for (struct node *n = stack->tail; n; n=n->link) {
+               p = stpcpy(p, n->name);
+               if (n->link)
+                       p = stpcpy(p, sep);
+       }
+
+       return data;
+}
+
+static void list_clear(struct list *list) {
+       struct node *node;
+
+       while ((node = list_remhead(list))) {
+               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);
+}
+
+static void print_spaces(int n) {
+       if (output_enabled) {
+               for (int i = 0; i < n; ++i)
+                       fputc('\t', output_file);
+       }
+}
+
+static int is_ref_type(tree type) {
+       switch (TREE_CODE(type)) {
+       case VECTOR_TYPE:
+       case ARRAY_TYPE:
+       case POINTER_TYPE:
+       case REFERENCE_TYPE:
+               return 1;
+       default:
+               return 0;
+       }
+}
+
+static tree target_type(tree type) {
+       while (is_ref_type(type))
+               type = TREE_TYPE(type);
+       return type;
+}
+
+static size_t value_size(tree n) {
+       return n ? tree_to_uhwi(n) : 0;
+}
+
+static const char *value_name(tree type) {
+       tree test = TYPE_IDENTIFIER(type);
+       const char *value = test ? IDENTIFIER_POINTER(test) : NULL;
+
+       // __va_list_tag is the final type beneath __builtin_va_list.
+       // it behaves different from other types - it has a TYPE_MAIN_VARIANT, but the main TYPE_NAME seems to give
+       // an unexpected tree, and therefore ORIG_TYPE_NAME returns a garbage value.
+       // I think this patch is good enough.
+       if (value && 0 == strcmp("__va_list_tag", value))
+               return "__va_list_tag";
+
+       test = TYPE_MAIN_VARIANT(type);
+       test = test ? TYPE_NAME(test) : test;
+       test = test && TREE_CODE(test) == TYPE_DECL ? DECL_NAME(test) : test;
+
+       return test ? IDENTIFIER_POINTER(test) : value;
+}
+
+/*
+  Find a non-ref type in the type chain, i.e. skip pointers/arrays.
+*/
+static tree simple_type(tree t) {
+       while (is_ref_type(t))
+               t = TREE_TYPE(t);
+       return t;
+}
+
+/*
+  Create a 'panama' signature for a single type.
+*/
+static void export_desc(tree field, tree field_type, struct buffer *b) {
+       const size_t data_size = value_size(TYPE_SIZE(field_type));
+
+       switch (TREE_CODE(field_type)) {
+       case VOID_TYPE:
+               buffer_add(b, "v");
+               break;
+       case VECTOR_TYPE:
+       case ARRAY_TYPE: {
+               const size_t elem_size = tree_to_uhwi(TYPE_SIZE_UNIT(TREE_TYPE(field_type)));
+               size_t num_elem;
+
+               if (elem_size == 0 || NULL == TYPE_SIZE_UNIT(field_type)) {
+                       // it is a flexible array or empty types
+                       num_elem = 0;
+               } else {
+                       // it might be 0 / elem_size, in which case we also end up with num_elem = 0.
+                       num_elem = tree_to_uhwi(TYPE_SIZE_UNIT(field_type)) / elem_size;
+               }
+
+               buffer_room(b, 16);
+               b->pos += sprintf(b->data + b->pos, "[%zu", num_elem);
+               export_desc(field, TREE_TYPE(field_type), b);
+               buffer_add(b, "]");
+               break;
+       }
+       case POINTER_TYPE:
+       case REFERENCE_TYPE:
+               buffer_room(b, 16);
+               b->pos += sprintf(b->data + b->pos, "u%zu:", data_size);
+               export_desc(field, TREE_TYPE(field_type), b);
+               break;
+       case FUNCTION_TYPE: {
+               // TODO: handle void f() type -> null TYPE_ARG_TYPES()
+               tree return_type = TREE_TYPE(field_type);
+
+               buffer_add(b, "(");
+               for (tree param = TYPE_ARG_TYPES(field_type); param != NULL; param = TREE_CHAIN(param)) {
+                       tree param_type = TREE_VALUE(param);
+
+                       if (TREE_CODE(param_type) == VOID_TYPE)
+                               break;
+
+                       export_desc(param, param_type, b);
+               }
+               buffer_add(b, ")");
+               export_desc(field, return_type, b);
+               break;
+       }
+       case ENUMERAL_TYPE:
+#if defined(TYPED_ENUMS)
+               buffer_add(b, "${");
+               buffer_add(b, TYPE_IDENTIFIER(field_type) ? value_name(field_type) : "enum");
+               buffer_add(b, "}");
+#else
+               buffer_room(b, 16);
+               b->pos += sprintf(b->data + b->pos, "%c%zu",
+                       TYPE_UNSIGNED(field_type) ? 'u' : 'i',
+                       data_size);
+#endif
+               break;
+       case RECORD_TYPE:
+       case UNION_TYPE:
+               if (TYPE_IDENTIFIER(field_type)) {
+                       todump_add(field_type, NULL);
+
+                       buffer_add(b, "${");
+                       buffer_add(b, value_name(field_type));
+                       buffer_add(b, "}");
+               } else {
+                       char *name = stack_path(&context_stack, "_");
+
+                       todump_add(field_type, name);
+
+                       buffer_add(b, "${");
+                       buffer_add(b, name);
+                       buffer_add(b, "}");
+
+                       free(name);
+               }
+               break;
+       case REAL_TYPE:
+               buffer_room(b, 16);
+               b->pos += sprintf(b->data + b->pos, "f%zu", data_size);
+               break;
+       case INTEGER_TYPE:
+               buffer_room(b, 16);
+               b->pos += sprintf(b->data + b->pos, "%c%zu",
+                       TYPE_UNSIGNED(field_type) ? 'u' : 'i',
+                       data_size);
+               break;
+       default:
+               debug_tree_helper(field_type, "unknown type!");
+               gcc_unreachable();
+               break;
+       }
+}
+
+/*
+  Create a 'c' description of type.
+*/
+static void export_cdesc(tree field, tree field_type, struct buffer *b) {
+       const size_t data_size = value_size(TYPE_SIZE(field_type));
+
+       switch (TREE_CODE(field_type)) {
+       case VOID_TYPE:
+               buffer_add(b, "void");
+               break;
+       case VECTOR_TYPE:
+       case ARRAY_TYPE: {
+               const size_t elem_size = tree_to_uhwi(TYPE_SIZE_UNIT(TREE_TYPE(field_type)));
+               size_t num_elem;
+
+               if (elem_size == 0 || NULL == TYPE_SIZE_UNIT(field_type)) {
+                       // it is a flexible array or empty types
+                       num_elem = 0;
+               } else {
+                       // it might be 0 / elem_size, in which case we also end up with num_elem = 0.
+                       num_elem = tree_to_uhwi(TYPE_SIZE_UNIT(field_type)) / elem_size;
+               }
+
+               export_cdesc(field, TREE_TYPE(field_type), b);
+
+               buffer_room(b, 16);
+               buffer_add(b, "[");
+               if (num_elem)
+                       b->pos += sprintf(b->data + b->pos, "%zu", num_elem);
+               buffer_add(b, "]");
+               break;
+       }
+       case POINTER_TYPE:
+       case REFERENCE_TYPE:
+               buffer_room(b, 16);
+               buffer_add(b, "*");
+               //b->pos += sprintf(b->data + b->pos, "u%zu:", data_size);
+               export_cdesc(field, TREE_TYPE(field_type), b);
+               break;
+       case FUNCTION_TYPE: {
+               // TODO: handle void f() type -> null TYPE_ARG_TYPES()
+               tree return_type = TREE_TYPE(field_type);
+               int args = 0;
+
+               export_cdesc(field, return_type, b);
+
+               buffer_add(b, "(*)");
+               buffer_add(b, "(");
+               for (tree param = TYPE_ARG_TYPES(field_type); param != NULL; param = TREE_CHAIN(param)) {
+                       tree param_type = TREE_VALUE(param);
+
+                       // TREE_TYPE might? point to a type that a previous decl has also referenced
+
+                       if (args++)
+                               buffer_add(b, ", ");
+
+                       if (TREE_CODE(param_type) == VOID_TYPE) {
+                               buffer_add(b, "void");
+                               break;
+                       }
+
+                       export_cdesc(param, param_type, b);
+               }
+               buffer_add(b, ")");
+               break;
+       }
+       case ENUMERAL_TYPE:
+#if defined(TYPED_ENUMS)
+               buffer_add(b, "${");
+               buffer_add(b, TYPE_IDENTIFIER(field_type) ? value_name(field_type) : "enum");
+               buffer_add(b, "}");
+#else
+               buffer_room(b, 16);
+               b->pos += sprintf(b->data + b->pos, "%c%zu",
+                       TYPE_UNSIGNED(field_type) ? 'u' : 'i',
+                       data_size);
+#endif
+               break;
+       case RECORD_TYPE:
+       case UNION_TYPE:
+               if (TYPE_IDENTIFIER(field_type)) {
+                       todump_add(field_type, NULL);
+
+                       buffer_add(b, "${");
+                       buffer_add(b, value_name(field_type));
+                       buffer_add(b, "}");
+               } else {
+                       char *name = stack_path(&context_stack, "_");
+
+                       todump_add(field_type, name);
+
+                       buffer_add(b, "${");
+                       buffer_add(b, name);
+                       buffer_add(b, "}");
+
+                       free(name);
+               }
+               break;
+       case REAL_TYPE:
+               buffer_room(b, 16);
+               b->pos += sprintf(b->data + b->pos, "f%zu", data_size);
+               break;
+       case INTEGER_TYPE:
+               buffer_room(b, 16);
+               b->pos += sprintf(b->data + b->pos, "%c%zu",
+                       TYPE_UNSIGNED(field_type) ? 'u' : 'i',
+                       data_size);
+               break;
+       default:
+               debug_tree_helper(field_type, "unknown type!");
+               gcc_unreachable();
+               break;
+       }
+}
+
+/*
+  Print a single parameter or field.
+*/
+static void export_param(tree field, tree field_type, size_t field_size) {
+       static tree enclosing_type = NULL;
+
+       if (debug_level > 1) {
+               fprintf(stderr, "export_param (%s, %s)\n", ZTREE_CODE(field), ZTREE_CODE(field_type));
+               //fprintf(stderr, " field name '%s'\n", TYPE_IDENTIFIER(field) ? IDENTIFIER_POINTER(TYPE_IDENTIFIER(field)) : "<unknown>");
+               fprintf(stderr, " field_type name '%s'\n", TYPE_IDENTIFIER(field_type) ? IDENTIFIER_POINTER(TYPE_IDENTIFIER(field_type)) : "<unknown>");
+               debug_tree(field_type);
+       }
+
+       switch (TREE_CODE(field_type)) {
+       case VECTOR_TYPE:
+       case ARRAY_TYPE:
+       case POINTER_TYPE:
+       case REFERENCE_TYPE: {
+               tree base_type;
+               size_t base_size;
+               struct buffer b;
+
+               buffer_init(&b, 256);
+               export_desc(field, field_type, &b);
+               generate(" deref => '%s',", b.data);
+               free(b.data);
+
+               base_type = simple_type(field_type);
+               base_size = value_size(TYPE_SIZE(base_type));
+
+               if (debug_level > 1)
+                       fprintf(stderr, "recurse %s %s\n", ZTREE_CODE(field_type), ZTREE_CODE(base_type));
+
+               enclosing_type = field_type;
+               export_param(field, base_type, base_size);
+               enclosing_type = NULL;
+               break;
+       }
+       case VOID_TYPE:
+               generate(" type => 'void',");
+               generate(" ctype => 'void',");
+               break;
+       case ENUMERAL_TYPE: {
+#if defined(TYPED_ENUMS)
+               const char *names = TYPE_IDENTIFIER(field_type) ? value_name(field_type) : "enum";
+               generate(" type => 'enum:%s',", names);
+#else
+               generate(" type => '%c%zu',", TYPE_UNSIGNED(field_type) ? 'u' : 'i', field_size);
+#endif
+               generate(" ctype => 'enum %s',", value_name(field_type));
+               break;
+       }
+       case FUNCTION_TYPE: {
+               struct buffer b;
+               tree root_type = TREE_TYPE(field);
+
+               if (root_type == NULL || !TYPE_IDENTIFIER(root_type))
+                       root_type = enclosing_type;
+
+               // If this is a typedef we might have a name for the type, otherwise it's a signature based name
+               if (root_type && TYPE_IDENTIFIER(root_type)) {
+                       generate(" type => 'call:%s', ", value_name(root_type));
+               } else {
+                       // note it is added to todump in reverse
+                       buffer_init(&b, 256);
+                       export_desc(field, field_type, &b);
+                       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);
+
+                               if (root_type) {
+                                       fprintf(stderr, "type is '%s\n", ZTREE_CODE(root_type));
+                                       fprintf(stderr, "type name is '%s'\n", TYPE_IDENTIFIER(root_type) ? IDENTIFIER_POINTER(TYPE_IDENTIFIER(root_type)) : "<anon>");
+                                       fprintf(stderr, "target is '%s'\n", ZTREE_CODE(target_type(root_type)));
+                               }
+                       }
+               }
+
+               //buffer_init(&b, 256);
+               //export_cdesc(field, field_type, &b);
+               //free(b.data);
+
+               break;
+       }
+       case REAL_TYPE:
+               generate(" ctype => '%s',", value_name(field_type));
+               generate(" type => 'f%zu',", field_size);
+               break;
+       case INTEGER_TYPE:
+               if (TREE_CODE(field) == FIELD_DECL && DECL_BIT_FIELD(field)) {
+                       generate(" ctype => 'bitfield',");
+                       generate(" type => '%c%zu',", TYPE_UNSIGNED(field_type) ? 'u' : 'i', value_size(DECL_SIZE(field)));
+               } else {
+                       generate(" ctype => '%s',", value_name(field_type));
+                       generate(" type => '%c%zu',", TYPE_UNSIGNED(field_type) ? 'u' : 'i', field_size);
+               }
+               break;
+       case RECORD_TYPE:
+       case UNION_TYPE: {
+               const char *us = TREE_CODE(field_type) == RECORD_TYPE ? "struct" : "union";
+
+               if (TYPE_IDENTIFIER(field_type)) {
+                       generate(" type => '%s:%s',", us, value_name(field_type));
+               } else {
+                       char *name = stack_path(&context_stack, "_");
+
+                       todump_add(field_type, name);
+                       generate(" type => '%s:%s',", us, name);
+                       free(name);
+               }
+               break;
+       }
+       default:
+               fprintf(stderr, "unknown param type: %s\n", ZTREE_CODE(field_type));
+               gcc_unreachable();
+       }
+}
+
+/*
+  Export a chain of parameters
+*/
+static void export_params(tree func) {
+       int id = 0;
+       char nameb[32];
+       struct list args = { 0 };
+       struct node *name;
+       struct node *fwd = hash_lookup_bytype(&forward_types, func);
+
+       if (fwd) {
+               // use the forward reference to find the names
+               if (debug_level > 0)
+                       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
+               // but they are in reverse order
+               int id = 0;
+               for (tree param = TYPE_ARG_TYPES(func); param; param = TREE_CHAIN(param)) {
+                       tree param_type = TREE_VALUE(param);
+
+                       if (!TREE_CHAIN(param) && TREE_CODE(param_type) == VOID_TYPE) {
+                               if (id == 0) {
+                                       struct node *decl = stack_pull(&parameters);
+                                       if (debug_level > 0)
+                                               fprintf(stderr, "(pull parameter dummy '%s')\n", decl->name);
+                               }
+                               break;
+                       }
+
+                       struct node *decl = stack_pull(&parameters);
+                       if (decl) {
+                               if (debug_level > 0)
+                                       fprintf(stderr, "(pull parameter args '%s')\n", decl->name);
+                               stack_push(&args, decl);
+                       } else
+                               fprintf(stderr, "ERROR: parameter %d missing parameter declaration\n", id);
+                       id++;
+               }
+               name = args.head;
+       }
+
+       for (tree param = TYPE_ARG_TYPES(func); param; param = TREE_CHAIN(param)) {
+               tree param_type = TREE_VALUE(param);
+               const size_t data_size = value_size(TYPE_SIZE(param_type));
+               const char *names = NULL;
+
+               // non-varags functions end in VOID_TYPE
+               if (!TREE_CHAIN(param) && TREE_CODE(param_type) == VOID_TYPE)
+                       break;
+
+               generate("\t\t{");
+
+               // size: do we need it?
+               generate(" size => %zu,", data_size);
+
+               if (name) {
+                       // this should be a parm_decl with an identifier of the name
+                       names = name->name;
+
+                       // can one check it's a matching type?
+                       name = name->next;
+               }
+
+               if (!names || !names[0]) {
+                       sprintf(nameb, "arg$%d", id);
+                       names = nameb;
+               }
+
+               generate(" name => '%s',", names);
+               stack_push(&context_stack, node_alloc(param, names));
+
+               // value: details
+               export_param(param, param_type, data_size);
+
+               free(stack_pull(&context_stack));
+
+               generate("},\n");
+               id++;
+       }
+
+       list_clear(&args);
+}
+
+/*
+  Export a chain of fields.
+*/
+static void export_fields(tree first_field, size_t base_offset, int indent) {
+       for (tree field = first_field; field; field = TREE_CHAIN(field)) {
+               gcc_assert(TREE_CODE(field) == FIELD_DECL);
+
+               tree field_type = TREE_TYPE(field);
+               const size_t field_size = value_size(DECL_SIZE(field));
+               size_t offset = base_offset + tree_to_uhwi(DECL_FIELD_OFFSET(field)) * 8 + tree_to_uhwi(DECL_FIELD_BIT_OFFSET(field));
+
+               // name: if none, then inline
+               if (!DECL_NAME(field)) {
+                       if (is_struct_or_union(field_type))
+                               export_fields(TYPE_FIELDS(field_type), offset, indent);
+               } else {
+                       const char *names = IDENTIFIER_POINTER(DECL_NAME(field));
+
+                       if (debug_level > 1)
+                               fprintf(stderr, "   field: %s\n", names);
+                       print_spaces(indent+1);
+                       generate("{ name => '%s', size => %zu, offset => %zu,", names, field_size, offset);
+                       stack_push(&context_stack, node_alloc(field, names));
+
+                       // value: details
+                       export_param(field, field_type, field_size);
+
+                       free(stack_pull(&context_stack));
+
+                       generate("},\n");
+               }
+       }
+}
+
+// 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.
+*/
+static void export_type(tree type, const char *names) {
+       tree name;
+       tree deftype;
+       tree target;
+
+       switch (TREE_CODE(type)) {
+       case TYPE_DECL: {
+               if (!names) {
+                       name = DECL_NAME(type);
+                       if (!name)
+                               return;
+                       names = IDENTIFIER_POINTER(name);
+               }
+
+               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: {
+                       // TODO: should be able to call export_type(target, names) here, except code is slightly different
+
+                       if (hash_lookup(&dumped, names)) {
+                               if (debug_level > 1)
+                                       fprintf(stderr, " - type already output, skipping");
+                               return;
+                       }
+                       hash_put(&dumped, node_alloc(type, names));
+
+                       // function pointer typdef
+                       // I don't know if i even want this
+                       generate("'call:%s' => { name => '%s', type => 'call',", names, names);
+
+                       // the deftype is always a pointer for a function_type
+
+                       struct buffer b;
+
+                       buffer_init(&b, 256);
+                       export_desc(type, deftype, &b);
+                       generate(" deref => '%s',", b.data);
+                       free(b.data);
+
+                       generate(" ctype => '%s',", print_generic_expr_to_str(target));
+
+                       // TODO: cleanup
+                       {
+                               tree result_type = TREE_TYPE(target);
+                               const size_t data_size = value_size(TYPE_SIZE(result_type));
+
+                               generate("\n\tresult => {");
+                               export_param(target, result_type, data_size);
+                               generate(" },");
+                       }
+
+                       generate("\n\targuments => [\n");
+                       export_params(target);
+                       generate("]},\n");
+                       break;
+               }
+               case ENUMERAL_TYPE: {
+                       if (hash_lookup(&dumped, names)) {
+                               if (debug_level > 1)
+                                       fprintf(stderr, " - type already output, skipping");
+                               return;
+                       }
+                       hash_put(&dumped, node_alloc(type, names));
+
+                       // TODO: should be able to call export_type(target, names) here
+                       // TODO: typedef of anonymous enumerations may be repeated
+                       // TODO: this could be detected in the frontend - e.g. don't include any
+                       // TODO: anon enum values if any are already there
+                       // TODO: or maybe duplicates could be detected here
+                       size_t size = tree_to_uhwi(TYPE_SIZE(target));
+
+                       if (size > 64) {
+                               fprintf(stderr, "Warning: enum '%s' requires too many bits (%zu)\n", names, size);
+                               return;
+                       }
+
+                       generate("'enum:%s' => { name => '%s', type => 'enum', size => %zu, value_type => '%c%zu', values => [\n",
+                               names, names, size, TYPE_UNSIGNED(target) ? 'u' : 'i', size);
+
+                       for (tree v = TYPE_VALUES(target); v != NULL; v = TREE_CHAIN (v)) {
+                               generate("\t{ name => '%s', value => '%ld' },\n",
+                                       IDENTIFIER_POINTER(TREE_PURPOSE(v)),
+                                       tree_to_shwi(TREE_VALUE(v)));
+                       }
+                       generate("]},\n");
+                       break;
+               }
+               case RECORD_TYPE:
+               case UNION_TYPE:
+                       if (TYPE_FIELDS(target)) {
+                               // 'typedef struct { } foo;'
+                               export_type(target, names);
+                       } else {
+                               // forward declaration or opaque types
+                               if (debug_level)
+                                       fprintf(stderr, "forward ignored %s: %s\n", ZTREE_CODE(target), names);
+                       }
+                       break;
+               case VOID_TYPE:
+               case INTEGER_TYPE:
+               case REAL_TYPE:
+                       if (debug_level)
+                               fprintf(stderr, "ignored %s: %s\n", ZTREE_CODE(target), names);
+                       break;
+               default:
+                       fprintf(stderr, "unknown type def: %s\n", ZTREE_CODE(target));
+                       gcc_unreachable();
+               }
+
+               break;
+       }
+       case FUNCTION_DECL: {
+               if (!names) {
+                       name = DECL_NAME(type);
+                       if (!name)
+                               return;
+                       names = IDENTIFIER_POINTER(name);
+               }
+               if (hash_lookup(&dumped, names))
+                       return;
+               hash_put(&dumped, node_alloc(type, names));
+
+               if (debug_level > 1)
+                       fprintf(stderr, "export: %-16s %s\n", ZTREE_CODE(type), names);
+
+               generate("'func:%s' => { name => '%s', type => 'func',", names, names);
+
+               // FUNCTION_DECL -> FUNCTION_TYPE -> RESULT_TYPE, get FUNCTION_TYPE
+               type = TREE_TYPE(type);
+
+               generate(" ctype => '%s',", print_generic_expr_to_str(type));
+
+               // TODO: cleanup
+               debug_tree_helper(type, "function 1");
+               {
+                       tree result_type = TREE_TYPE(type);
+                       const size_t data_size = value_size(TYPE_SIZE(result_type));
+
+                       generate("\n\tresult => {");
+                       export_param(type, result_type, data_size);
+                       generate(" },");
+               }
+
+               generate("\n\targuments => [\n");
+               //export_decl_params(DECL_ARGUMENTS(type), 0);
+               export_params(type);
+               generate("]},\n");
+               break;
+       }
+       case FUNCTION_TYPE: {
+               int output_save = output_enabled;
+
+               // This is called for un-typedef'd function pointers.
+               // WHY IS THIS DIFFERENT TO ABOVE?
+               if (!names) {
+                       name = TYPE_IDENTIFIER(type);
+                       if (!name)
+                               return;
+                       names = IDENTIFIER_POINTER(name);
+               }
+               if (hash_lookup(&dumped, names)) {
+                       if (debug_level > 0)
+                               fprintf(stderr, " - type already output, supressing generation\n");
+                       output_enabled = 0;
+               }
+               hash_put(&dumped, node_alloc(type, names));
+
+               if (debug_level > 1)
+                       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));
+
+               debug_tree_helper(type, "function type");
+
+               // TODO: cleanup
+               // FUNCTION_TYPE -> RESULT_TYPE
+               {
+                       tree result = TREE_TYPE(type);
+                       //tree result_type = TREE_TYPE(result);
+                       //printf(" result type type %s\n", ZTREE_CODE(result_type));
+                       const size_t data_size = value_size(TYPE_SIZE(result));
+
+                       if (debug_level > 2)
+                               fprintf(stderr, " result size %zu\n", data_size);
+                       generate("\n\tresult => {");
+                       export_param(type, result, data_size);
+                       generate(" },");
+               }
+
+               stack_push(&context_stack, node_alloc(type, names));
+               generate("\n\targuments => [\n");
+               export_params(type);
+               free(stack_pull(&context_stack));
+               generate("]},\n");
+
+               output_enabled = output_save;
+               break;
+       }
+       case RECORD_TYPE: // struct
+       case UNION_TYPE: {
+               const char *su = TREE_CODE(type) == RECORD_TYPE ? "struct" : "union";
+
+               // ignore empty types
+               if (!TYPE_FIELDS(type))
+                       return;
+
+               if (!names) {
+                       name = TYPE_IDENTIFIER(type);
+                       if (!name)
+                               return;
+                       names = IDENTIFIER_POINTER(name);
+               }
+               if (hash_lookup(&dumped, names)) {
+                       if (debug_level > 1)
+                               fprintf(stderr, "already exported: %-16s %s\n", ZTREE_CODE(type), names);
+                       return;
+               }
+               hash_put(&dumped, node_alloc(type, names));
+
+               if (debug_level > 1)
+                       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)));
+
+               stack_push(&context_stack, node_alloc(type, names));
+               export_fields(TYPE_FIELDS(type), 0, 0);
+               free(stack_pull(&context_stack));
+
+               generate("]},\n");
+               break;
+       }
+       case ENUMERAL_TYPE: {
+               // FIXME: see ENUMERAL_TYPE above regarding duplicate anon enums
+
+               // ignore empty enums
+               if (!TYPE_VALUES(type))
+                       return;
+
+               // We can only assume a non-named enum type isn't repeatedly sent here
+               if (!names) {
+                       name = TYPE_IDENTIFIER(type);
+                       if (name)
+                               names = IDENTIFIER_POINTER(name);
+               }
+               if (names) {
+                       if (hash_lookup(&dumped, names))
+                               return;
+                       hash_put(&dumped, node_alloc(type, names));
+               }
+
+               size_t size = tree_to_uhwi(TYPE_SIZE(type));
+
+               if (size > 64) {
+                       fprintf(stderr, "Warning: enum '%s' requires too many bits (%zu)\n", names, size);
+                       return;
+               }
+
+               // FIXME: choose a better anon name
+               char nameb[64];
+               static int namei;
+
+               if (!names) {
+                       sprintf(nameb, "enum$%d", namei++);
+                       names = nameb;
+               }
+
+               if (debug_level > 1)
+                       fprintf(stderr, "export: %s %s\n", names, ZTREE_CODE(type));
+
+               generate("'enum:%s' => { name => '%s', type => 'enum', size => %zu, value_type => '%c%zu', values => [\n",
+                       names, names, size, TYPE_UNSIGNED(type) ? 'u' : 'i', size);
+
+               for (tree v = TYPE_VALUES(type); v != NULL; v = TREE_CHAIN (v)) {
+                       generate("\t{ name => '%s', value => '%ld' },\n",
+                               IDENTIFIER_POINTER(TREE_PURPOSE(v)),
+                               tree_to_shwi(TREE_VALUE(v)));
+               }
+               generate("]},\n");
+               break;
+       }
+       case FIELD_DECL: {
+               if (!names) {
+                       name = DECL_NAME(type);
+                       if (name)
+                               names = IDENTIFIER_POINTER(name);
+               }
+
+               deftype = TREE_TYPE(type);
+               target = target_type(deftype);
+
+               if (debug_level > 1) {
+                       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
+               if (TREE_CODE(target) == FUNCTION_TYPE && !TYPE_IDENTIFIER(deftype)) {
+                       // We need to save the list of parameters for later,
+                       // it's keyed on target
+                       struct node *fwd = node_alloc(target, NULL);
+
+                       if (debug_level > 0) {
+                               struct buffer b;
+
+                               buffer_init(&b, 256);
+                               export_desc(deftype, target, &b);
+
+                               fprintf(stderr, "save forward reference field function type %p '%s' '%s'\n", target, names, b.data);
+                               // or should be todump?
+                               free(b.data);
+                       }
+
+                       for (tree param = TYPE_ARG_TYPES(target); param != NULL; param = TREE_CHAIN(param)) {
+                               tree param_type = TREE_VALUE(param);
+
+                               if (TREE_CODE(param_type) == VOID_TYPE)
+                                       break;
+
+                               struct node *decl = stack_pull(&parameters);
+                               if (decl) {
+                                       if (debug_level > 0)
+                                               fprintf(stderr, "(pull parameter '%s')\n", decl->name);
+                                       stack_push(&fwd->list, decl);
+                               } else
+                                       fprintf(stderr, "WARNING: stack is missing parameter name function %s\n", names);
+                       }
+
+                       hash_put_bytype(&forward_types, fwd);
+
+                       // make sure it's dumped somewhere
+                       // what if it's a typedef?
+
+                       // ... we don't know if'ts a typedef though right?
+                       // note it is added to todump in reverse
+                       if (0) {
+                               struct buffer b;
+
+                               buffer_init(&b, 256);
+                               export_desc(deftype, target, &b);
+                               todump_addhead(target, b.data);
+
+                               if (debug_level > 0)
+                                       fprintf(stderr, "save for later param type %p '%s'\n", target, b.data);
+
+                               free(b.data);
+                       }
+               }
+
+               break;
+       }
+       case PARM_DECL: {
+               // capture PARM_DECLs so they can be used at next function declaration
+               if (!names) {
+                       name = DECL_NAME(type);
+                       if (name)
+                               names = IDENTIFIER_POINTER(name);
+               }
+               // if this is a function pointer typedef, need to suck out the arguments at this point
+               deftype = TREE_TYPE(type);
+               target = target_type(deftype);
+
+               if (debug_level > 1) {
+                       fprintf(stderr, "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));
+               }
+
+               if (TREE_CODE(target) == FUNCTION_TYPE && !TYPE_IDENTIFIER(deftype)) {
+                       // We need to save the list of parameters for later,
+                       // it's keyed on target
+                       struct node *fwd = node_alloc(target, NULL);
+
+                       if (debug_level > 0) {
+                               struct buffer b;
+
+                               buffer_init(&b, 256);
+                               export_desc(deftype, target, &b);
+
+                               fprintf(stderr, "save forward reference function type %p '%s' '%s'\n", target, names, b.data);
+                               free(b.data);
+                       }
+
+                       for (tree param = TYPE_ARG_TYPES(target); param != NULL; param = TREE_CHAIN(param)) {
+                               tree param_type = TREE_VALUE(param);
+
+                               if (TREE_CODE(param_type) == VOID_TYPE)
+                                       break;
+
+                               struct node *decl = stack_pull(&parameters);
+                               if (decl) {
+                                       if (debug_level > 0)
+                                               fprintf(stderr, "(pull parameter '%s')\n", decl->name);
+                                       stack_push(&fwd->list, decl);
+                               } else
+                                       fprintf(stderr, "WARNING: stack is missing parameter name function %s\n", names);
+                       }
+
+                       hash_put_bytype(&forward_types, fwd);
+
+                       // make sure it's dumped somewhere
+                       // what if it's a typedef?
+
+                       // ... we don't know if'ts a typedef though right?
+                       // note it is added to todump in reverse
+                       if (1) {
+                               struct buffer b;
+
+                               buffer_init(&b, 256);
+                               export_desc(deftype, target, &b);
+                               todump_addhead(target, b.data);
+
+                               if (debug_level > 0)
+                                       fprintf(stderr, "save for later param type %p '%s'\n", target, b.data);
+
+                               free(b.data);
+                       }
+
+               }
+
+               if (debug_level > 0)
+                       fprintf(stderr, "(push parameter '%s' %s)\n", names, source_filename(type));
+               stack_push(&parameters, node_alloc(type, names));
+
+               break; }
+       case VAR_DECL:
+               // global external variables, might want these
+               // well, if there's a way to resolve them
+               break;
+       default:
+               fprintf(stderr, "unknown export: %s\n", ZTREE_CODE(type));
+               gcc_unreachable();
+       }
+}
+
+static void plugin_finish_type(void *event_data, void *user_data) {
+       tree type = (tree)event_data;
+
+       if (debug_level > 0)
+               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);
+}
+
+static void plugin_finish_decl(void *event_data, void *user_data) {
+       tree type = (tree)event_data;
+
+       if (debug_level > 0)
+               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);
+}
+
+static void plugin_finish(void *event_data, void *user_data) {
+       if (debug_level > 0)
+               fprintf(stderr, "\n\n----------------------------------------\nplugin finish\n");
+       generate("# forward references:\n");
+       for (struct node *n = todump.head; n; n=n->next) {
+               if (COMPLETE_TYPE_P(n->type)) {
+                       if (n->name[0]) {
+                               export_type(n->type, n->name);
+                       } else {
+                               export_type(n->type, value_name(n->type));
+                       }
+               }
+       }
+
+       generate("# dumped structs:\n");
+       for (struct node *n = dumped.list.head; n; n=n->next)
+               generate("# %s\n", n->name);
+
+       generate("}\n");
+
+       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) {
+       const char *output = NULL;
+
+       for (int i = 0; i < plugin_info->argc; ++i) {
+               if (0 == strcmp(plugin_info->argv[i].key, "output")) {
+                       output = plugin_info->argv[i].value;
+               } else if (0 == strcmp(plugin_info->argv[i].key, "debug")) {
+                       debug_level = atoi(plugin_info->argv[i].value);
+               }
+       }
+
+       if (NULL == output) {
+               fprintf(stderr, "export plugin: missing parameter: -fplugin-arg-export-output=<output>\n");
+               exit(EXIT_FAILURE);
+       }
+
+       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);
+       }
+
+       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);
+
+       return 0;
+}
diff --git a/src/notzed.nativez/native/list.h b/src/notzed.nativez/native/list.h
new file mode 100644 (file)
index 0000000..4a96688
--- /dev/null
@@ -0,0 +1,200 @@
+
+/*
+  Copyright (C) 2010,2019,2020 Michael Zucchi
+
+   This program is free software: you can redistribute it and/or
+   modify it under the terms of the GNU General Public License
+   as published by the Free Software Foundation, either version 3 of
+   the License, or (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public
+   License along with this program. If not, see
+   <http://www.gnu.org/licenses/>.
+*/
+
+/* **********************************************************************
+ * a simple ordered hash/list/stack
+ */
+
+struct node;
+
+struct list {
+       struct node *head;
+       struct node *tail;
+};
+
+struct node {
+       // list/stack next
+       struct node *next;
+       // hash chain link/stack prev
+       struct node *link;
+
+       // metadata
+       struct list list;
+
+       // payload
+       tree type;
+
+       char name[];
+};
+
+struct hash {
+       struct node *table[64];
+       struct list list;
+};
+
+static struct node *node_alloc(tree type, const char *name) {
+       struct node *n = (struct node *)xmalloc(sizeof(struct node) + (name ? strlen(name) + 1 : 1));
+
+       n->next = NULL;
+       n->link = NULL;
+       n->type = type;
+       n->list.head = NULL;
+       n->list.tail = NULL;
+
+       if (name)
+               strcpy(n->name, name);
+       else
+               n->name[0] = 0;
+
+       return n;
+}
+
+static void stack_push(struct list *stack, struct node *node) {
+       if (stack->head) {
+               stack->head->link = node;
+               node->next = stack->head;
+               stack->head = node;
+       } else {
+               node->next = NULL;
+               stack->head = node;
+               stack->tail = node;
+       }
+}
+
+static struct node *stack_pull(struct list *stack) {
+       struct node *n = stack->head;
+
+       if (n) {
+               stack->head = n->next;
+               if (stack->head)
+                       stack->head->link = NULL;
+               else
+                       stack->tail = NULL;
+       }
+
+       return n;
+}
+
+static unsigned int ez_hash_string(const char *s) {
+        unsigned int hash = 5381;
+
+        while (*s)
+                //hash = (*s++) + (hash << 5) + hash;
+                hash = hash * 33 + (*s++);
+
+        return hash;
+}
+
+static void list_add(struct list *list, struct node *node) {
+       node->next = NULL;
+       if (list->head) {
+               list->tail->next = node;
+               list->tail = node;
+       } else {
+               list->head = node;
+               list->tail = node;
+       }
+}
+
+static void list_addhead(struct list *list, struct node *node) {
+       node->next = list->head;
+       list->head = node;
+       if (!list->tail)
+               list->tail = node;
+}
+
+static struct node *list_remhead(struct list *list) {
+       struct node *node = list->head;
+
+       if (node && (list->head = node->next) == NULL)
+               list->tail = NULL;
+
+       return node;
+}
+
+static void hash_put(struct hash *hash, struct node *node) {
+       int code = ez_hash_string(node->name) & 63;
+
+       list_add(&hash->list, node);
+
+       node->link = hash->table[code];
+       hash->table[code] = node;
+}
+
+struct node *hash_lookup(struct hash *hash, const char *name) {
+       int code = ez_hash_string(name) & 63;
+
+       for (struct node *n = hash->table[code]; n; n=n->link) {
+               if (strcmp(n->name, name) == 0)
+                       return n;
+       }
+
+       return NULL;
+}
+
+static void hash_put_bytype(struct hash *hash, struct node *node) {
+       int code = (int)((uintptr_t)node->type >> 5) & 63;
+
+       list_add(&hash->list, node);
+
+       node->link = hash->table[code];
+       hash->table[code] = node;
+}
+
+struct node *hash_lookup_bytype(struct hash *hash, tree type) {
+       int code = (int)((uintptr_t)type >> 5) & 63;
+
+       for (struct node *n = hash->table[code]; n; n=n->link) {
+               if (n->type == type)
+                       return n;
+       }
+
+       return NULL;
+}
+
+/* simple growable c buffer */
+struct buffer {
+       size_t size;
+       size_t pos;
+       char *data;
+};
+
+static void buffer_init(struct buffer *b, size_t size) {
+       b->size = size;
+       b->pos = 0;
+       b->data = (char *)xmalloc(size);
+}
+
+static void buffer_room(struct buffer *b, size_t len) {
+       if (b->pos + len >= b->size) {
+               do {
+                       b->size *= 2;
+               } while (b->pos + len >= b->size);
+
+               b->data = (char *)xrealloc(b->data, b->size);
+       }
+}
+
+static void buffer_add(struct buffer *b, const char *v) {
+       size_t len = strlen(v);
+
+       buffer_room(b, len);
+       strcpy(b->data + b->pos, v);
+       b->pos += len;
+}
diff --git a/src/notzed.nativez/native/native.make b/src/notzed.nativez/native/native.make
new file mode 100644 (file)
index 0000000..48d90af
--- /dev/null
@@ -0,0 +1,16 @@
+
+notzed.nativez_NATIVE_LIBRARIES = export
+
+notzed.nativez_COMMANDS=export-api export-defines generate-api
+notzed.nativez_LIBRARIES=api.pm        code.api code.pm config.pm method.pm tokenise.pm types.api
+
+# JMOD target
+$(notzed.nativez_libdir)/%: src/notzed.nativez/lib/%
+       install -D $< $@
+$(notzed.nativez_bindir)/%: src/notzed.nativez/bin/%
+       install -D $< $@
+
+export_CXXSOURCES = export.cc
+export_SOURCES = tree-codes.c
+export_CXXFLAGS = -Wno-switch -g
+export_CPPFLAGS=-I. -I$(GCCPLUGINDIR)/include
diff --git a/src/notzed.nativez/native/tree-codes.c b/src/notzed.nativez/native/tree-codes.c
new file mode 100644 (file)
index 0000000..ca6f237
--- /dev/null
@@ -0,0 +1,10 @@
+/*
+  This builds a tree-code to name table, they are indexed
+  by position.
+  TODO: Not sure if there is some debug function to get them directly.
+ */
+#define END_OF_BASE_TREE_CODES "last_and_unused_tree_code"
+#define DEFTREECODE(a, b, c, d) b,
+const char *tree_codes[] = {
+#include <all-tree.def>
+};