-
Notifications
You must be signed in to change notification settings - Fork 0
Statements and Expressions
-
super(...)initializer lists. -
.-vs-->selection via type inference (including inherited fields). - Anonymous-struct-to-temporary expansion (typed, untyped, nested).
- Array / map / object literals.
Array→std::vector and Map→std::map with their container ops:
-
Array —
push/insert/pop/shift/unshift/length/indexOf/lastIndexOf/contains/remove/reverse/concat/slice/copy/join/filter/sort(the last two take a lambda:filterbuilds a kept-elements vector,sortis an in-place insertion sort driven by the comparator). -
Map —
get/exists/set/remove/keys(each an inline loop/expression, no<algorithm>dependency).
Containers are value types in the generated C++ — Haxe's are shared by reference, and mutating an
Array/Map parameter is linted at the Haxe line (see Container Semantics
for the full divergence and the idiomatic patterns).
Haxe's auto-extending array writes — a[i] = v past the end grows the vector first (an inline
resize), matching Haxe rather than letting C++ operator[] run off the end.
A typedef alias of a container — typedef Tileset = Array<Tile>; typedef Tilesets = Array<Tileset>;
— maps as a name to its emitted typedef std::vector<…>, and every container operation resolves
through the alias to that real container head: new Tilesets() value-constructs (it is never treated
as an owned heap pointer to free), .push→push_back, .length→.size(), arr[i] indexing, and
iteration / comprehensions all work on the aliased name exactly as on the underlying container.
A Map<K,V> field tagged @orderedMap is stored as two insertion-ordered parallel vectors instead
of a std::map — a VC6-safe ordered map; see Container Semantics. An anonymous
struct as a container element (Array<{x:Int}>) is a hard error (it would become std::vector<void*>) —
give the struct a typedef and use that named type.
for over a range, an array, an anonymous array literal (for (i in [1,2,3])), a map
(for (v in m) over values, for (k => v in m) over key/value pairs, via a std::map iterator), or a
custom Iterator / Iterable — any value exposing hasNext():Bool + next():T, or an
iterator() returning one, lowers to a while (it.hasNext()) { T x = it.next(); … } loop (./->
chosen by whether the iterator is a value or a reference type). When iterator() hands back a heap
(reference-type) iterator, the loop owns and deletes it — including on an early return out of
the body. The same forms drive array & map comprehensions ([for (x in e) …]).
Iteration that cannot be lowered is a hard error rather than a guess (see Diagnostics):
a value that is none of the above, a custom iterator whose protocol methods are only inherited from
a base class (they must be on the iterated type itself), and key => value over a value-only custom
iterator each fail loudly with a specific message. (A custom iterator reached through a typedef
alias does work — an alias is transparent, so it iterates exactly as its target.)
-
Module-level functions (
function f(...) {...}→ a namespace free function, public ones declared in the header,privateonesstaticin the.cpp). In--header-onlymode they are emittedinlineinto the amalgamated header instead (there is no.cpp); see Building & Usage. They are callable bare (f(...)) or — as Haxe allows, the module name doubling as the primary class name — through that name (Module.f(...)); either way the call is the free functionf(...)(namespace-qualified across modules), never a class-qualifiedModule::f. A same-named static member of the primary class still resolves to it (Module::f). - Closures inlined into a loop for
Array.map/filter/sort, or lowered to free functions for a top-levelfinalbinding (an arrow param's type may be left off and taken from the binding's function-type annotation —Cross:(Vec, Vec) -> Float = (a, b) -> …typesa/basVec).
String methods (charAt/charCodeAt/indexOf/lastIndexOf/substr/substring/toUpperCase/
toLowerCase/split), String.fromCharCode, StringBuf (an add/addChar/toString accumulator
over std::string), and the StringTools statics (replace/trim/startsWith/endsWith/hex) —
all mapped to std::string expressions; string interpolation and + concatenation (built as a
std::string — text appended directly and numeric operands formatted into type-bounded buffers, so no
value-guessed buffer can overflow; interpolation also supports the $ident shorthand).
- The
??null-coalesce and NULL-guarded?.. -
cast(C-style cast forcast(expr, T), passthrough forcast expr). - The
(expr : Type)type ascription (a compile-time hint that drives inference, e.g.([] : Array<Int>)).
On an integer/enum subject → a C++ switch; on a String subject → an if/else if chain, since
C++ case labels must be integral. With constant patterns — literals, negated numeric literals, and
enum constants (bare or EnumType.Member-qualified), with the wildcard case _: lowering to
default:, comma alternatives (case A, B:), and the or-pattern (case 1 | 2: — in pattern position
| means or, exactly as in Haxe, and lowers to two case labels).
Destructuring of parameterized enums — case Add(a, b): switches on the tag and binds one typed
local per non-_ capture from the variant's payload fields, with a side-effecting subject hoisted so
it evaluates once (a destructuring pattern must be its case's only alternative, payload positions take
only plain captures or _, and a bare capture pattern case x: stays flagged rather than emitted as
a broken label).
Haxe's break semantics are preserved: Haxe switch has no break of its own, so a break in a
case body exits the enclosing loop — inside a generated C++ switch it routes through a hoisted flag
checked after the switch (f = true; break; … if (f) break;), chaining through nested switches,
while a break in a loop nested within a case body stays bound to that inner loop (continue needs
no help — C++ gets it right natively) — including a switch used in value position
(var x = switch (e) { … }), which desugars to a hidden temporary assigned inside a statement
switch.
-
trace(...)(with--no-tracesto strip it). -
throw/try/catchexception handling (a thrownStringis coerced tostd::string; a typedcatch (e:T)maps the exception type; an untyped/Dynamiccatch becomes the non-bindingcatch (...)— so it may catch but cannot use the value: referencing the caught name there is a hard error, since C++catch (...)binds nothing; see Memory Ownership for unwind behaviour). - The
Math/Std/Sysintrinsics (Std.int/Std.string/Std.parseInt/Std.parseFloat/Std.random→ inline(int)/sprintf/strtol/atof/rand()).
Hatchet is licensed under the MIT License — see LICENSE. (c) 2026 Andrew Grant Lind
Getting Started
Language Support
- Declarations
- Value Types & Abstracts
- Members & Access
- Statements & Expressions
- Types & Nullability
- Conditional Compilation
- Memory Ownership
Semantics & Interop
Internals