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.Doubleshould 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
Setis present in the map file since its data constructor was exported.MessageDigestis in the map file since its data constructor was exported.JCharacteris not in the map file since its data constructor wasn't exported.JDoubleis also not in the map file since it wasn't exported at all.
- Generate
.ffimapfiles. - 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].ffispecfile- All the Maven dependencies listed under
maven-depends: - All the jar files and class files listed under
java-sources: - All the
*.ffimapfiles 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
.ffispecfile presence and triggereta-ffi - Send auto-generated files to
etaCLI 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.DigestAt 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: JStringThis 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
helloandhellloand so on. -
A list of filters, all of which must hold (AND)
Example:
filter: - he.*llo - hi.*
This will match against
hehilloandhehehilloand so on. -
A conjunction of filters (AND)
Example:
filter: and: - he.*llo - hi.*
This will match against
hehilloandhehehilloand so on. -
A disjunction of filters (OR)
Example:
filter: or: - he.*llo - hi.*
This will match against
helloandhiand so on. -
Negation of a filter
Example:
filter: not: he.*llo
This will not match against
helloandhellloand so on. -
Prefix filter
Example:
filter: prefix: hello
This will match against
helloandhello1and so on. -
Suffix filter
Example:
filter: suffix: hello
This will match against
helloand1helloand so on. -
Scoped filters
Example:
filter: scope: hello. filter: or: - hi - hello
This will match against
hello.helloandhello.hiand so on. -
abstractType:booleanDescription: 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)
-
staticType:booleanDescription: Iftrue, matches only static methods. Example:static: true
-
typeType:stringDescription: 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
Inheritstype family instance and only public empty constructors will be imported for all the classes that match the corresponding - Abstract Class/Interface: JWT,
Inheritsinstance, and imports of all the public and protected abstract methods with subtype-polymorphism in the tag type of theJavamonad. - Enums: JWT,
Inheritsinstance, 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:
-
filterType:filterDefault:.*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
-
asType:stringDefault: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
-
safetyType:stringValues:unsafe,safe,interruptibleDefault:unsafeDescription: Sets the import to beunsafe,safe, orinterruptible, respectively. Example:safety: safe -
pureType:booleanDefault:falseDescription: Iftrue, the import is done in theJavamonad, otherwise it's a pure import.Example:
pure: true
Combined Examples:
constructors:
filter: (int)
as: new$1
pure: trueconstructors:
- filter: (int)
pure: false
- filter: 3
pure: trueThis key is used to specify which methods need to be imported, and how they are imported.
Keys:
-
filterType:filterDescription: 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
-
asType:stringDefault:$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
-
safetyType:stringValues:unsafe,safe,interruptibleDefault:unsafeDescription: Sets the import to beunsafe,safe, orinterruptible, respectively. Example:safety: safe -
pureType:booleanDefault:falseDescription: Iftrue, the import is done in theJavamonad, otherwise it's a pure import.Example:
pure: true
Combined Example:
methods:
- doFinal
- filter: getDigestSize
as: $1
pure: true
- filter: getAlgorithmName
pure: falseThis key is used to specify which fields need to be imported, and how they are imported.
Keys:
-
filterType:filterDescription: 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
-
asType:stringDefault:$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
-
safetyType:stringValues:unsafe,safe,interruptibleDefault:unsafeDescription: Sets the import to beunsafe,safe, orinterruptible, respectively. Example:safety: safe -
setType:booleanDefault:falseDescription: Iftrue, will do a field setter import, otherwise it won't. Example:set: true -
pureType:booleanDefault:falseDescription: Iftrue, the import is done in theJavamonad, otherwise it's a pure import. This cannot betruewhensetis true, since that's a contradiction. Example:pure: true
Combined Example:
methods:
- doFinal
- filter: getDigestSize
as: $1
pure: true
- filter: getAlgorithmName
pure: falseType: 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: BouncyCastleType: boolean
Default: false
Description: If true, will treat all matching classes as immutable objects.
Example:
pure: trueType: 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