Skip to content

Commit

Permalink
Add working initial code.
Browse files Browse the repository at this point in the history
  • Loading branch information
skial committed Oct 22, 2018
1 parent 5bb54f0 commit c868fdd
Show file tree
Hide file tree
Showing 11 changed files with 371 additions and 1 deletion.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
bin/*
dump/*
4 changes: 4 additions & 0 deletions .haxerc
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"version": "c85de49",
"resolveLibs": "scoped"
}
66 changes: 65 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,67 @@
# coerce

Type conversion from one type to another.
Helpful Types to select functions based on method signatures.
~~Type conversion from one type to another.~~

### Types

#### `Resolve`

```haxe
abstract Resolve<T:Function, @:const R:EReg> {
@:from public static function coerce<In>(expr:haxe.macro.Expr.ExprOf<Class<Int>>):haxe.macro.Expr;
}
```

```haxe
import be.types.Resolve.coerce;
class Main {
public static function main() {
var input = '999';
trace( asInt(coerce(Std), input) ); // trace(999);
trace( asInt(coerce(Fake), input) ); // trace(1000);
}
public static inline function asInt(r:Resolve<String->Int, ~/int/i>, v:String):Int return r(v);
}
class Fake {
public static function parseFloat(v:String):Float return 0.0;
public static function falseSig(v:String):Int return throw 'This is skipped due to the `~/int/i` regular expression';
public static function parseInt(v:String):Int return 1000;
}
```

#### `Pick`

`Pick` is a `@:genericBuild` macro which wraps `Resolve` making it a more UX friendly type to work with.
`Pick` only requires the type signature.

```haxe
import be.types.Pick;
import be.types.Resolve.coerce;
class Main {
public static function main() {
var input = '999';
trace( asInt(coerce(Std), input) ); // trace(999);
trace( asInt(coerce(Fake), input) ); // trace(1000);
}
public static function asInt(r:Pick<String->Int>, v:String):Int return r(v);
}
class Fake {
public static function parseFloat(v:String):Float return 0.0;
public static function parseInt(v:String):Int return 1000;
}
```

### Notes

- It's recommended to `import be.type.Resolve.coerce` and wrapping classes in `coerce` to avoid false autocompletion errors.

### Defines

- `-D coerce-verbose` - Paired with `-debug`, this will have the build macros print a bunch of trace statements.
18 changes: 18 additions & 0 deletions build.hxml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
-lib tink_macro

-cp src
-cp tests

-debug

-D eval-stack
-D analyzer-optimize
-D coerce-verbose

-main Entry

-dce full

--each

-js bin/co.js
3 changes: 3 additions & 0 deletions haxe_libraries/tink_core.hxml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
-D tink_core=1.18.0
# @install: lix --silent download "haxelib:/tink_core#1.18.0" into tink_core/1.18.0/haxelib
-cp ${HAXE_LIBCACHE}/tink_core/1.18.0/haxelib/src
4 changes: 4 additions & 0 deletions haxe_libraries/tink_macro.hxml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
-D tink_macro=0.17.2
# @install: lix --silent download "gh://github.com/haxetink/tink_macro#b7e413d839dbf8b81d4a064ae4e90013a2c8615b" into tink_macro/0.17.2/github/b7e413d839dbf8b81d4a064ae4e90013a2c8615b
-lib tink_core
-cp ${HAXE_LIBCACHE}/tink_macro/0.17.2/github/b7e413d839dbf8b81d4a064ae4e90013a2c8615b/src
43 changes: 43 additions & 0 deletions src/be/co/Coerce.hx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package be.co;

#if (eval || macro)
import haxe.macro.Context;
using tink.MacroApi;
#end

@:callable @:notNull abstract Coerce<From, To>(From->To) from From->To {

@:noCompletion public static var stringint:Coerce<String, Int> = Std.parseInt;
@:noCompletion public static var stringfloat:Coerce<String, Float> = Std.parseFloat;


public static macro function value<From, To>(input:ExprOf<From>):ExprOf<To> {
trace( input );
var _input = input.typeof().sure();
var _return = Context.getExpectedType();
trace( _input, _return );
var _property = (_input.getID() + _return.getID()).toLowerCase();
return 'be.co.Coerce.$_property'.resolve(input.pos).call([input]);
}

}

/**
General Structure Layout
---
Builtin @:from conversions for basic types.
---
Self = {
public function from${InputTypeName}:${Self};
public function as${OutputTypeName}(v:${InputType}):${OutputType};
...
}
**/

