Evil Haxe is a superset of Haxe that is created to add as many modern and cool features the language can possibly contain without completely breaking compatibility with vanilla Haxe. It was summoned from an alternative universe using a demonic ritual, and it continues to gander at your soul from the darkest depths of our world.
In all seriousness, this is a fork of Haxe created primarily for personal use. The provided features are extremely opinionated, and they are only added due to the (near) impossibility of them being implemented into the official Haxe compiler because of their redundancy, absurdity, or explicit rejection.
Also, please note I have no experience in OCaml outside of modifying the Haxe compiler, so there are no guarentees I know what the hell I'm doing... just in case you actually look at the source code.
To learn more about normal Haxe, visit:
Official Haxe Github
Official Haxe Website
Official Haxe Download
- Download like you would with any version of Haxe from Releases or build it yourself.
- Add it to your PC's "PATH" if necessary.
- And/or if you're using Visual Studio Code add
"haxe.executable"
to yoursettings.json
with the location of Evil Haxe'shaxe.exe
as the value.
Feature | Description |
---|---|
Tuples | Platform-optimal tuples using standard parentheses syntax |
Named Destructuring | Unpack named fields into new variables |
Ordered Destructuring | Unpack ordered fields from Array s, enum s, or special class es |
with Feature |
Alias expression or fields for block |
Trailing Block Arguments | Pretty syntax for passing block expression as final argument |
Object Initializers | Initialize object fields on new expression |
Auto-Trace "All Alone" Strings | Auto-trace all alone strings |
as Operator |
Cast objects using standard as operator |
unless Expression |
if statements for false expressions |
Modifier if and unless |
Suffix if statements for expressions |
Shorthand Nullable Types | Add a ? to the end of a type to wrap in Null<..> |
Shorthand Array Types | Add a [] to the end of a type to wrap in Array<..> |
if /while /for No Parentheses |
Parentheses no longer required for common-use statements |
@:finalAccess Meta |
Allow assigning to final variables |
const Keyword |
An alternative to the final keyword |
struct Keyword |
An alternative to the class keyword that adds the @:struct meta |
fn Keyword |
An alternative to the function keyword |
Based on C#'s tuples, these are like Haxe's anonymous structures, but with better performance on static platforms. Specifically, Tuples on HashLink, C, C++ (TODO), and C# are value types, so they work well as temporary values and return types and do not cause performance issues with GC. On all platforms, Tuples have more performant field-access, as they are essentially classes with the minimal fields required to store the necessary data. On the other hand, Tuples do not provide any dynamic-access or reflection capabilities.
// create tuple using mixed types using parentheses
var myTuple = (123, "Hello", true);
// access using itemX
trace(myTuple.item1); // 123
trace(myTuple.item2); // "Hello"
// describe tuple type using multiple types in parentheses (order matters)
function getTuple(): (String, Int) {
return ("Dolphins", 300);
}
// tuples have built-in == and != operators
if(getTuple() == ("Dolphins", 300)) {
trace("There are 300 Dolphins");
}
// tuples have toString() as well
trace((1, 2, 3, "four")); // (1, 2, 3, "four")
Based on JavaScript's object destructuring, multiple variables can be initialized from an object with properties or fields. The names of the variables will be the field names that are retrieved from the object.
var { length } = "abcd";
trace(length); // 4
// ---
class GameData {
public var level: Int;
public var coins: Int;
public var time: Float;
public function new() {
level = 1; coins = 0; time = 100.0;
}
}
final { level, time } = new GameData();
trace(level, time); // 1, 100.0
Based on Kotlin's destructuring, multiple variables can be initialized from an instance of Array
, a TupleX
, an enum
, a class with componentX()
functions, or an abstract with array-access. The order of the identifiers dictactes the value they are assigned; empty identifiers can be used to skip unwanted values.
/** array-access **/
var (first, _, third) = [for(i in 1...10) i];
trace(first, third); // 1, 3
/** tuple **/
var (_, str) = (123, "Hello World!");
trace(str); // "Hello World!"
/** enum **/
enum Suit {
Fancy(buttons: Int, size: Int);
Simple;
}
var (_, s) = Fancy(12, 24);
trace(s); // 24
/** class **/
class Animal {
public var name: String;
public var legs: Int;
public var arms: Int;
public function new(n: String, l: Int, a: Int) {
name = n; legs = l; arms = a;
}
public function component1() return name;
public function component2() return legs;
public function component3() return arms;
}
var (aniName, aniLegs, aniArms) = new Animal("Dog", 4, 0);
trace(aniName, aniLegs, aniArms); // "Dog", 4, 0
Syntax sugar for aliasing an expression's resulting value or the value's fields to a new scope.
var point = new Point(10, 20);
with p as point {
trace(p.x, p.y); // 10, 20
}
with x, y from point {
trace(x, y); // 10, 20
}
Kotlin's trailing lambdas provide users with the ability to use a nice syntax for passing a lambda as the final argument to a function. This feature works similarily, but instead of passing lambda functions, it passes a block scope (which executes and passes the final value at call-time). This works well with macro functions that can take the block scope as an Expr
and modify it.
// https://github.com/RobertBorghese/Haxe-ExtraFeatures/
using ExtraFeatures;
// ---
var player = new Player();
Math.floor(player.x / 100.0).with {
trace(it);
}
var meters = player.getDistance().with(dist) {
recordDistance(dist);
triggerEffect() if dist > 1000;
convertDistanceToMeters(dist); // convert to meters and return
}
Based on C#'s object initializers, this feature allows for a simple syntax to initialize multiple fields upon an object's creation.
var c = new Color() {
name = "blue";
alpha = 0.5;
}
trace(c.name == "blue", c.alpha == 0.5); // true, true
Based on HolyC's Auto-Print, this feature transforms any standalone Strings (that do not have any impact on the execution/value of its parent expression) into trace
statements.
"Hello World"; // equivalent to: trace("Hello World");
// prints "one" and "two", but not "three" since it gets stored in "result"
var result = {
"one";
"two";
"three";
};
// print other values using either:
var num = 123;
'$num'; // string interpolation
"" + num; // or concatenation to empty-string
Based on C#'s as
operator. Converts an expression like this: var as Type
to cast(var, Type)
.
var float: Float = 35.0;
var int = float as Int;
Similar to the as
operator, but transforms into the compile-time type-check expression: var isa Type
to (var : Type)
.
// inform compiler return-type is Transform
var comp = gameObject.GetComponent() isa Transform;
Based on Ruby's unless
, this keyword can be used just like if
, but the provided expression must be false to execute the block.
const number = 123;
unless number == 0 {
trace("This will print.");
} else if number == 123 {
trace("This will not print.");
}
Based on Ruby's modifier conditions, conditions can be appended to any expression that doesn't end with a }
. This is the equivalent of wrapping the expression with an if
or unless
condition.
for(i in 0...inputNumber) {
break if i > 10;
continue if i % 2 == 1;
evenNumbers.push(i);
}
player.update() if player.canUpdate();
player.draw() unless player.invisible();
Based on Kotlin's nullable type syntax, adding a ?
to the end of a type is the equavalent of surronding with Null<...>
.
@:nullSafety(Strict) {
var test: Int? = Math.random() < 0.5 ? 100 : null;
}
Based on Java's array type syntax, adding []
to the end of a type is the equavalent of surronding with Array<...>
.
@:nullSafety(Strict) {
// Array<Int>
var optA: Int[] = [1, 2, 3, 5, 8, 13];
// Array<Null<Int>>
var optB: Int?[] = [123, null, null, 321, null];
// Null<Array<Int>>
var optC: Int[]? = null;
}
Based on Rust's and Swift's conditional/flow control, the parentheses surronding the inputs for flow control statements can be omitted.
// valid
if Math.random() < 0.5 {
trace("50% chance of seeing this");
}
// also valid
if Math.random() < 0.5 trace("50% chance of seeing this");
// for
for i in arr {
trace(i);
}
Similar to @:privateAccess
, this metadata allows for final
variables to be assigned.
final str = "Hello";
@:finalAccess {
str = "Goodbye"; // valid
}
Based on JavaScript's constant variable keyword. It works exactly like Haxe's final
keyword.
const str = "Hello";
str = "Goodbye"; // error: Cannot assign to final
Based on C#'s struct keyword. Automatically adds @:struct
metadata to a class declaration if used instead of the class
keyword.
// equivalent to:
// @:struct class MyStruct
struct MyStruct {
public var data = 123;
public function new(d: Int) { data = d; }
}
Based on Rust's function keyword. Can be used as a replacement for function
in almost all cases.
fn test() {
trace("do thing");
}
fn main() {
// "fn" can still be used as variable name for compatibility...
var fn = () -> {
trace("i am smol");
};
// ...as a result, it can't be used to create functions in expressions.
// Use lambdas instead.
var fn2 = fn() { // invalid
trace("so am i");
};
}