Skip to content

Commit

Permalink
feat: macro for easy and safe submodule access
Browse files Browse the repository at this point in the history
  • Loading branch information
danielo515 committed Mar 28, 2023
1 parent 5ad7e27 commit e5844ee
Show file tree
Hide file tree
Showing 2 changed files with 58 additions and 21 deletions.
12 changes: 8 additions & 4 deletions src/vim/plugin/Plugin.hx
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,14 @@ package vim.plugin;
@:autoBuild(vim.plugin.PluginMacro.pluginInterface())
interface VimPlugin {
/*
This is an empty interface that is used to attach the @:autoBuild
to classes that implement it. The @:autoBuild macro will generate
the required require code to load the plugin safely.
The implementing class must have a field named libName, which will be used
This is an empty interface that is used to attach @:autoBuild
to classes that implement it.
The @:autoBuild macro will generate the required require function to load the plugin safely.
The implementing class must have a field named `libName`, which will be used
in the generated require function.
Any public variable annotated with `@module` will generate a getter function
to require that as a submodule of the plugin, also using the `libName` field
and a safe require function that is guaranteed to not throw.
*/
}
67 changes: 50 additions & 17 deletions src/vim/plugin/PluginMacro.hx
Original file line number Diff line number Diff line change
Expand Up @@ -6,27 +6,60 @@ import haxe.macro.Context;
import haxe.macro.Expr;

class PluginMacro {
/**
helper to transform the declaration annotated with @module
into a getter that calls Vimx.require with the correct path
based on the library name.
e.g.:
```haxe
// given
@module var submodule:SomeType;
// becomes
public static var submodule(get, null):Null< SomeType >;
inline static public function get_submodule():Null< SomeType > {
return vim.Vimx.require("mylib.submodule");
}
```
**/
static public function makeModule(libName, name, typeName):Array< Field > {
final returnType = macro :$typeName;
final getter = 'get_$name';
final requirePath = libName + '.' + name;
final built = macro class {
public static var $name(get, null):Null< $returnType >;

inline static public function $getter():Null< $returnType > {
return vim.Vimx.require($v{requirePath});
}
};
return built.fields;
}

/**
For docs about this take a look at the VimPlugin interface
*/
macro static public function pluginInterface():Array< Field > {
final fields = Context.getBuildFields();
final localType = Context.getLocalType().toComplexType();
final returnType = macro :$localType;
final newFields = [for (field in fields) {
switch field {
case {name: "libName", kind: FVar(_, {expr: EConst(CString(val, _))})}:
final built = macro class X {
inline static public function require():Null< $returnType > {
return vim.Vimx.require($v{val});
}
};
built.fields[0];
var libName = null;
return fields.flatMap(field -> switch field {
case {name: "libName", kind: FVar(_, {expr: EConst(CString(val, _))})}:
final built = macro class X {
inline static public function require():Null< $returnType > {
return vim.Vimx.require($v{val});
}
};
libName = val;
built.fields;

case _:
continue;
}
}];
if (newFields.length == 0) {
Context.error("No libName field found in plugin interface", Context.currentPos());
}
return fields.concat(newFields);
case {name: name, meta: [{name: "module"}], kind: FVar(t, e)}:
if (libName == null) {
Context.error("libName must be defined before any @module", field.pos);
}
makeModule(libName, name, t);

case _: [field];
});
}
}

0 comments on commit e5844ee

Please sign in to comment.