typedef InputString = {
public function fromString(v:String):InputString;
public function asInt():Int;
public function asFloat():Int;
public function asDate():Date;
}
100 changes: 100 additions & 0 deletions src/be/macros/PickBuilder.hx
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
package be.macros;

import haxe.macro.Type;
import haxe.macro.Expr.TypePath;
import haxe.macro.Expr.ComplexType;
import haxe.macro.*;
import tink.macro.BuildCache;

using tink.MacroApi;

/**
Provides a way to select any method that satifies
- expected arg types
- expected return type
- method names using a regex
---
be.types.Pick<String->Int> // Any method that unifies with String->Int signature.
be.types.Pick<String->Rest->Int> // Any method that has non optional String, zero or more optional args, and returns Int.
be.types.Pick<Int->Self> // Any method that unifies with Int->Self. Self means the method returns a type that can unify with the type/module whatever.
be.types.Pick<Int->Self, ~/from[a-zA-Z]+/> // Same as before, but uses a regular expression to filter method names.
---
thought1
Pick<String->Int>, Pick<T->Int> - this creates an abstract of `String->Int` or `T->Int` in these examples.
Resolve<Class<Std>>, Resolve<Class<T>> - Class<T> is the underlying type. Resolve has a macro @:from function
that detects the expected type of Pick<T>
---
thought2
Instead of a generic build macro, Pick<T> is an abstract class. Underlying type Class<T>, with only a macro @:from
function that searches the Class<T> for a compatible `Context.getExpectedType()`.
**/

class PickBuilder {

public static function search() {
return BuildCache.getTypeN('be.types.Pick', function(ctx:BuildContextN) {
var typeName = ctx.name;
var signature = null;
var signatureType = null;
var signatureComplex = null;
var reg = null;
var ereg = macro ~//;
var filter:EReg = null;
var self = 'be.types.$typeName'.asComplexType();
var ctor = 'be.types.$typeName'.asTypePath();

for (type in ctx.types) {
switch type {
case TFun(args, ret) if (signature == null):
signature = {args:args, ret:ret};
signatureType = type;
signatureComplex = signatureType.toComplex();

case TInst(_.get() => {kind:KExpr( e = {expr:EConst( CRegexp(r, opt) ), pos:pos} )}, _):
reg = {r:r, opt:opt};
filter = new EReg(r, opt);
ereg = e;

case x:
#if (debug && coerce_verbose)
trace( x );
#end
}
}

var ctype = macro:be.types.Resolve<$signatureComplex>;
var call = macro new $ctor(null);
var td = macro class $typeName {
public inline function new(v) this = v;
@:from public static inline function fromResolve(r:$ctype) return new $ctor(r);
}

switch ctype {
case TPath({params:params}):
// Manually insert TPExpr as `macro:be.types.Resolve<$signatureComplex, $ereg>` fails
params.push( TPExpr(ereg) );

case x:
trace(x);
}
td.kind = TDAbstract(ctype, [ctype], [ctype]);
td.meta = [
{name:':forward', params:[], pos:ctx.pos},
{name:':forwardStatics', params:[], pos:ctx.pos},
{name:':notNull', params:[], pos:ctx.pos},
{name:':callable', params:[], pos:ctx.pos}
];

#if (debug && coerce_verbose)
trace( new Printer().printTypeDefinition(td) );
#end

return td;
});
}

public static function foo(expr:Expr):Expr {
return macro {};
}

}
12 changes: 12 additions & 0 deletions src/be/types/Pick.hx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package be.types;

