Skip to content

skial/klas

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

85 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Klas

Klas gives you more control on the order build macros are run. With Klas you only have to add implements Klas to your class and any build macro that self registers with Klas can be accessed with metadata.

Installation Setup Register w/ Klas Build Hooks Build Order
Utilities Defines Metadata Example Libs using Klas

You can find more information on how klas works at the end of the README.

Installation

With haxelib git.

haxelib git klas https://github.com/skial/klas master src

With haxelib local.

# Download the archive.
https://github.com/skial/klas/archive/master.zip

# Install archive contents.
haxelib local master.zip

Setup

Add implements Klas to any class. Make sure -lib klas is in your .hxml build file.

Register a build macro with Klas

To add your build macro to Klas you need to do two things.

  1. Add the following initialize method to your build macro.

    private static function initialize() {
    	try {
    		KlasImp.initialize();
    	} catch (e:Dynamic) {
    		// This assumes that `implements Klas` is not being used
    		// but `@:autoBuild` or `@:build` metadata is being used 
    		// with the provided `your.macro.Class.build()` method.
    	}
    }
  2. You should also provide a normal entry point for people not using Klas, who will be using the @:autoBuild or @:build metadata.

  3. If your build macro does not already have an extraParams.hxml file, create one in the root of your library.

  4. Add --macro path.to.your.Class.initialize() to your extraParams.hxml file.

  5. Anyone using Klas and your macro library, with all the correct -lib entries will automatically bootstrap themselves into Klas.

Build hooks

Klas provides the following hooks/variables you can register with. You would place the hook after the line KlasImp.initialize() in your initialize method.

  1. The info string map allows you to register a callback which allows you to inspect the Type and its build fields if the Type has any. The string map key should be the path to the type you are interested in and your handler type should be Array<Type->Array<Field>->Void>.

    // Hooking into Klas.
    private static function initialize() {
    	try {
    		KlasImp.initialize();
    		KlasImp.info.set( 'path.to.your.Type', ClsMacro.handler );
    	} catch (e:Dynamic) { 
    		
    	}
    }

    You can request to inspect a type by calling uhx.macro.KlasImp.inspect('path.to.your.Type', Your.callback) which will run Your.callback once the Type has been processed by Klas. Your callback should have the type of Type->Array<Field>->Void.

  2. The allMetadata signal allows you to register your callback which will be run for each class that implements Klas. Your handler should be of the type ClassType->Array<Field>->Array<Field>.

    // Hooking into Klas.
    private static function initialize() {
    	try {
    		KlasImp.initialize();
    		KlasImp.allMetadata.add( ClsMacro.handler );
    	} catch (e:Dynamic) { 
    		
    	}
    }
  3. The classMetadata signal allows you to register your interest in classes that have a specific metadata attached to them. Your handler should be of the type ClassType->Array<Field>->Array<Field>.

    package path.to.your;
    
    @:metadata('value1', 'value2') class Cls {
    	
    }
    // Hooking into Klas.
    private static function initialize() {
    	try {
    		KlasImp.initialize();
    		KlasImp.classMetadata.add( ':metadata', ClsMacro.handler );
    	} catch (e:Dynamic) { 
    		
    	}
    }
  4. The fieldMetadata signal allows you to register your interest in methods and variables that have specific metadata attached to them. Your handler should be of the type ClassType->Field->Field.

    package path.to.your;
    
    class Cls {
    	
    	@:metadata public static var hello = 'hello';
    	@:metadata public static function main() {}
    	
    }
    // Hooking into Klas.
    private static function initialize() {
    	try {
    		KlasImp.initialize();
    		KlasImp.fieldMetadata.add( ':metadata', ClsMacro.handler );
    	} catch (e:Dynamic) { 
    		
    	}
    }
  5. The inlineMetadata signal allows you to register your interest in methods that contain inline metadata. Your handler should be of the type ClassType->Field->Field.

    package path.to.your;
    
    class Cls {
    	
    	public function new() {}
    	
    	public function setup():Void {
    		var a = @:metadata 100;
    	}
    	
    }
    // Hooking into Klas.
    private static function initialize() {
    	try {
    		KlasImp.initialize();
    		KlasImp.inlineMetadata.add( ~/@:metadata\s/, ClsMacro.handler );
    	} catch (e:Dynamic) { 
    		
    	}
    }
  6. The rebuild string map allows you to register a handler which will return a rebuilt type. Your handler should be of the type ClassType->Array<Field>->Null<TypeDefinition>. To trigger a rebuild, you have to call uhx.macro.KlasImp.triggerRebuild('your.Class', ':metadata'). This should only be called during the macro context.

    // Hooking into Klas.
    private static function initialize() {
    	try {
    		KlasImp.initialize();
    		KlasImp.rebuild.set( ':metadata', ClsMacro.handler );
    	} catch (e:Dynamic) { 
    		
    	}
    }
    // Triggering a retype.
    private static macro function hello_world():ExprOf<String> {
    	uhx.macro.KlasImp.triggerRebuild( 'path.to.your.Cls', ':metadata' );
    	return macro 'Hello World';
    }

