-
-
Save jdonaldson/8f35f8c977a7e1722b853e39a9b8ef71 to your computer and use it in GitHub Desktop.
| class Main { | |
| static function main(){ | |
| var router = Paths.buildRouter(Page); | |
| var o = router(["Home"]); | |
| trace(o + " is the value for o"); | |
| var p = router(["Foo","Baz", "1"]); | |
| trace(p + " is the value for p"); | |
| var q = router(["Scales","Guitar","Chromatic"]); | |
| trace(q + " is the value for q"); | |
| } | |
| } | |
| enum abstract Bar(String) from String to String { | |
| var Baz ='heeey'; | |
| var Bing='hii'; | |
| var Boo = 'hooo'; | |
| } | |
| enum Page { | |
| Home; | |
| Foo(bar : Bar, val : Int); | |
| Scales(instrument:GuitarMember, scale:Scale); | |
| Intervals(instrument:GuitarMember, scale:Scale, key:Note); | |
| ChordProgressionPage(instrument:GuitarMember, scale:Scale, key:Note, highlighted:Scale); | |
| SuspendedChordsPage(instrument:GuitarMember, scale:Scale, key:Note, highlighted:Scale); | |
| PowerChordsPage(instrument:GuitarMember, scale:Scale, key:Note, highlighted:Scale); | |
| ScaleNotesPage(instrument:GuitarMember, scale:Scale, key:Note); | |
| ChordNotesPage(instrument:GuitarMember, scale:Scale, key:Note); | |
| NoteOverviewPage(instrument:GuitarMember, key:Note); | |
| } | |
| @:enum abstract GuitarMember(String) from String to String { | |
| var Guitar = "guitar"; | |
| var Ukulele = "ukulele"; | |
| var BassGuitar = "bass-guitar"; | |
| var Banjo = "banjo"; | |
| var Mandolin = "mandolin"; | |
| } | |
| @:enum abstract Scale(String) from String to String { | |
| var Chromatic = "chromatic"; | |
| var NaturalMinor = "natural-minor"; | |
| var NaturalMajor = "natural-major"; | |
| var MinorPentatonic = "minor-pentatonic"; | |
| var MajorPentatonic = "major-pentatonic"; | |
| var MelodicMinor = "melodic-minor"; | |
| var HarmonicMinor = "harmonic-minor"; | |
| var Blues = "blues"; | |
| var Ionian = "ionian"; | |
| var Dorian = "dorian"; | |
| var Phygian = "phygian"; | |
| var Lydian = "lydian"; | |
| var Mixolydian = "mixolydian"; | |
| var Aeolian = "aeolian"; | |
| var Locrian = "locrian"; | |
| } | |
| @:enum abstract Note(String) from String to String { | |
| /* 1 */ var C = "e"; | |
| /* 2 */ var CSharp = "c-sharp"; | |
| /* 3 */ var D = "d"; | |
| /* 4 */ var DSharp = "d-sharp"; | |
| /* 5 */ var E = "e"; | |
| /* 6 */ var F = "f"; | |
| /* 7 */ var FSharp = "f-sharp"; | |
| /* 8 */ var G = "g"; | |
| /* 9 */ var GSharp = "g-sharp"; | |
| /* 10 */ var A = "a"; | |
| /* 11 */ var ASharp = "a-sharp"; | |
| /* 12 */ var B = "b"; | |
| } |
| Main.hx|7 info| Home is the value for o | |
| Main.hx|10 info| Foo(heeey,1) is the value for p | |
| Main.hx|13 info| Scales(guitar,chromatic) is the value for q |
| #if macro | |
| import haxe.macro.Expr; | |
| import haxe.macro.Type; | |
| import haxe.macro.Context; | |
| import haxe.macro.Context.currentPos as pos; | |
| import haxe.macro.Context.followWithAbstracts as follow; | |
| #end | |
| class Paths { | |
| macro public static function buildRouter(enm : Expr) : Expr { | |
| return macro function(paths : Array<String>) { | |
| return ${buildSwitchExpr(enm)} | |
| }; | |
| } | |
| #if macro | |
| public static function buildExprFromType(type : Type, idx = 0) : Expr { | |
| return switch type { | |
| case TEnum(_,_) : buildSwitchFromType(type); | |
| case TAbstract(abs, []) if (abs.get().module == "StdTypes") : { | |
| switch (abs.get().name) { | |
| case "String" : macro paths.shift(); | |
| case "Int" : macro Std.parseInt(paths.shift()); | |
| case "Float" : macro Std.parseFloat(paths.shift()); | |
| default : macro null; | |
| } | |
| } | |
| case TAbstract(abs, []) : { | |
| var impl = abs.get().impl.get(); | |
| buildSwitchFromAbsImpl(impl); | |
| } | |
| default : macro null; | |
| } | |
| } | |
| public static function buildCaseFromEnumField(enmf: EnumField) : Case { | |
| return switch enmf.type { | |
| case TEnum(enm, []) : { | |
| return { | |
| values : [macro $v{enmf.name}], | |
| expr : macro $i{enmf.name} | |
| } | |
| } | |
| case TFun(args, ret) : { | |
| var arg_exprs = Lambda.map(args, a->{ | |
| return buildExprFromType(a.t); | |
| }); | |
| return { | |
| values : [macro $v{enmf.name}], | |
| expr : macro $i{enmf.name}($a{arg_exprs}) | |
| } | |
| } | |
| default : null; | |
| }; | |
| } | |
| public static function buildSwitchFromAbsImpl(impl : ClassType ) : Expr { | |
| var cases = Lambda.map(impl.statics.get(), s->{ | |
| return { | |
| values : [macro $v{s.name}], | |
| expr : macro $i{s.name}, | |
| guard : null | |
| }; | |
| }); | |
| cases.push({ | |
| values : [macro _], | |
| expr : macro null, | |
| guard : null | |
| }); | |
| return { | |
| expr : ESwitch(macro paths.shift(), cases, null), | |
| pos : Context.currentPos() | |
| }; | |
| } | |
| public static function buildSwitchFromType(type : Type, idx = 0) : Expr { | |
| return switch type { | |
| case TEnum(enm, []) : { | |
| var enme = enm.get(); | |
| var cases : Array<Case> = Lambda.map(enme.constructs, c-> { | |
| return buildCaseFromEnumField(enme.constructs.get(c.name)); | |
| }); | |
| cases.push({ | |
| values : [macro _], | |
| expr : macro null | |
| }); | |
| return { | |
| expr : ESwitch( macro paths.shift(), cases, null), | |
| pos : Context.currentPos() | |
| } | |
| } | |
| default : macro null; | |
| } | |
| } | |
| public static function buildSwitchExpr(expr : Expr , idx = 0) : Expr { | |
| return switch expr.expr{ | |
| case EConst(CIdent(val)) : buildSwitchFromType(Context.getType(val)); | |
| case _: throw new Error("not an enum", Context.currentPos()); | |
| } | |
| } | |
| #end | |
| } | |
That's great, thanks for the ideas! I put my latest efforts up on a wip repo : https://github.com/jdonaldson/paths
Reversing the enum to a path is exactly what I was going to do next. I'll give a shot at merging everything soon.
FWIW, I feel pretty good about this ADT parsing approach for handling basic paths. However, I've run into some issues when trying to handle parameters. I can parse a parameter string ( e.g. ?option=1&foo=bar&baz=2) into a given structural type ({option:Int, foo:String, baz:Int}). That's no problem. Also, Haxe pattern matching + extractors seem like a great way of checking/ensuring different fields are present/typed:
switch params {
case {option : n} : {
trace(n);
}
}I had in mind that you could switch across 3 different relevant components to define your controller logic (path, parameters, and headers). This could be done using Haxe's switchable array notation:
switch [path, params, headers] {
case [Home, {option : opt}, GET(cookie)] : HomeControllerLogic(opt, cookie.user_id)
// ...
}I hit a snag with parameters though. Switching on a structural type means that you would need to define that type beforehand. The structural types needed to encode params will likely differ between paths, and so I wound up creating different parameter structural types for each path, and then wrapping it up in another enum. That winds up feeling cumbersome, I'm trying to think of a different way of doing it.
switch [path, params, headers] {
case [Home, HomeParams({option : opt}), GET(cookie)] : HomeControllerLogic(opt, cookie.user_id)
case [Foo, FooParams({bar :n}), GET(cookie)] if n > 10: FooControllerLogic(n, cookie.user_id)
// ...
}Having a path enum, a parameter enum and object literals seems a bit much. On the other hand you might have a lot of optional parameters which will look odd when converted back to a path (e.g, Home/// or Home/null/null/).
In my project I only put a few identifying parameters into my Page enum, I didn't go for all parameters. E.g. some pages have filter and sorting features, and they still use regular GET parameters - I might add them at some point.
I'll keep an eye on your repository!
I tried using your routing macro in a project in the last few days. It works great, though I had to make a few adjustments (see https://gist.github.com/AlexHaxe/7a2618835b3b24d10ce7bc103f2e20dd).
I also needed a way to reverse an enum back to a path, so I integrated your
toPathmacro (also with adjustments).I've added a few testcases and a
@catchrestmeta so that you can have one case that matches all unmatched paths (e.g. because they come from a database or someplace else).