#if (eval||macro)
import haxe.macro.*;

using tink.MacroApi;
#end

#if !(eval || macro)
@:genericBuild( be.macros.PickBuilder.search() )
#end
class Pick<Rest> {}
84 changes: 84 additions & 0 deletions src/be/types/Resolve.hx
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package be.types;

import haxe.Constraints.Function;

#if (eval || macro)
import haxe.macro.*;

using haxe.macro.Context;
using tink.CoreApi;
using tink.MacroApi;
#end

@:callable @:notNull abstract Resolve<T:Function, @:const R:EReg>(T) {

@:noCompletion public inline function get():T return this;

@:from private static inline function fromFunction<T:Function>(v:T):Resolve<T, ~//i> return (cast v:Resolve<T, ~//i>);

@:from public static macro function coerce<In, Out:Function>(expr:ExprOf<Class<In>>):ExprOf<Resolve<Out, ~//i>> {
var typeof = expr.typeof().sure();
var expectedType = Context.getExpectedType();
var expectedComplex = expectedType.toComplex();
var rawType = (macro (null:$expectedComplex).get()).typeof().sure();
var ereg:EReg = null;

switch expectedType.reduce() {
case TAbstract(_, params):
for (param in params) switch param {
case TInst(_.get() => {kind:KExpr({expr:EConst(CRegexp(r, opt)), pos:p})}, _):
#if (debug && coerce_verbose)
trace( r, opt );
#end
ereg = new EReg(r, opt);

case TFun(args, ret):
rawType = param;

case x:
#if (debug && coerce_verbose)
trace( x );
#end

}

case x:
trace( x );

}

var rawComplex = rawType.toComplex();
var result:Expr = null;

switch typeof.reduce() {
case TAnonymous(_.get() => { status: AClassStatics( _.get() => cls )}):
var path = cls.pack.join('.');
path += (path == '' ? '' : '.') + cls.name;
var tpath = path.asTypePath();
var matches = [];
var eregMatch = true;

for (field in cls.statics.get()) {
#if (debug && coerce_verbose)
trace( field.name );
if (ereg != null) trace( ereg.match( field.name ) );
#end
if (ereg != null) eregMatch = ereg.match(field.name);
if (eregMatch && field.type.unify(rawType)) matches.push( field );

}

if (matches.length > 0) result = '$path.${matches[matches.length-1].name}'.resolve();

case x:
//strace( x );
}

#if (debug && coerce_verbose)
trace( rawComplex.toString() );
trace( result.toString() );
#end
return macro @:pos(expr.pos) ($result:$rawComplex);
}

}
36 changes: 36 additions & 0 deletions tests/Entry.hx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package ;

import be.types.Pick;
import be.types.Resolve;
import be.types.Resolve.coerce;

class Entry {

public static function main() {
var m:Pick<String->Int> = coerce(Std);
var a:Resolve<String->Int, ~/int(2)?/i> = coerce(Entry);

trace( foo(m, '123') );
trace( foo(a, '124') );
trace( foo(_ -> 1, '125') );

var input = '999';
trace( asInt(coerce(Std), input) ); // trace(999);
trace( asInt(coerce(Fake), input) ); // trace(1000);
}

public static function fake(v:String):Int return throw 'bugger';
public static function fakeParseInt1(v:String):Int return 10000;
public static function fakeParseInt2(v:String):Int return 20000;

public static inline function foo(func:Pick<String->Int, ~/int(2)?/i>, v:String):Int return func(v);

public static inline function asInt(r:Resolve<String->Int, ~/int/i>, v:String):Int return r(v);

}

class Fake {
public static function parseFloat(v:String):Float return 0.0;
public static function falseSig(v:String):Int return throw 'This is skipped due to the `~/int/i` regular expression';
public static function parseInt(v:String):Int return 1000;
}

0 comments on commit c868fdd

Please sign in to comment.