Skip to content

Instantly share code, notes, and snippets.

@nadako
Last active October 16, 2021 04:38
Show Gist options
  • Select an option

  • Save nadako/7edbc859e5206b2dbd65df2fd96aa334 to your computer and use it in GitHub Desktop.

Select an option

Save nadako/7edbc859e5206b2dbd65df2fd96aa334 to your computer and use it in GitHub Desktop.

Revisions

  1. nadako revised this gist Apr 18, 2019. 1 changed file with 10 additions and 0 deletions.
    10 changes: 10 additions & 0 deletions 2 WithMacro.hx
    Original file line number Diff line number Diff line change
    @@ -36,6 +36,16 @@ class WithMacro {
    case macro $i{fieldName} = $value:
    objectDecl.push({field: fieldName, expr: value});
    overriden[fieldName] = true;
    case {expr: EDisplay(macro null, DKMarked), pos: p}: // toplevel completion
    var remainingFieldsCT = TAnonymous([
    for (field in fields) if (!overriden.exists(field.name)) {
    pos: field.pos,
    name: field.name,
    doc: field.doc,
    kind: FVar(field.type.toComplexType())
    }
    ]);
    return {pos: p, expr: EDisplay({pos: p, expr: EField(macro (null : $remainingFieldsCT), "")}, DKDot)};
    case _:
    throw new Error("Invalid override expression, should be field=value", expr.pos);
    }
  2. nadako revised this gist Mar 13, 2019. 1 changed file with 0 additions and 2 deletions.
    2 changes: 0 additions & 2 deletions 2 WithMacro.hx
    Original file line number Diff line number Diff line change
    @@ -1,5 +1,3 @@
    package ax3;

    #if macro
    import haxe.macro.Context;
    import haxe.macro.Expr;
  3. nadako revised this gist Mar 13, 2019. 1 changed file with 12 additions and 0 deletions.
    12 changes: 12 additions & 0 deletions 2 WithMacro.hx
    Original file line number Diff line number Diff line change
    @@ -1,3 +1,5 @@
    package ax3;

    #if macro
    import haxe.macro.Context;
    import haxe.macro.Expr;
    @@ -6,6 +8,16 @@ using haxe.macro.Tools;

    @:dce
    class WithMacro {
    /**
    Return a copy of a structure, replacing given fields.
    This provides an OCaml-like `with` syntax:
    ```haxe
    object.with(a = 13, b = "hi")
    // is the same as
    {a: 13, b: "hi", otherField: object.otherField}
    ```
    **/
    public static macro function with<T:{}>(object:ExprOf<T>, overrides:Array<Expr>):ExprOf<T> {
    // process given object expression and get its type
    var type = Context.typeof(object);
  4. nadako created this gist Feb 28, 2018.
    17 changes: 17 additions & 0 deletions 1 Main.hx
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,17 @@
    using WithMacro;

    typedef Player = {
    final name:String;
    final level:Int;
    }

    class Main {
    static function main() {
    var player = {name: "Dan", level: 15};
    trace(levelUp(player));
    }

    static function levelUp(player:Player) {
    return player.with(level = player.level + 1);
    }
    }
    55 changes: 55 additions & 0 deletions 2 WithMacro.hx
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,55 @@
    #if macro
    import haxe.macro.Context;
    import haxe.macro.Expr;
    using haxe.macro.Tools;
    #end

    @:dce
    class WithMacro {
    public static macro function with<T:{}>(object:ExprOf<T>, overrides:Array<Expr>):ExprOf<T> {
    // process given object expression and get its type
    var type = Context.typeof(object);

    // check that it's an anonymous structure and extract fields information
    var fields = switch type.follow() {
    case TAnonymous(_.get() => anon): anon.fields;
    case _: throw new Error("Not an anonymous structure", object.pos);
    }

    var objectDecl:Array<ObjectField> = [];
    var overriden = new Map();

    // check field override argument expressions and add them to the new object declaration,
    // as well as marking them as overriden for easier checking in the second part
    for (expr in overrides) {
    switch expr {
    case macro $i{fieldName} = $value:
    objectDecl.push({field: fieldName, expr: value});
    overriden[fieldName] = true;
    case _:
    throw new Error("Invalid override expression, should be field=value", expr.pos);
    }
    }

    // add the rest of fields from this type (those that aren't overriden)
    for (field in fields) {
    var fieldName = field.name;
    if (!overriden.exists(fieldName)) {
    // we use `tmp` as the reference for the original object, since we store it into a local var
    objectDecl.push({field: fieldName, expr: macro @:pos(object.pos) tmp.$fieldName});
    }
    }

    // construct object declaration expression
    var expr = {expr: EObjectDecl(objectDecl), pos: Context.currentPos()};

    // get the syntax representation of object's type
    var ct = type.toComplexType();

    // construct the whole resulting expression. it consists of three parts:
    // - the `tmp` var declaration in which we store the original object
    // - the generated new object declaration expression (that references `tmp` for non-overriden fields)
    // - the type-check expression that ensures that our resulting expression is of correct type
    return macro @:pos(expr.pos) ({ var tmp = $object; $expr; } : $ct);
    }
    }
    11 changes: 11 additions & 0 deletions 3 generated.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,11 @@
    // Generated by Haxe 4.0.0 (git build development @ eaf32fecd)
    (function () { "use strict";
    var Main = function() { };
    Main.main = function() {
    console.log("Main.hx:11:",Main.levelUp({ name : "Dan", level : 15}));
    };
    Main.levelUp = function(player) {
    return { level : player.level + 1, name : player.name};
    };
    Main.main();
    })();