The Eta FFI Generator will consist of support from three components:
- The Eta compiler
- The Etlas build tool
- The eta-ffi tool
When compiling multiple modules at once, the Eta compiler will collect all the JWT (Java Wrapper Type) declarations that export their data constructors and output them in a single .ffimap
file which is a CSV file without a header.
Suppose we were compiling a single module:
module Java.Imports
(Set(..)
,MDigest(..)
,JCharacter
)
where
data Set a = Set (@java.util.Set a)
data MDigest = MD @java.security.MessageDigest
data JCharacter = JD @java.lang.Character
data JDouble = JD @java.lang.Double
should output a .ffimap
file with at least the following contents:
Qualified Class,Eta Type,Eta Module
java.util.Set,Set,Java.Imports
java.security.MessageDigest,MDigest,Java.Imports
Set
is present in the map file since its data constructor was exported.MessageDigest
is in the map file since its data constructor was exported.JCharacter
is not in the map file since its data constructor wasn't exported.JDouble
is also not in the map file since it wasn't exported at all.
- Generate
.ffimap
files. - Ensure JWTs w/o exported data constructors are excluded from the map file.
- Ensure JWTs that are not exported are excluded from the map file.
When running an etlas build
, etlas
should look for a single [package-name].ffispec
file at the project root as a signal that it should run the eta-ffi
tool to generate binding modules in dist/build/autogen
and pass in the following information:
[package-name].ffispec
file- All the Maven dependencies listed under
maven-depends:
- All the jar files and class files listed under
java-sources:
- All the
*.ffimap
files from the packages in transitive closure of the dependencies listed inbuild-depends:
- Module prefix for the modules.
Further, etlas
should detect changes to the [package-name].ffispec
file and re-run eta-ffi
on the updated [package-name].ffispec
file to generate the new output.
The user should not modify the files that are generated by eta-ffi
.
- Detect
.ffispec
file presence and triggereta-ffi
- Send auto-generated files to
eta
CLI arguments during build
This tool will take the inputs mentioned above and generate Eta source files.
eta-ffi [FLAGS] [SPEC-FILE]
FLAGS
-----
-i --include-mapping FILEPATH
FILEPATH should be a valid filepath in the CSV format described above. The basename of the
FILEPATH will be used to determine the package name and version that it originates from.
-o --output-dir FILEPATH
FILEPATH should be the location of a directory in which to generate the output.
-cp --classpath
Same as `javac`. The list of all the jars which you want to import from.
SPEC-FILE
---------
SPEC-FILE should be a valid filepath in the YAML format with specific structure described below.
The .ffispec
format is a YAML file that looks something like below:
targets:
- filter:
scope: org.bouncycastle.crypto.digests.
filter:
or:
- prefix: Blake2b
- prefix: Keccak
- prefix: MD
- prefix: RIPEMD160
- prefix: SHA
- prefix: Skein
- prefix: Tiger
- prefix: Whirlpool
action:
- filter:
or:
- prefix: Blake2b
- prefix: Keccak
- prefix: SHAKE
- prefix: Skein
constructors: (int)
- filter: org.bouncycastle.crypto.digests.Digest
At the top-level there are two keys: mappings
and targets
.
This should be a list of mappings. Each mapping must have:
class
: The fully qualified class name which is being mapped.module
: The Eta module which contains a JWT of the class.type
: The JWT in the module which corresponds to the class.
These mappings are used to resolve ambiguity in the cases where multiple packages export the same class.
Example:
mappings:
- class: java.lang.String
module: Java.String
type: JString
This key is required since it specifies what classes you want to import, which methods you want to import, and how you want to import them.
This should consist of a list of filter/actions pairs.
A filter
provides a way to narrow down the classes you want to import and an action
specifies
how you should import it.
Filters can take the following forms:
-
A single regular expression
Example:
filter: he.*llo
This will match against
hello
andhelllo
and so on. -
A list of filters, all of which must hold (AND)
Example:
filter: - he.*llo - hi.*
This will match against
hehillo
andhehehillo
and so on. -
A conjunction of filters (AND)
Example:
filter: and: - he.*llo - hi.*
This will match against
hehillo
andhehehillo
and so on. -
A disjunction of filters (OR)
Example:
filter: or: - he.*llo - hi.*
This will match against
hello
andhi
and so on. -
Negation of a filter
Example:
filter: not: he.*llo
This will not match against
hello
andhelllo
and so on. -
Prefix filter
Example:
filter: prefix: hello
This will match against
hello
andhello1
and so on. -
Suffix filter
Example:
filter: suffix: hello
This will match against
hello
and1hello
and so on. -
Scoped filters
Example:
filter: scope: hello. filter: or: - hi - hello
This will match against
hello.hello
andhello.hi
and so on. -
abstract
Type:boolean
Description: Iftrue
, matches only abstract methods. Example:abstract: true
-
length: [integer]
Description:
[integer]
is the length of the arguments list that should be matched.Example:
length: 4
-
signature: ([args])
Description:
[args]
is a comma-separated list of types, where simple class names can be used to refer to Java classes.Example:
signature: (int, Object)
-
static
Type:boolean
Description: Iftrue
, matches only static methods. Example:static: true
-
type
Type:string
Description: Should describe the type of the field. Can be a regular expression. Example:abstract: true
The operations above can be composed and nested arbitrarily.
All regular expressions must follow the format of java.util.Pattern.
Actions operate on classes, interfaces, and enums and specify what and how to import methods.
By default, the following operations will be performed if nothing else is specified:
- Concrete Class: A JWT will be generated with an
Inherits
type family instance and only public empty constructors will be imported for all the classes that match the corresponding - Abstract Class/Interface: JWT,
Inherits
instance, and imports of all the public and protected abstract methods with subtype-polymorphism in the tag type of theJava
monad. - Enums: JWT,
Inherits
instance, and import all the public static fields.
To override these defaults, you can specify a list of maps as the value for the actions
key that can have the following keys:
constructors
: Specify the exact constructors that need to be imported.methods
: Specify the exact methods that need to be imported.fields
: Specify the exact fields that need to be imported.module-prefix
: Specifies what prefix should be used for the bindings module.filter
: Filter the simple class names that need to be overriden.pure
: Specifies whether a class is immutable.wrapper
: Specifies whether to generate a @wrapper import. Only applies to interfaces and abstract classes.
This key is used to specify which constructors need to be imported, and how they are imported.
Keys:
-
filter
Type:filter
Default:.*
Description: This selects constructors that should be imported. The value should be same as described in the Filters section above. Note that for constructors, it doesn't match on the name, but either on the signature or the number of arguments. Hence, they can be specified directly.Examples:
filter: (int, int)
filter: 4
filter: or: - (int, int) - 4
-
as
Type:string
Default:new$
Description: This specifies what the Eta function name will be for this import. You can use$
to refer to the simple class name.Examples:
as: newHello
as: new$
as: $new
-
safety
Type:string
Values:unsafe
,safe
,interruptible
Default:unsafe
Description: Sets the import to beunsafe
,safe
, orinterruptible
, respectively. Example:safety: safe
-
pure
Type:boolean
Default:false
Description: Iftrue
, the import is done in theJava
monad, otherwise it's a pure import.Example:
pure: true
Combined Examples:
constructors:
filter: (int)
as: new$1
pure: true
constructors:
- filter: (int)
pure: false
- filter: 3
pure: true
This key is used to specify which methods need to be imported, and how they are imported.
Keys:
-
filter
Type:filter
Description: This selects methods that should be imported. The value should be same as described in the Filters section above. By default, this accepts method name regular expressions, integers to describe the number of arguments, and parenthesized list of Java types to describe method signatures.Examples:
filter: get.*
filter: or: - set.* - 5
filter: or: - signature: (int, int) - 4
-
as
Type:string
Default:$
Description: This specifies what the Eta function name will be for this import. You can use$
to refer to the Java method name.Examples:
as: hello
as: get$
as: $1
-
safety
Type:string
Values:unsafe
,safe
,interruptible
Default:unsafe
Description: Sets the import to beunsafe
,safe
, orinterruptible
, respectively. Example:safety: safe
-
pure
Type:boolean
Default:false
Description: Iftrue
, the import is done in theJava
monad, otherwise it's a pure import.Example:
pure: true
Combined Example:
methods:
- doFinal
- filter: getDigestSize
as: $1
pure: true
- filter: getAlgorithmName
pure: false
This key is used to specify which fields need to be imported, and how they are imported.
Keys:
-
filter
Type:filter
Description: This selects fields that should be imported. By default, it imports the getter. The value should be same as described in the Filters section above. By default, this accepts field name regular expressions.Examples:
filter: hello.*
filter: or: - set.* - 5
filter: or: - signature: (int, int) - 4
-
as
Type:string
Default:$
Description: This specifies what the Eta function name will be for this import. You can use$
to refer to the Java method name.Examples:
as: hello
as: get$
as: $1
-
safety
Type:string
Values:unsafe
,safe
,interruptible
Default:unsafe
Description: Sets the import to beunsafe
,safe
, orinterruptible
, respectively. Example:safety: safe
-
set
Type:boolean
Default:false
Description: Iftrue
, will do a field setter import, otherwise it won't. Example:set: true
-
pure
Type:boolean
Default:false
Description: Iftrue
, the import is done in theJava
monad, otherwise it's a pure import. This cannot betrue
whenset
is true, since that's a contradiction. Example:pure: true
Combined Example:
methods:
- doFinal
- filter: getDigestSize
as: $1
pure: true
- filter: getAlgorithmName
pure: false
Type: string
Default: $
Description: If present, generates a wrapper function with the name given. $
refers to the capitalized
version of the existing package name. For example,
org.bouncycastle.crypto
is changed to Org.Bouncycastle.Crypto
.
Example:
module-prefix: BouncyCastle
Type: boolean
Default: false
Description: If true
, will treat all matching classes as immutable objects.
Example:
pure: true
Type: string
Default: Not present
Description: If present, generates a wrapper function with the name given.
Example:
wrapper: mk$
This key only applies to interfaces and abstract classes.
Don't forget to handle Arrays