Build order

  1. info
  2. classMetadata
  3. fieldMetadata
  4. inlineMetadata
  5. allMetadata
  6. rebuild

The info hook might be processed before all other hooks.

rebuild will only run after allMetadata if any pending calls to uhx.macro.KlasImp.rebuild exist. Otherwise it runs whenever it is called with uhx.macro.KlasImp.triggerRebuild.

Utilities

You can call these utility methods from your macro methods.

  1. KlasImp.triggerRebuild(path:String, metadata:String):Null<TypeInfo>. Attempt to rebuild the type specified by path if it has matchingmetadata. If successful, a TypeInfo object will be returned, otherwise null is returned.

  2. KlasImp.onRebuild:Signal1<TypeInfo>. A signal which will notify you when a type has been rebuilt.

  3. KlasImp.inspect(path:String, callback:Type->Array<Field>->Void):Bool. Register a callback to be called when the specified path is detected.

Conditional Defines

Add the following defines to your hxml file.

  1. -D klas_verbose. Setting this will leave certain utility fields in your output and print debug information to your terminal.

  2. -D klas_rebuild. Used internally when types have been rebuilt.

Metadata

  1. @:KLAS_SKIP. Used internally to completely skip a type from being processed by Klas.

Example

The following initialize, build and handler methods are taken from Wait.hx.

	private static function initialize() {
		try {
			KlasImp.initialize();
			KlasImp.inlineMetadata.add( ~/@:wait\s/, Wait.handler );
		} catch (e:Dynamic) {
			// This assumes that `implements Klas` is not being used
			// but `@:autoBuild` or `@:build` metadata is being used 
			// with the provided `uhx.macro.Wait.build()` method.
		}
	}
	
	public static function build():Array<Field> {
		var cls = Context.getLocalClass().get();
		var fields = Context.getBuildFields();
		
		for (i in 0...fields.length) {
			fields[i] = handler( cls, fields[i] );
		}
		
		return fields;
	}
	
	public static function handler(cls:ClassType, field:Field):Field {
		switch(field.kind) {
			case FFun(method) if (method.expr != null): loop( method.expr );
			case _:
		}
		
		return field;
	}

Libraries Using Klas

  1. yield
  2. wait
  3. cmd
  4. named
  5. seri
  6. 3rd_klas

How Klas Works

How inspect works

Klas adds one build macro to every single type which gets initialized via its extraParams.hxml file. This KlasImp.inspection build macro allows Klas to gather information about all types which is later available via KlasImp.inspect.

How rebuild works

You can rebuild a type by calling KlasImp.rebuild. In this method Klas checks that its details have been gathered and the metadata you specified exists. It then passes all the information about the type to a pre registered callback and expects to receive a TypeDefintion object back.

Klas then saves this TypeDefinition object to $cwd/klas/gen/, reconstructing the types package. For example, rebuilding a type called a.b.Test will save the rebuilt type to $cwd/klas/gen/a/b/Test.hx. At the end of compiling, Klas re-invokes the compiler with the same arguments you compiled with but with the additional arguments -cp $cwd/klas/gen/ and -D klas_rebuild.

This is currently the only cross platform, target independant way to rebuild a type where a combination of initialization macros, build macros, generic macros and macro methods are just not enough to achieve your goal.

About

Managed Build Macros [Semi Experimental] [Working]

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages