Skip to content

Instantly share code, notes, and snippets.

@rahulmutt
Last active January 12, 2018 01:11
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save rahulmutt/ba5f5302de7b23a635a454ccc9e8961c to your computer and use it in GitHub Desktop.
Save rahulmutt/ba5f5302de7b23a635a454ccc9e8961c to your computer and use it in GitHub Desktop.
Eta FFI Specification

Eta FFI Specification

Architecture

The Eta FFI Generator will consist of support from three components:

  • The Eta compiler
  • The Etlas build tool
  • The eta-ffi tool

Eta Compiler

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

Notes

  • 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.

Implementation Status

  • 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.

Etlas Build Tool

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 in build-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.

Implementation Status

  • Detect .ffispec file presence and trigger eta-ffi
  • Send auto-generated files to eta CLI arguments during build

Eta FFI Tool

This tool will take the inputs mentioned above and generate Eta source files.

CLI Specification

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.

.ffispec Format

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.

mappings

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

targets

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

Filters can take the following forms:

  • A single regular expression

    Example:

    filter: he.*llo

    This will match against hello and helllo and so on.

  • A list of filters, all of which must hold (AND)

    Example:

    filter: 
    - he.*llo
    - hi.*

    This will match against hehillo and hehehillo and so on.

  • A conjunction of filters (AND)

    Example:

    filter: 
      and:
      - he.*llo
      - hi.*

    This will match against hehillo and hehehillo and so on.

  • A disjunction of filters (OR)

    Example:

    filter: 
      or:
      - he.*llo
      - hi.*

    This will match against hello and hi and so on.

  • Negation of a filter

    Example:

    filter: 
      not: he.*llo

    This will not match against hello and helllo and so on.

  • Prefix filter

    Example:

    filter: 
      prefix: hello

    This will match against hello and hello1 and so on.

  • Suffix filter

    Example:

    filter: 
      suffix: hello

    This will match against hello and 1hello and so on.

  • Scoped filters

    Example:

    filter: 
      scope: hello.
      filter: 
        or:
        - hi
        - hello

    This will match against hello.hello and hello.hi and so on.

  • abstract Type: boolean Description: If true, 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: If true, 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

Actions operate on classes, interfaces, and enums and specify what and how to import methods.

Defaults

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 the Java 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.
constructors

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 be unsafe, safe, or interruptible, respectively. Example:

    safety: safe
    
  • pure Type: boolean Default: false Description: If true, the import is done in the Java 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
methods

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 be unsafe, safe, or interruptible, respectively. Example:

    safety: safe
    
  • pure Type: boolean Default: false Description: If true, the import is done in the Java monad, otherwise it's a pure import.

    Example:

    pure: true

Combined Example:

methods:
- doFinal
- filter: getDigestSize
  as: $1
  pure: true
- filter: getAlgorithmName
  pure: false
fields

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 be unsafe, safe, or interruptible, respectively. Example:

    safety: safe
    
  • set Type: boolean Default: false Description: If true, will do a field setter import, otherwise it won't. Example:

    set: true
    
  • pure Type: boolean Default: false Description: If true, the import is done in the Java monad, otherwise it's a pure import. This cannot be true when set is true, since that's a contradiction. Example:

    pure: true

Combined Example:

methods:
- doFinal
- filter: getDigestSize
  as: $1
  pure: true
- filter: getAlgorithmName
  pure: false
module-prefix:

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
pure

Type: boolean Default: false Description: If true, will treat all matching classes as immutable objects.

Example:

pure: true
wrapper

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.

@Jyothsnasrinivas
Copy link

Don't forget to handle Arrays

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment