forked from LeenkxTeam/LNXSDK
580 lines
17 KiB
Haxe
580 lines
17 KiB
Haxe
/*
|
|
* Copyright (C)2005-2019 Haxe Foundation
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining a
|
|
* copy of this software and associated documentation files (the "Software"),
|
|
* to deal in the Software without restriction, including without limitation
|
|
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
|
* and/or sell copies of the Software, and to permit persons to whom the
|
|
* Software is furnished to do so, subject to the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be included in
|
|
* all copies or substantial portions of the Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
|
* DEALINGS IN THE SOFTWARE.
|
|
*/
|
|
|
|
package haxe.macro;
|
|
|
|
import haxe.macro.Expr;
|
|
|
|
/**
|
|
All these methods can be called for compiler configuration macros.
|
|
**/
|
|
#if hl
|
|
@:hlNative("macro")
|
|
#end
|
|
class Compiler {
|
|
/**
|
|
A conditional compilation flag can be set on the command line using
|
|
`-D key=value`.
|
|
|
|
Returns the value of a compiler flag.
|
|
|
|
If the compiler flag is defined but no value is set,
|
|
`Compiler.getDefine` returns `"1"` (e.g. `-D key`).
|
|
|
|
If the compiler flag is not defined, `Compiler.getDefine` returns
|
|
`null`.
|
|
|
|
Note: This is a macro and cannot be called from within other macros. Refer
|
|
to `haxe.macro.Context.definedValue` to obtain defined values in macro context.
|
|
|
|
@see https://haxe.org/manual/lf-condition-compilation.html
|
|
**/
|
|
macro /* <-- ! */ static public function getDefine(key:String) {
|
|
return macro $v{haxe.macro.Context.definedValue(key)};
|
|
}
|
|
|
|
#if (neko || (macro && hl) || (macro && eval))
|
|
static var ident = ~/^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
static var path = ~/^[A-Za-z_][A-Za-z0-9_.]*$/;
|
|
|
|
public static function allowPackage(v:String) {
|
|
#if (neko || eval)
|
|
load("allow_package", 1)(v);
|
|
#end
|
|
}
|
|
|
|
/**
|
|
Set a conditional compiler flag.
|
|
|
|
Usage of this function outside of initialization macros is deprecated and may cause compilation server issues.
|
|
**/
|
|
public static function define(flag:String, ?value:String) {
|
|
#if (neko || eval)
|
|
load("define", 2)(flag, value);
|
|
#end
|
|
}
|
|
|
|
#if (!neko && !eval)
|
|
private static function typePatch(cl:String, f:String, stat:Bool, t:String) {}
|
|
|
|
private static function metaPatch(meta:String, cl:String, f:String, stat:Bool) {}
|
|
|
|
private static function addGlobalMetadataImpl(pathFilter:String, meta:String, recursive:Bool, toTypes:Bool, toFields:Bool) {}
|
|
#end
|
|
|
|
/**
|
|
Removes a (static) field from a given class by name.
|
|
An error is thrown when `className` or `field` is invalid.
|
|
**/
|
|
public static function removeField(className:String, field:String, ?isStatic:Bool) {
|
|
if (!path.match(className))
|
|
throw "Invalid " + className;
|
|
if (!ident.match(field))
|
|
throw "Invalid " + field;
|
|
#if (neko || eval)
|
|
load("type_patch", 4)(className, field, isStatic == true, null);
|
|
#else
|
|
typePatch(className, field, isStatic == true, null);
|
|
#end
|
|
}
|
|
|
|
/**
|
|
Set the type of a (static) field at a given class by name.
|
|
An error is thrown when `className` or `field` is invalid.
|
|
**/
|
|
public static function setFieldType(className:String, field:String, type:String, ?isStatic:Bool) {
|
|
if (!path.match(className))
|
|
throw "Invalid " + className;
|
|
if (!ident.match((field.charAt(0) == "$") ? field.substr(1) : field))
|
|
throw "Invalid " + field;
|
|
#if (neko || eval)
|
|
load("type_patch", 4)(className, field, isStatic == true, type);
|
|
#else
|
|
typePatch(className, field, isStatic == true, type);
|
|
#end
|
|
}
|
|
|
|
/**
|
|
Add metadata to a (static) field or class by name.
|
|
An error is thrown when `className` or `field` is invalid.
|
|
**/
|
|
public static function addMetadata(meta:String, className:String, ?field:String, ?isStatic:Bool) {
|
|
if (!path.match(className))
|
|
throw "Invalid " + className;
|
|
if (field != null && !ident.match(field))
|
|
throw "Invalid " + field;
|
|
#if (neko || eval)
|
|
load("meta_patch", 4)(meta, className, field, isStatic == true);
|
|
#else
|
|
metaPatch(meta, className, field, isStatic == true);
|
|
#end
|
|
}
|
|
|
|
/**
|
|
Add a class path where ".hx" source files or packages (sub-directories) can be found.
|
|
|
|
Usage of this function outside of initialization macros is deprecated and may cause compilation server issues.
|
|
**/
|
|
public static function addClassPath(path:String) {
|
|
#if (neko || eval)
|
|
load("add_class_path", 1)(path);
|
|
#end
|
|
}
|
|
|
|
public static function getOutput():String {
|
|
#if (neko || eval)
|
|
return load("get_output", 0)();
|
|
#else
|
|
return null;
|
|
#end
|
|
}
|
|
|
|
public static function setOutput(fileOrDir:String) {
|
|
#if (neko || eval)
|
|
load("set_output", 1)(fileOrDir);
|
|
#end
|
|
}
|
|
|
|
public static function getDisplayPos():Null<{file:String, pos:Int}> {
|
|
#if (neko || eval)
|
|
return load("get_display_pos", 0)();
|
|
#else
|
|
return null;
|
|
#end
|
|
}
|
|
|
|
/**
|
|
Adds a native library depending on the platform (e.g. `-swf-lib` for Flash).
|
|
|
|
Usage of this function outside of initialization macros is deprecated and may cause compilation server issues.
|
|
**/
|
|
public static function addNativeLib(name:String) {
|
|
#if (neko || eval)
|
|
load("add_native_lib", 1)(name);
|
|
#end
|
|
}
|
|
|
|
/**
|
|
Adds an argument to be passed to the native compiler (e.g. `-javac-arg` for Java).
|
|
**/
|
|
public static function addNativeArg(argument:String) {
|
|
#if (neko || eval)
|
|
load("add_native_arg", 1)(argument);
|
|
#end
|
|
}
|
|
|
|
/**
|
|
Includes all modules in package `pack` in the compilation.
|
|
|
|
In order to include single modules, their paths can be listed directly
|
|
on command line: `haxe ... ModuleName pack.ModuleName`.
|
|
|
|
By default `Compiler.include` will search for modules in the directories defined with `-cp`.
|
|
If you want to specify a different set of paths to search for modules, you can use the optional
|
|
argument `classPath`.
|
|
|
|
@param pack The package dot-path as String. Use `''` to include the root package.
|
|
@param rec If true, recursively adds all sub-packages.
|
|
@param ignore Array of module names to ignore for inclusion.
|
|
You can use `module*` with a * at the end for Wildcard matching
|
|
@param classPaths An alternative array of paths (directory names) to use to search for modules to include.
|
|
Note that if you pass this argument, only the specified paths will be used for inclusion.
|
|
@param strict If true and given package wasn't found in any of class paths, fail with an error.
|
|
**/
|
|
public static function include(pack:String, ?rec = true, ?ignore:Array<String>, ?classPaths:Array<String>, strict = false) {
|
|
var ignoreWildcard:Array<String> = [];
|
|
var ignoreString:Array<String> = [];
|
|
if (ignore != null) {
|
|
for (ignoreRule in ignore) {
|
|
if (StringTools.endsWith(ignoreRule, "*")) {
|
|
ignoreWildcard.push(ignoreRule.substr(0, ignoreRule.length - 1));
|
|
} else {
|
|
ignoreString.push(ignoreRule);
|
|
}
|
|
}
|
|
}
|
|
var skip = if (ignore == null) {
|
|
function(c) return false;
|
|
} else {
|
|
function(c:String) {
|
|
if (Lambda.has(ignoreString, c))
|
|
return true;
|
|
for (ignoreRule in ignoreWildcard)
|
|
if (StringTools.startsWith(c, ignoreRule))
|
|
return true;
|
|
return false;
|
|
}
|
|
}
|
|
var displayValue = Context.definedValue("display");
|
|
if (classPaths == null) {
|
|
classPaths = Context.getClassPath();
|
|
// do not force inclusion when using completion
|
|
switch (displayValue) {
|
|
case null:
|
|
case "usage":
|
|
case _:
|
|
return;
|
|
}
|
|
// normalize class path
|
|
for (i in 0...classPaths.length) {
|
|
var cp = StringTools.replace(classPaths[i], "\\", "/");
|
|
if (StringTools.endsWith(cp, "/"))
|
|
cp = cp.substr(0, -1);
|
|
if (cp == "")
|
|
cp = ".";
|
|
classPaths[i] = cp;
|
|
}
|
|
}
|
|
var prefix = pack == '' ? '' : pack + '.';
|
|
var found = false;
|
|
for (cp in classPaths) {
|
|
var path = pack == '' ? cp : cp + "/" + pack.split(".").join("/");
|
|
if (!sys.FileSystem.exists(path) || !sys.FileSystem.isDirectory(path))
|
|
continue;
|
|
found = true;
|
|
for (file in sys.FileSystem.readDirectory(path)) {
|
|
if (StringTools.endsWith(file, ".hx") && file.substr(0, file.length - 3).indexOf(".") < 0) {
|
|
if( file == "import.hx" ) continue;
|
|
var cl = prefix + file.substr(0, file.length - 3);
|
|
if (skip(cl))
|
|
continue;
|
|
Context.getModule(cl);
|
|
} else if (rec && sys.FileSystem.isDirectory(path + "/" + file) && !skip(prefix + file))
|
|
include(prefix + file, true, ignore, classPaths);
|
|
}
|
|
}
|
|
if (strict && !found)
|
|
Context.error('Package "$pack" was not found in any of class paths', Context.currentPos());
|
|
}
|
|
|
|
/**
|
|
Exclude a class or an enum without changing it to `@:nativeGen`.
|
|
**/
|
|
static function excludeBaseType(baseType:Type.BaseType):Void {
|
|
if (!baseType.isExtern) {
|
|
var meta = baseType.meta;
|
|
if (!meta.has(":nativeGen")) {
|
|
meta.add(":hxGen", [], baseType.pos);
|
|
}
|
|
baseType.exclude();
|
|
}
|
|
}
|
|
|
|
/**
|
|
Exclude a specific class, enum, or all classes and enums in a
|
|
package from being generated. Excluded types become `extern`.
|
|
|
|
@param pack The package dot-path as String. Use `''` to exclude the root package.
|
|
@param rec If true, recursively excludes all sub-packages.
|
|
**/
|
|
public static function exclude(pack:String, ?rec = true) {
|
|
Context.onGenerate(function(types) {
|
|
for (t in types) {
|
|
var b:Type.BaseType, name;
|
|
switch (t) {
|
|
case TInst(c, _):
|
|
name = c.toString();
|
|
b = c.get();
|
|
case TEnum(e, _):
|
|
name = e.toString();
|
|
b = e.get();
|
|
default:
|
|
continue;
|
|
}
|
|
var p = b.pack.join(".");
|
|
if ((p == pack || name == pack) || (rec && StringTools.startsWith(p, pack + ".")))
|
|
excludeBaseType(b);
|
|
}
|
|
}, false);
|
|
}
|
|
|
|
/**
|
|
Exclude classes and enums listed in an extern file (one per line) from being generated.
|
|
**/
|
|
public static function excludeFile(fileName:String) {
|
|
fileName = Context.resolvePath(fileName);
|
|
var f = sys.io.File.read(fileName, true);
|
|
var classes = new haxe.ds.StringMap();
|
|
try {
|
|
while (true) {
|
|
var l = StringTools.trim(f.readLine());
|
|
if (l == "" || !~/[A-Za-z0-9._]/.match(l))
|
|
continue;
|
|
classes.set(l, true);
|
|
}
|
|
} catch (e:haxe.io.Eof) {}
|
|
Context.onGenerate(function(types) {
|
|
for (t in types) {
|
|
switch (t) {
|
|
case TInst(c, _):
|
|
if (classes.exists(c.toString()))
|
|
excludeBaseType(c.get());
|
|
case TEnum(e, _):
|
|
if (classes.exists(e.toString()))
|
|
excludeBaseType(e.get());
|
|
default:
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
Load a type patch file that can modify the field types within declared classes and enums.
|
|
**/
|
|
public static function patchTypes(file:String):Void {
|
|
var file = Context.resolvePath(file);
|
|
var f = sys.io.File.read(file, true);
|
|
try {
|
|
while (true) {
|
|
var r = StringTools.trim(f.readLine());
|
|
if (r == "" || r.substr(0, 2) == "//")
|
|
continue;
|
|
if (StringTools.endsWith(r, ";"))
|
|
r = r.substr(0, -1);
|
|
if (r.charAt(0) == "-") {
|
|
r = r.substr(1);
|
|
var isStatic = StringTools.startsWith(r, "static ");
|
|
if (isStatic)
|
|
r = r.substr(7);
|
|
var p = r.split(".");
|
|
var field = p.pop();
|
|
removeField(p.join("."), field, isStatic);
|
|
continue;
|
|
}
|
|
if (r.charAt(0) == "@") {
|
|
var rp = r.split(" ");
|
|
var type = rp.pop();
|
|
var isStatic = rp[rp.length - 1] == "static";
|
|
if (isStatic)
|
|
rp.pop();
|
|
var meta = rp.join(" ");
|
|
var p = type.split(".");
|
|
var field = if (p.length > 1 && p[p.length - 2].charAt(0) >= "a") null else p.pop();
|
|
addMetadata(meta, p.join("."), field, isStatic);
|
|
continue;
|
|
}
|
|
if (StringTools.startsWith(r, "enum ")) {
|
|
define("enumAbstract:" + r.substr(5));
|
|
continue;
|
|
}
|
|
var rp = r.split(" : ");
|
|
if (rp.length > 1) {
|
|
r = rp.shift();
|
|
var isStatic = StringTools.startsWith(r, "static ");
|
|
if (isStatic)
|
|
r = r.substr(7);
|
|
var p = r.split(".");
|
|
var field = p.pop();
|
|
setFieldType(p.join("."), field, rp.join(" : "), isStatic);
|
|
continue;
|
|
}
|
|
throw "Invalid type patch " + r;
|
|
}
|
|
} catch (e:haxe.io.Eof) {}
|
|
}
|
|
|
|
/**
|
|
Marks types or packages to be kept by DCE.
|
|
|
|
This also extends to the sub-types of resolved modules.
|
|
|
|
In order to include module sub-types directly, their full dot path
|
|
including the containing module has to be used
|
|
(e.g. `msignal.Signal.Signal0`).
|
|
|
|
This operation has no effect if the type has already been loaded, e.g.
|
|
through `Context.getType`.
|
|
|
|
@param path A package, module or sub-type dot path to keep.
|
|
@param paths An Array of package, module or sub-type dot paths to keep.
|
|
@param recursive If true, recurses into sub-packages for package paths.
|
|
**/
|
|
public static function keep(?path:String, ?paths:Array<String>, ?recursive:Bool = true) {
|
|
if (null == paths)
|
|
paths = [];
|
|
if (null != path)
|
|
paths.push(path);
|
|
for (path in paths) {
|
|
addGlobalMetadata(path, "@:keep", recursive, true, true);
|
|
}
|
|
}
|
|
|
|
/**
|
|
Enables null safety for a type or a package.
|
|
|
|
@param path A package, module or sub-type dot path to enable null safety for.
|
|
@param recursive If true, recurses into sub-packages for package paths.
|
|
**/
|
|
public static function nullSafety(path:String, mode:NullSafetyMode = Loose, recursive:Bool = true) {
|
|
addGlobalMetadata(path, '@:nullSafety($mode)', recursive);
|
|
}
|
|
|
|
/**
|
|
Adds metadata `meta` to all types (if `toTypes = true`) or fields (if
|
|
`toFields = true`) whose dot-path matches `pathFilter`.
|
|
|
|
If `recursive` is true a dot-path is considered matched if it starts
|
|
with `pathFilter`. This automatically applies to path filters of
|
|
packages. Otherwise an exact match is required.
|
|
|
|
If `pathFilter` is the empty String `""` it matches everything (if
|
|
`recursive = true`) or only top-level types (if `recursive = false`).
|
|
|
|
This operation has no effect if the type has already been loaded, e.g.
|
|
through `Context.getType`.
|
|
**/
|
|
public static function addGlobalMetadata(pathFilter:String, meta:String, ?recursive:Bool = true, ?toTypes:Bool = true, ?toFields:Bool = false) {
|
|
#if (neko || eval)
|
|
load("add_global_metadata_impl", 5)(pathFilter, meta, recursive, toTypes, toFields);
|
|
#else
|
|
addGlobalMetadataImpl(pathFilter, meta, recursive, toTypes, toFields);
|
|
#end
|
|
}
|
|
|
|
/**
|
|
Change the default JS output by using a custom generator callback
|
|
**/
|
|
public static function setCustomJSGenerator(callb:JSGenApi->Void) {
|
|
#if (neko || eval)
|
|
load("set_custom_js_generator", 1)(callb);
|
|
#end
|
|
}
|
|
|
|
#if (neko || eval)
|
|
static inline function load(f, nargs):Dynamic {
|
|
return @:privateAccess Context.load(f, nargs);
|
|
}
|
|
#end
|
|
|
|
/**
|
|
Clears cached results of file lookups
|
|
**/
|
|
public static function flushDiskCache() {
|
|
#if (neko || eval)
|
|
load("flush_disk_cache", 0)();
|
|
#end
|
|
}
|
|
|
|
#end
|
|
|
|
#if (js || lua || macro)
|
|
/**
|
|
Embed a JavaScript or Lua file at compile time (can be called by `--macro` or within an `__init__` method).
|
|
**/
|
|
public static #if !macro macro #end function includeFile(file:String, position:IncludePosition = Top) {
|
|
return switch ((position : String).toLowerCase()) {
|
|
case Inline:
|
|
if (Context.getLocalModule() == "")
|
|
Context.error("Cannot use inline mode when includeFile is called by `--macro`", Context.currentPos());
|
|
|
|
var f = try sys.io.File.getContent(Context.resolvePath(file)) catch (e:Dynamic) Context.error(Std.string(e), Context.currentPos());
|
|
var p = Context.currentPos();
|
|
if(Context.defined("js")) {
|
|
macro @:pos(p) js.Syntax.plainCode($v{f});
|
|
} else {
|
|
macro @:pos(p) untyped __lua__($v{f});
|
|
}
|
|
case Top | Closure:
|
|
@:privateAccess Context.includeFile(file, position);
|
|
macro {};
|
|
case _:
|
|
Context.error("unknown includeFile position: " + position, Context.currentPos());
|
|
}
|
|
}
|
|
#end
|
|
}
|
|
|
|
enum abstract IncludePosition(String) from String to String {
|
|
/**
|
|
Prepend the file content to the output file.
|
|
**/
|
|
var Top = "top";
|
|
|
|
/**
|
|
Prepend the file content to the body of the top-level closure.
|
|
|
|
Since the closure is in strict-mode, there may be run-time error if the input is not strict-mode-compatible.
|
|
**/
|
|
var Closure = "closure";
|
|
|
|
/**
|
|
Directly inject the file content at the call site.
|
|
**/
|
|
var Inline = "inline";
|
|
}
|
|
|
|
enum abstract NullSafetyMode(String) to String {
|
|
/**
|
|
Disable null safety.
|
|
**/
|
|
var Off;
|
|
|
|
/**
|
|
Loose safety.
|
|
If an expression is checked `!= null`, then it's considered safe even if it could be modified after the check.
|
|
E.g.
|
|
```haxe
|
|
function example(o:{field:Null<String>}) {
|
|
if(o.field != null) {
|
|
mutate(o);
|
|
var notNullable:String = o.field; //no error
|
|
}
|
|
}
|
|
|
|
function mutate(o:{field:Null<String>}) {
|
|
o.field = null;
|
|
}
|
|
```
|
|
**/
|
|
var Loose;
|
|
|
|
/**
|
|
Full scale null safety.
|
|
If a field is checked `!= null` it stays safe until a call is made or any field of any object is reassigned,
|
|
because that could potentially alter an object of the checked field.
|
|
E.g.
|
|
```haxe
|
|
function example(o:{field:Null<String>}, b:{o:{field:Null<String>}}) {
|
|
if(o.field != null) {
|
|
var notNullable:String = o.field; //no error
|
|
someCall();
|
|
var notNullable:String = o.field; // Error!
|
|
}
|
|
if(o.field != null) {
|
|
var notNullable:String = o.field; //no error
|
|
b.o = {field:null};
|
|
var notNullable:String = o.field; // Error!
|
|
}
|
|
}
|
|
```
|
|
**/
|
|
var Strict;
|
|
|
|
/**
|
|
Full scale null safety for a multi-threaded environment.
|
|
With this mode checking a field `!= null` does not make it safe, because it could be changed from another thread
|
|
at the same time or immediately after the check.
|
|
The only nullable thing could be safe are local variables.
|
|
**/
|
|
var StrictThreaded;
|
|
}
|