HaxeFoundation / haxe-evolution

Repository for maintaining proposal for changes to the Haxe programming language
111 stars 58 forks source link

Macro instances #71

Closed filt3rek closed 4 years ago

filt3rek commented 4 years ago

Hej !

I'm sorry, I didn't know how a proposal work, at the begining I just wanted to ask simply if something like that could be done without much work. Then now, I followed the proposal process, I hope all is filled right and I do all the right way...

rendered version

Thanks for your interest, Regards, Michal

RealyUniqueName commented 4 years ago

Given macros exist at compile time only, I don't think that is possible to implement.

filt3rek commented 4 years ago

I don't understand, yes it's supposed to be in compile-time.

RealyUniqueName commented 4 years ago

Because in your sample:

var myHtmlFile  = new HtmlFile( "path/to/my/template.mtt" );
var myElement1  = myHtmlFile.getElementById( "myId1" );
var myElement2  = myHtmlFile.getElementById( "myId2" );
var myElement3  = myHtmlFile.getElementById( "myId3" );

myHtmlFile is defined and exists at run time.

filt3rek commented 4 years ago

Yes it looks like, but in fact I don't want it to exist at runtime, only compiletime. If you look at the sample I wrote, getElementById()method is a macro method that generates a real js.Browser.document.getElementById() call for runtime if it find the id at compiletime or throws a compiletime error if the id doesn't exist. So here, neither var myHtmlFile = new HtmlFile( "path/to/my/template.mtt" ); and var myElement1 = myHtmlFile.getElementById( "myId1" ); don't need to exist at runtime, only compiletime... i don't know if it's clear :/

filt3rek commented 4 years ago

The only difference between a static method, that I put in the sample too and which works, and a "new one using macro instance", would be that I give the path to the Html file in the macro instance constructor and then I call the macro instance method giving it only the id, and not the path + the id...

RealyUniqueName commented 4 years ago

Then what would happen if a user do this?

var myHtmlFile = new HtmlFile( "path/to/my/template.mtt" );
var anotherVar = myHtmlFile;
anotherVar.getElementById( "myId1" );

What if myHtmlFile was passed to an argument of a function?

I think it's to much hassle for avoiding static caches.

filt3rek commented 4 years ago

I think myHtmlFile should be a "macro type", so user couldn't assign a "macro type" to a "run-time" type, something like that... Maybe Kevin should come to this discussion because he wrote that technically it could be done.

R32 commented 4 years ago

For your example, I found two ways to achieve it:

The first:

import HtmlFile.home;
import HtmlFile.cart;

class Main {
    static function main() {
        var header = home("#header");
        var menu = cart(".menu");
    }
}
class HtmlFile {
#if macro
    var ids : Array<String>;
    function new( path : String ) {
    }
    function querySelector( css : String ) {
        return macro js.Browser.document.querySelector( $v{ css } );
    }

    static function _query( path : String, css : String ) {
        var hf = map.get(path);
        if (hf == null) {
            hf = new HtmlFile(path);
            map.set(path, hf);
        }
        return hf.querySelector(css);
    }
    @:persistent static var map = new Map<String, HtmlFile>();
#end
    macro public static function home( css : String ) {
        return _query("home.html", css);
    }
    macro public static function cart( css : String ) {
        return _query("cart.html", css);
    }
}

The second: uses @:genericBuild

// main.hx

// maybe "@:eager" is not necessary
@:eager typedef Home = HtmlFileBuild<"home.html">;
@:eager typedef Cart = HtmlFileBuild<"cart.html">;

class Main {
    static function main() {
        var header = Home.query("#header");
        var menu = Cart.query(".menu");
    }
}

// HtmlFile.hx

@:genericBuild(HtmlFile.build()) 
class HtmlFile<Const> {
#if macro
    public static function build() {
    // ......
    }
#end
}
filt3rek commented 4 years ago

Hej @R32 ,

Thanks for your reply !

Your first example, it's not very generic, as a instance could be, because you have to modify the HtmlFile to implement the templates by yourself.

The second example could be good and near to the behaviour a macro instance could have even if the type params could not take any type of arguments, or they would be tricky to implement. I think It's like in haxe.xml.Proxy<T,T2>

filt3rek commented 4 years ago

It's a bit harder to implement and to use :

#if macro
import haxe.macro.Context;
import sys.io.File;
import haxe.macro.Expr;
#end

#if !macro
@:genericBuild( HtmlFileBuilder.build() ) 
#end
class HtmlFile<Const>{}

class HtmlFileBuilder{
#if macro
    public static function build() : ComplexType{
        var spath   = switch Context.getLocalType(){
            case TInst(_, [TInst(t, _)]):
                t.toString().substr( 1 );
            case _ : null;
        }
        if( spath == null ){
            Context.fatalError( 'Const type parameter needed', Context.currentPos() );
        }
        var cfPath  = Sys.getCwd() + Context.getPosInfos( Context.currentPos() ).file;
        var path    = new haxe.io.Path( cfPath );
        var content = null;
        try{
            content = File.getContent( path.dir + "/" + spath );
        }catch( e : Dynamic ){
            Context.fatalError( 'Can\'t find file "$spath"', Context.currentPos() );
        }
        var regex   = ~/id=["||']([a-z0-9_-]+)["||']/igm;
        var s       = content;
        var ae      = [];
        while (regex.match( s ) ){
            var name    = regex.matched( 1 );
            ae.push( macro $v{ name } );
            s   = regex.matchedRight();
        }

        var cname   = 'HtmlFile_S${ spath.split( "/" ).join( "_" ).split( "." ).join( "_" ) }';
        var cl  = macro class $cname{
            static var ids  = $a{ ae };
            macro public static function getElementById( eid : haxe.macro.Expr ){
                var id  = switch eid.expr {
                    case haxe.macro.Expr.ExprDef.EConst( CString(s) )   : s;
                    case _  : null;
                }
                if( ids.indexOf( id ) == -1 ){
                    haxe.macro.Context.fatalError( 'id "'+ id +'" doesn\'t exist in "$spath"', haxe.macro.Context.currentPos() );
                    return null;
                }else{
                    return macro js.Browser.document.getElementById( $eid );
                }
            }
        }

        Context.defineType( cl );
        return Context.toComplexType( Context.getType( cname ) );
    }
#end
}
Simn commented 4 years ago

We have decided to reject this proposal in our haxe-evolution meeting yesterday.

It is unclear what problem you're trying to solve here and the suggestion raises more questions than it answers. Please present the actual issue you're having as a forum post over at http://community.haxe.org so it can be discussed.