Polyglot Cross-Platform Library using Haxe Language & Cross-compiler
Tower of Babel - Marten van Valckenborch - via Wikimedia-commons

Polyglot Cross-Platform Library using Haxe Language & Cross-compiler

This is an advanced level tutorial. In this (rather long) tutorial, we will discuss the use of a cross-platform compiler technology to provide an alternative solution to a very-well known system architecture concern.

The technologies discussed in here: Haxe. We will discuss the various Haxe compilation targets such C++, Java, NodeJS, Python and PHP along with development tools such as VS Code, Android Studio and Xcode so it is advisable some knowledge on those, although not strictly required. We specially will go over Haxe's development workflow (including external libraries, setting up unit testing, IDE integration and some other goodies you may find useful). As as extra, I also included a build step for targeting C# (either in .NET or Mono) but I did not include an implementation step mainly for timing reasons.

Fictional scenario. You are assigned the task to deploy to production the hottest new 100% best deal algorithm that your company just produced, regarded as the game changer by everyone. It must be available to the company's mobile apps (native iOS and Android), the website, and also the internal services in the company (different micro-services built on Java, Python, PHP and NodeJS).

The problem is that business wants two things:

  1. The output of the algorithm must be validated on all ends.
  2. It must work offline (no internet connectivity).

The first thought that comes to mind (and the most obvious one) is to build some backend service to provide the algorithm's I/O exchange and expose the result somehow. However, while this would work great for business need #1, it would violate business need #2. We definitely need for it to work offline/isolated mode.

So after some thought, the team decided that it'd be better if we develop the algorithm in every target platform. The problem now is that the code segmentation is high; Java/Kotlin for the Android app, Swift/Obj-C for the iOS app, NodeJS/Javascript for our web app, and Java/Php for our validation micro-services. Clearly, there will be implementation problems, incompatible logic, and even inherent platform-specific feature handling mismatches (e.g string to decimal rounding differences!). And not to mention human errors...

Finally, the team came to the decision of creating a cross-platform library that will have a single source code but is able to compile to several platforms. After some research, the team decided to go with Haxe.

"In a real-world scenario, this approach has other business problems to consider. For instance, our super-secret algorithm integrity. How can we make sure that our algorithm code is steal-free from decompiling (or source code view in web)? Another issue is release management. If the algorithm changes, will we need to deploy to all platforms in sync? On the testing front, How to make sure to run valid target-specific unit-tests (and possibly integration tests)?"

Project

In the following image you can find what the solution entails.

No alt text provided for this image

Introducing Haxe

"Haxe is an open source high-level strictly-typed programming language with a fast optimizing cross-compiler." - https://haxe.org/

In summary, Haxe is a fully OOP statically typed language (resembles a lot to Java/C# in my opinion) that can either be compiled to different targets or can run in interpreted mode by itself. It is used to develop cross-platform solutions such as web/desktop applications, command line tools, APIs, etc.

I will not go into much detail on Haxe basics, as they have a pretty solid documentation, but I will highlight the important pieces for our tutorial.

OK great, the first step is to make sure you install haxe. The installation process is a very straightforward task on most platforms. Second, make sure you install haxelib as well, this is the haxe package manager (for managing libraries).

You may want to also install Android Studio for running the Android project. On the iOS front, you will need a Mac and latest Xcode. If not on a Mac, feel free to skip the iOS app. You will still be able to build the library, you just won't be able to use it. Or you could also build a cross-platform app using react-native, my all time favorite (you also will have a javascript port of the library anyways, so you can safely use it as well).

As for the Haxe code development and debugging, there are several editors available. I use visual studio code with the haxe-extension installed.

We'll build a very simple cross-platform library that encapsulates an algorithm that calculates a best deal price based on two (fictional) facts:

  1. Time of the day.
  2. Number of past purchases.

Conditions of the best-deal campaign:

  1. If no past purchases => No best deal.
  2. Time Of Day => If lunch time then get best deal. It only applies for current day.

All right, so let's dive into the code right away.

Step 1 - Project Setup

It's a good time for cloning the repository: https://github.com/danielwind/haxelib-tutorial. Make sure you have Haxe installed. My Haxe version is 4.1.3 running on a Mac (Catalina 10.15.4)

The project structure should look like the following:

No alt text provided for this image









Let's quickly go over the project structure and understand what each component is about:

.unittest => Hidden directory holding the unit testing framework configuration for running the VS Code test runner (Automatically generated)

.vscode => Hidden directory holding the VS code haxe setup (Automatically generated)

bin => Directory that contains the distribution binary cross-platform code. Once the Haxe code is built, it will automatically generate all the platform libraries for us to use.

scripts => Directory holding a set of scripts to illustrate how to use the generated libraries on each platform. This is where we test the generated libraries we will create!

tests => Directory holding a set of Haxe unit tests. These are Haxe specific.

.gitignore => File for ignoring unneeded files on the git repository.

BDAEngine.hx => The most important class that holds all tutorial's logic. Note Haxe classes extension is hx.

build.hxml => This file is the main haxe build file for this project. here we define how we want to build the project and its output. Note config files have .hxml as extension.

dependencies.hxml => This file is the haxe dependencies list for our project. Here we define a set of libraries that we will use internally (haxelib).

test.hxml => This file is the test environment build file. Here we define which is the main entry point for the tests for running unit testing.

OK. Now, we are going to install all the dependencies we need for running the project. Feel free to skip those platforms you are not interested in. The libraries we need to install are included in dependencies.hxml:

-lib hxjava
-lib hxcpp
-lib hxcs
-lib utest
-lib json2object

It is preferred if you install these libraries manually in the global scope since they have OS dependencies and may fail. NOTE: C# is not being used in this tutorial and can be safely skipped.

Java

// Support library for the Java backend of the Haxe compiler
// Docs => https://lib.haxe.org/p/hxjava
haxelib install hxjava

C++

// Hxcpp is the runtime support for the C++ backend of the Haxe compiler.
// Docs => https://lib.haxe.org/p/hxcpp
haxelib install hxcpp

C#

// Support library for the C# backend of the Haxe compiler
// Docs => https://lib.haxe.org/p/hxcs
// NOT USED IN THIS TUTORIAL - Just for reference
haxelib install hxcs

Utest (Unit Testing Library)

// Docs =>  https://github.com/haxe-utest/utest
haxelib install utest

Json2Object

// Docs =>  https://github.com/elnabo/json2object/
haxelib install json2object

OK so once you ran those commands in the terminal and installed all these tools, we are ready to roll!

Step 2 - Code Analysis

Let's head onto the code. The following is the Best Deal Algorithm class (BDAEngine.hx) Full comments below the following snippet

@:expose  // <- makes the class reachable from plain JavaScript
@:keep    // <- avoids accidental removal by dead code elimination
public class BDAEngine {
 
  private static var VALID_HOURS = [12, 13];


  private static var APPLICABLE_ITEMS = "[{\"id\":1001,\"name\":\"Hamburger\", \"price\": 9.99 },{\"id\":1002,\"name\":\"Taco\", \"price\": 5.99},{\"id\":1003,\"name\":\"Burrito\", \"price\": 8.99},{\"id\":1004,\"name\":\"Pizza\", \"price\": 2.99},{\"id\":1005,\"name\":\"Falafel\", \"price\": 3.99},{\"id\":1006,\"name\":\"Gyro\", \"price\": 4.99}]";


  public static function main():Void {
    trace("BDAEngine init!");
  }


  public static function getBestDeal(itemId:Int, price:Float, previousPurchases:Int):Float{
      try{
        if (!validateInput(itemId, price, previousPurchases)) { 
          trace("BDAEngine - getBestDeal | Validation failed. No best deal can be performed.");
          return price; 
        }
        return process(itemId, price, previousPurchases);


      }catch(e:haxe.Exception){
        //in case of any processing error, just return the original price
        trace('BDAEngine - getBestDeal | Error while attempting to process best deal. Ignoring. Error: $e');
        return price;
      }
  }


  private static function process(itemId:Int, price:Float, purchases:Int):Float {
    //---------------------------------------------------------
    //1. Check if item id is entitled for best deal
    //2. Check if time is noon (normal lunch time: 12pm - 1pm)
    //3. If lunch, then give best deal!
    //---------------------------------------------------------
    if(checkTimeIsLunch()) {
      var item = checkItemIsEligible(itemId);
      if(item != null){
        return applyBestDeal(item, purchases);
      }
    }
    return price;
  }


  public static function applyBestDeal(item:Item, purchases:Int):Float {
    return item.price - (0.5 * purchases);
  }


  public static function checkItemIsEligible(itemId:Int):Item {
    var items:Array<Item> = haxe.Json.parse(APPLICABLE_ITEMS);
    var item:Array<Item> = items.filter(item -> item.id == itemId);
    if(item.length > 0) { return item[0]; }
    return null;
  }


  public static function checkTimeIsLunch(?date:Null<Date>):Bool {
    var today = Date.now();
    var time = today.getHours();
    if(date != null) { time = date.getHours();}
    if( time >= VALID_HOURS[0] && time < VALID_HOURS[1]){
      return true;
    }
    return false;
  }


  private static function validateInput(itemId:Int, price:Float, previousPurchases:Int):Bool{
      if (previousPurchases <= 0) { return false; }
      if(itemId <= 0){ return false; }
      if(price <= 0) {return false; }
      return true;
  }
}


//type definitions
typedef Item = {
  var id:Int;
  var name: String;
  var price: Float;
}


Let's depict the code little by little. Starting from the top:

@:expose  // <- makes the class reachable from plain JavaScript
@:keep    // <- avoids accidental removal by dead code elimination
class BDAEngine {
...

Haxe is a fully OOP language. We create a class called called BDAEngine. However, we see two annotations (the @: thing there) on top of it. These are called "metadata" and they are just construct decorators to instruct the compiler (either at build time or at runtime) that we want some special behavior added to that class. The full list can be found here. Here, we are using two of them:

@:expose --> It tells the compiler that we want to include the class in the haxe exports for making the class available in a dynamic language. This metadata annotation is specially useful if you are planning to use the Javascript or Lua generated script.

@:keep --> It tells the compiler that it must keep the class even if it is candidate for Dead Code Elimination (DCE). What in the world is DCE? It is just a compiler feature that removes unused code from the output generated code (works for all platforms). This allows the generated code output to be streamlined and not very big (by removing code that you may not use).

  private static var VALID_HOURS = [12, 13];


  private static var APPLICABLE_ITEMS = "[{\"id\":1001,\"name\":\"Hamburger\", \"price\": 9.99 },{\"id\":1002,\"name\":\"Taco\", \"price\": 5.99},{\"id\":1003,\"name\":\"Burrito\", \"price\": 8.99},{\"id\":1004,\"name\":\"Pizza\", \"price\": 2.99},{\"id\":1005,\"name\":\"Falafel\", \"price\": 3.99},{\"id\":1006,\"name\":\"Gyro\", \"price\": 4.99}]";


We define two static variables to hold information that our algorithm needs. For simplicity sake I have "hardcoded" this information within the class but we could very well get this information by invoking some web-service or external process.

VALID_HOURS --> An array containing two items representing a valid range of hours (from 12 pm to 1 pm in this case).

APPLICABLE_ITEMS --> A json encoded array with the menu items information that apply for the promo.

Of note, we are defining these variables as static for illustration purposes. You can definitely define them as instance variables.

public static function main():Void {
    trace("BDAEngine init!");
    
    //add invocation
    getBestDeal(1001, 9.99, 2);
  
}

This is the main entry point for the library. For Java or C developers this might look very familiar. In essence, this is just the method that gets invoked automatically when the code is run. Now since we are developing a library, and not really an application, this might not be necessary at all. The reason I am including it is for verification purposes when we test the library via the test scripts. Bear with me!

The trace() method helps us to log information to the console. Once we compile the code to other platforms, this gets converted to its appropriate native logger, pretty cool!

public static function getBestDeal(itemId:Int, price:Float, previousPurchases:Int):Float{
      try{
        if (!validateInput(itemId, price, previousPurchases)) { 
          trace("BDAEngine - getBestDeal | Validation failed. No best deal can be performed.");
          return price; 
        }
        return process(itemId, price, previousPurchases);


      }catch(e:haxe.Exception){
        //in case of any processing error, just return the original price
        trace('BDAEngine - getBestDeal | Error while attempting to process best deal. Ignoring. Error: $e');
        return price;
      }
 
}



Here we define the getBestDeal function which accepts three parameters:

itemId --> Integer representing the item id

price --> A floating point number representing the item price

previousPurchases --> Integer representing the number of previous purchases

Of note, Haxe always specify the type for a variable with ":" after the variable name. In addition, the function construct always need to define the type of return. In this case, the function returns a Float type.

All the normal programming language utilities are available in Haxe. In this case, we use a try-catch to detect any exception that may happen during the process of determining the best deal and if there is any, we just return the original price of the item to not to affect the consuming client logic.

We start by trying to validate the input information. This is a utility method in this same class that validates bogus data being passed (negative or zero number of purchases, item ids, prices, etc). It returns true if valid, or false if invalid.

private static function validateInput(itemId:Int, price:Float, previousPurchases:Int):Bool{
      if (previousPurchases <= 0) { return false; }
      if(itemId <= 0){ return false; }
      if(price <= 0) {return false; }
      return true;
}


If valid, we proceed processing the best deal algorithm by invoking the process method, depicted below:

private static function process(itemId:Int, price:Float, purchases:Int):Float {

    if(checkTimeIsLunch()) {
      var item = checkItemIsEligible(itemId);
      if(item != null){
        return applyBestDeal(item, purchases);
      }
    }
    return price;
}


This method checks the following conditions:

1. Check if item id is entitled for best deal

 2. Check if time is noon (normal lunch time: 12pm - 1pm)

 3. If lunch, then give best deal!

If any of the two initial conditions is not met, then we just return the unmodified price of the item (no best deal applied). The way these conditions are evaluated are separated as three distinct functions:

checkTimeIsLunch:

public static function checkTimeIsLunch(?date:Null<Date>):Bool {
    var today = Date.now();
    var time = today.getHours();
    if(date != null) { time = date.getHours();}
    if( time >= VALID_HOURS[0] && time < VALID_HOURS[1]){
      return true;
    }
    return false;
  
}

This function has an optional parameter (in Haxe optional params are marked with ?) representing a date you may want to pass. This is done for future implementations if business decides to check on multiple dates, not only current date (maybe getting the discount for tomorrow's lunch?).

?date: Null<Date>

Why is this Date type wrapped in a Null interface? Well, in Haxe if you want to mark a variable as possibly nullable at some point in your code, you need to wrap it with the Null. The reason why is explained here very well: https://haxe.org/manual/types-nullability.html#define-nullt but in summary, due to the cross-platform nature of this language, this is needed for proper runtime behavior on static and dynamic targets.

OK, so moving on with our code, we then simply check the valid range hours and if the current time is within that range, we simply return true, indicating that it is indeed lunch time.

checkItemIsEligible:

public static function checkItemIsEligible(itemId:Int):Item {
    var items:Array<Item> = haxe.Json.parse(APPLICABLE_ITEMS);
    var item:Array<Item> = items.filter(item -> item.id == itemId);
    if(item.length > 0) { return item[0]; }
    return null;
 
}

This method starts by parsing the JSON data in our APPLICABLE_ITEMS variable. Then we filter the array looking for the specific item by the itemId that was passed. Note that the filter operation returns an Array! so we finally check if the array has something in it, then we finally return the first entry. If not, then we return a null object.

For those Javascript or Ruby devs, there is no equivalent of find() or includes() method for Arrays in Haxe. That is the sole reason I went with the filter approach.

Note that for illustration purposes we are parsing the JSON file every time we check if an item is eligible. In a real implementation, we should cache this value perhaps by exposing it as a static constant.

applyBestDeal

public static function applyBestDeal(item:Item, purchases:Int):Float {
    return item.price - (0.5 * purchases);
}

This method is simply the best deal calculation. We just subtract half of the total purchases to the item original price. This is obviously ultra-simplistic for illustration purposes.

Step 3 - Unit Testing

Any good software developer knows that unit testing is key for quality software. In our case, we are using utest haxe library. There are two important parts: The tests directory and the test.hxml. The first one is just a directory holding our unit tests, and the latter is the haxe build file that will help us run the unit tests.

No alt text provided for this image












In the tests directory you will need a main class that will start the unit test engine (utest) and register all the unit tests (aka test suite) we want to include a single unit test run. In our project, the code for the main test setup is depicted below:

package tests;


import utest.Runner;
import utest.ui.Report;
import tests.ValidationTestCases;
import tests.LunchTimeTestCases;
import tests.EligibleItemTestCases;


class BDAEngineTestsMain {


    static public function main():Void {
        var runner = new Runner();
        runner.addCase(new ValidationTestCases());
        runner.addCase(new LunchTimeTestCases());
        runner.addCase(new EligibleItemTestCases());
        runner.addCase(new BestDealTestCases());
        Report.create(runner);
        runner.run();
    }
}

As you can see, this class exposes a main() method to identify the tests suite point of entry. It starts by creating a utest runner, which acts as the test orchestrator (starts, runs and ends all the unit tests). The then add each test case we want to run. We finally create a report by leveraging the Report class and run the orchestrator. If you want to include more tests, then just add a new case to the runner with your specific test case. Is that simple.

OK great! So now, what does a utest unit test look like?

BestDealTestCases.hx

package tests;


import utest.Assert;
import utest.Test;
import BDAEngine;


class BestDealTestCases extends Test {
    
    var validItem:Item = { "id":1001,"name":"Hamburger", "price": 9.99 };


    function testValidBestDealTwoPurchases() {
        var discountedPrice = BDAEngine.applyBestDeal(validItem, 2);
        Assert.equals(discountedPrice, 8.99);
    }


    function testValidBestDealThreePurchases() {
        var discountedPrice = BDAEngine.applyBestDeal(validItem, 3);
        Assert.equals(discountedPrice, 8.49);
    }


    function testValidBestDealFourPurchases() {
        var discountedPrice = BDAEngine.applyBestDeal(validItem, 4);
        Assert.equals(discountedPrice, 7.99);
    }


    function testValidBestDealZeroPurchases() {
        var discountedPrice = BDAEngine.applyBestDeal(validItem, 0);
        Assert.equals(discountedPrice, 9.99);
    }
}

This class inherits from the utest Test class (required). It inherits a set of methods to do the normal test setup (setup, teardown, init) which are not being used in this class as we just want to test simple stuff. In a more complex project of course you should need to set those up.

Each test case is defined in a single function where we test the specific functionality we want to assess and then we assert the code with our expectation.

For running the unit tests I recommend you install the Haxe Test Explorer vscode extension. This is a view that allows you to run the test suite or even a single test case. However, you will need to specify a test build file (test.hxml) for it to understand how to run the tests and its dependencies.

You can also run the tests from the terminal directly by typing: haxe test.hxml


No alt text provided for this image

test.hxml

What is this HXML file for? Well this is a configuration file that haxe uses for managing build routines and dependency management. In this project we actually have two of them: build.hxml (for building the entire project) and test.hxml (for building the unit tests).

I'll start by showing the test.hxml:

-main tests.BDAEngineTestsMain
-lib utest
--interp

Yup, that simple! The -main flag indicates haxe that our test main entry point is located at the tests package in a class named BDAEngineTestsMain. It MUST have a main() method for it to start running it.

The -lib flag tells Haxe that we want to use an external library. In our case, we use utest, which is the library we selected for helping us on the unit testing front.

Finally, we add the --interp to display results in the console by using internal macro system.

For running it manually, open your terminal and type: haxe test.hxml. It will start running and all the test output will be displayed:

dwind@Daniels-Mac-Pro haxelib-tutorial % haxe test.hxml

utest/ui/text/PrintReport.hx:52: 
assertations: 12
successes: 12
errors: 0
failures: 0
warnings: 0
execution time: 0.002


results: ALL TESTS OK (success: true)
tests.BestDealTestCases
  testValidBestDealFourPurchases: OK .
  testValidBestDealThreePurchases: OK .
  testValidBestDealTwoPurchases: OK .
  testValidBestDealZeroPurchases: OK .
tests.EligibleItemTestCases
  testEligibleItem: OK .
  testIneligibleItem: OK .
tests.LunchTimeTestCases
  testAfterLunch: OK .
  testBeforeLunch: OK .
  testLunchTime: OK .
tests.ValidationTestCases
  testInvalidItemId: OK .
  testInvalidItemPrice: OK .
  
testInvalidPreviousPurchases: OK .



Step 4 - Transpiling/Compiling the project

The build.hxml is the build file that we need in order to build/compile the project. Here is where we define the set of commands that will allow us to use the real power of Haxe: compiling cross-platform binaries that we can reuse in our specific platforms. After running the build, the bin directory will show all the compiled/transpiled targets as shown in this image:

Haxe cross-platform bin directory

Let's start by looking at the entire file and then I'll cover each section:

#-----------------------------------------------------#
# @author: Daniel Wind
# This build file contains multiple target compilation
# To execute this file, run:
# haxe build.hxml
#-----------------------------------------------------#


# Remove all bin files (if any)
-cmd rm -rf bin/*


--next


# Build JS File (NodeJS)
-main BDAEngine
-D js-es=6
-dce full
-js bin/bestdeal.js


--next 


# Build Jar File (Java)
#-java bin/bestdeal.jar
-main BDAEngine
-dce full
--jvm bin/bestdeal.jar


--next 


# Build iOS compatible (ObjC++)
# -D iphoneos  - For targeting the real device
-main BDAEngine
-D static_link
-D iphonesim
-D objc
-D file_extension=mm
-D HXCPP_M64
-cpp bin/bestdeal


--next 


# Build Python file
-main BDAEngine
-dce full
-python bin/bestdeal.py 


--next 


# Build PHP file
-main BDAEngine
-dce full
-php bin/bestdeal.php


--next


# Build C# file
-main BDAEngine
-dce full
-cs  bin/bestdeal.cs


OK, let's dig the code little by little:

# Remove all bin files (if any)
-cmd rm -rf bin/*

Here we just remove all generated files (if the build has been previously run). Note that we are storing all generated binaries in the bin directory by default! You separate each compilation iteration using the next compiler argument:

--next

For a list of all compiler arguments refer to: https://haxe.org/manual/compiler-usage-hxml.html


--------------------------------------------------------------------------------------------------------------

OK now we will have some fun with compiling stuff! Let's being with the iOS compatible compilation!

C++ Compilation (iOS)

The C++ port will allow us to export our code as .a file (this is a static library generated by the archive tool "ar") that can be consumed by any C++ supported platform (in our case, both iOS and Android support it). In this tutorial I will just do it for iOS as I will use the Java compiled package for Android (illustrated below in the java compilation step). So, with that said, let's get hands on:

# Build iOS compatible (Obj-C++)
# -D iphoneos  - For targeting the real device

-main BDAEngine
-dce full
-D static_link
-D iphonesim
-D objc
-D file_extension=mm
-D HXCPP_M64
-cpp bin/bestdeal

Let's dissect these arguments:

-main BDAEngine => This indicates Haxe our main entry point class.

-dce full => This tells Haxe we want to use the full Dead Code Elimination feature

-D static_link => IMPORTANT: This tells haxe we want to build a static library

-D iphonesim => IMPORTANT: we tell haxe we want to build a simulator compatible library. The reason we need to specify this is that iOS has specific architectures for their devices, but the simulator has different ones (obviously, since it runs in your computer rather than in your phone!). For a detailed list, see:

No alt text provided for this image
For targeting the real device, please use the (-D iphoneos) flag instead!

-D objc => This flag allows us to generate Obj-C++ classes (This is, Objective-C classes mixed with C++ code)

-D file_extension=mm => This is added for Xcode support of Obj-C++ classes (which have .mm extension).

-D HXCPP_M64 => We want to force 64-bit compilation (if your computer runs on this architecture). If not, then just do not set this flag or set it to: HXCPP_M32 (32 bits) or just remove it entirely.

**Be aware though that your tests may not run properly/at all on the simulator/phone if your source mode is 32 bits!

 -cpp bin/bestdeal => Finally, we tell Haxe that we want to generate C++ and its output should be located in the bin directory within a bestdeal subdirectory.

All right, so if you run the build.hxml:

haxe build.hxml

you can see that the directory got created:

No alt text provided for this image

Now our task is to import this into an iOS project and verify it works. For this, I created a super simple Swift-based iOS single view project which you can find in scripts/ios directory.

I decided to use Swift instead of Obj-C based project as I want to illustrate how to include this library in a modern source based app project (everyone is using Swift nowadays it seems!). If you have an Obj-C based source project, it is even easier to get it working.
  1. Open the Xcode Project (you can see the xcodeproj file in scripts/ios if you wish to see my implementation)
  2. Then head onto adding the library in: "Build Phases > Link Binary With Libraries" and click on the + sign, a pop-up window will appear. On that window, click at the bottom left dropdown where it says "Add Other..." and select: "Add Files" and look for the libBDAEngine.iphonesim-64.a and select it. It should be added to your project without any issue:
No alt text provided for this image

3. Now under "Build Settings" search for "Library Search Paths" and give it the relative location for the library. In my case it is:

No alt text provided for this image

4. In order to access the Obj-C++ code from Swift, you need to create a "bridge" header file. Let's do that by adding a new Header file into the project by selecting the project in the Project Navigator and right click -> "New File" and select Header. Name it anything you like (I named mine: BestDeal-Binding-Header.h). Once created, remove all of its content and leave it blank!

No alt text provided for this image

5. Let's add the following content to this header file:

No alt text provided for this image

6. Once that is done, let's configure our project to understand and find this new header. You do that by going to Build Settings > Swift Compiler - General and set the "Objective-C Bridging Header" to your local path where your header is located.

No alt text provided for this image

7. Then, in the same Build Settings sections, we need to specify some new linker flags to link the c++ standard library. We do this in the section: Build Settings > Linking > Other Linker Flags and set its value to: "-lstdc++" (no quotes)

No alt text provided for this image

And finally, you can use the Obj-C++ method in your swift code as follows:

No alt text provided for this image

OK, let's run the build in the simulator and see if that works

No alt text provided for this image

And eccola! Our code is now running in an iOS app!

--------------------------------------------------------------------------------------------------------------

Java Compilation (Android)

For illustrating the Java compiled code implementation I will use an Android Project. The ultimate goal of this exercise is to import our generated jar library into our Android project and invoke its main functionality.

I decided to use Kotlin instead of a Java-based project as I want to illustrate how to include this library in a modern source based app project. If you have a Java-based source project, it is even easier to get it working.

OK let's discuss how to generate the Java code:

-main BDAEngine
-dce full
--jvm bin/bestdeal.jar

Let's dissect the compiler arguments:

-main BDAEngine => This indicates Haxe our main entry point class.

-dce full => This tells Haxe we want to use the full Dead Code Elimination feature

--jvm => This tells haxe we want JVM compatible source code (Java). This library can be consumed by any JVM language really, such as Scala, Groovy, Kotlin, Clojure, etc.

**Note: This routine generates a JAR file, which is just a java code archive package.

Now our task is to import our library as a dependency for an Android project and verify it works. For this, I created a super simple Kotlin-based Andoid single activity project which you can find in scripts/android directory.

Now, there are several ways to include a Jar file (Java external library) in Android Studio. I chose the easy way. Let's go over the steps:

  1. Open Android Studio with your project (optionally, I have provided an already preconfigured Android Kotlin Project under scripts/android directory of the tutorial repository)
  2. Let's import our jar as a module. Go to: File > Project Structure. From that window select The modules tab (at the left side column) and click on the + button. From the selector window, select "Import .JAR/.AAR package" and finally select our "bestdeal.jar" from the bin/ folder!
No alt text provided for this image

3. Once imported, go back to the File > Project Structure. Now select "dependencies" tab from the left side column. You will see the bestdeal module but it is not yet associated to your app. To do that, make sure to select your app and on the right panel click on the + under "Declared dependencies" and from there select "Module Dependency". A new window showing the bestdeal module should appear. Select it and click on OK.

No alt text provided for this image

4. Great! Now to use our Haxe generated Java code in our Kotlin class, let's import the module and use it...

No alt text provided for this image

And finally run it (Click on the run app green arrow):

No alt text provided for this image

Yay! We got our app running in Android Kotlin!

--------------------------------------------------------------------------------------------------------------

Javascript Transpiling

# Build JS File (NodeJS compatible)
-main BDAEngine
-D js-es=6
-dce full
-js bin/bestdeal.js

First, we let Haxe know we want for it to transpile our code to Javascript by using the -js compiler argument. It receives the path where you want to store the resulting JS file. You may also want to specify the EcmaScript version you want Haxe to target for (by default, it targets EcmaScript 5). In my case, I like using ES6 for using the latest Javascript features. You do this by using the js-es=6 flag (The -D is a conditional compilation flag to allow us define this). We then use the dce compiler argument to tell the Haxe compiler to use the Dead Code Elimination feature. It has several options (std, full, no), in our specific case we are using full.  Finally, we indicate Haxe that the main entry point of our code is the BDAEngine class by using the main compiler argument (-main).

More information about Haxe JS found here: https://haxe.org/manual/target-javascript.html

After running the build, you can view the generated JS file in the bin directory. It looks like the following:

// Generated by Haxe 4.1.3
(function ($hx_exports, $global) { "use strict";
class BDAEngine {
    static main() {
        console.log("BDAEngine.hx:9:","BDAEngine init!");
        let finalPrice = BDAEngine.getBestDeal(1001,9.99,2);
        console.log("BDAEngine.hx:11:","BDAEngine getBestDeal: " + finalPrice);
    }
    static getBestDeal(itemId,price,previousPurchases) {
        try {
            if(!BDAEngine.validateInput(itemId,price,previousPurchases)) {
                console.log("BDAEngine.hx:17:","BDAEngine - getBestDeal | Validation failed. No best deal can be performed.");
                return price;
            }
            return BDAEngine.process(itemId,price,previousPurchases);
        } catch( _g ) {
            let e = haxe_Exception.caught(_g);
            console.log("BDAEngine.hx:24:","BDAEngine - getBestDeal | Error while attempting to process best deal. Ignoring. Error: " + Std.string(e));
            return price;
        }
    }
    static process(itemId,price,purchases) {
        if(BDAEngine.checkTimeIsLunch()) {
            let item = BDAEngine.checkItemIsEligible(itemId);
            if(item != null) {
                return BDAEngine.applyBestDeal(item,purchases);
            }
        }
        return price;
    }
    static applyBestDeal(item,purchases) {
        return item.price - 0.5 * purchases;
    }
    static checkItemIsEligible(itemId) {
        let items = JSON.parse(BDAEngine.APPLICABLE_ITEMS);
        let _g = [];
        let _g1 = 0;
        let _g2 = items;
        while(_g1 < _g2.length) {
            let v = _g2[_g1];
            ++_g1;
            if(v.id == itemId) {
                _g.push(v);
            }
        }
        let item = _g;
        if(item.length > 0) {
            return item[0];
        }
        return null;
    }
    static checkTimeIsLunch(date) {
        let today = new Date();
        let time = today.getHours();
        if(date != null) {
            time = date.getHours();
        }
        if(time >= BDAEngine.VALID_TIMES[0] && time < BDAEngine.VALID_TIMES[1]) {
            return true;
        }
        return false;
    }
    static validateInput(itemId,price,previousPurchases) {
        if(previousPurchases <= 0) {
            return false;
        }
        if(itemId <= 0) {
            return false;
        }
        if(price <= 0) {
            return false;
        }
        return true;
    }
}
$hx_exports["BDAEngine"] = BDAEngine;
BDAEngine.__name__ = true;
Math.__name__ = true;
class Std {
    static string(s) {
        return js_Boot.__string_rec(s,"");
    }
}
Std.__name__ = true;
class haxe_Exception extends Error {
    constructor(message,previous,native) {
        super(message);
        this.message = message;
        this.__previousException = previous;
        this.__nativeException = native != null ? native : this;
    }
    toString() {
        return this.get_message();
    }
    get_message() {
        return this.message;
    }
    static caught(value) {
        if(((value) instanceof haxe_Exception)) {
            return value;
        } else if(((value) instanceof Error)) {
            return new haxe_Exception(value.message,null,value);
        } else {
            return new haxe_ValueException(value,null,value);
        }
    }
}
haxe_Exception.__name__ = true;
class haxe_ValueException extends haxe_Exception {
    constructor(value,previous,native) {
        super(String(value),previous,native);
        this.value = value;
    }
}
haxe_ValueException.__name__ = true;
class haxe_iterators_ArrayIterator {
    constructor(array) {
        this.current = 0;
        this.array = array;
    }
    hasNext() {
        return this.current < this.array.length;
    }
    next() {
        return this.array[this.current++];
    }
}
haxe_iterators_ArrayIterator.__name__ = true;
class js_Boot {
    static __string_rec(o,s) {
        if(o == null) {
            return "null";
        }
        if(s.length >= 5) {
            return "<...>";
        }
        let t = typeof(o);
        if(t == "function" && (o.__name__ || o.__ename__)) {
            t = "object";
        }
        switch(t) {
        case "function":
            return "<function>";
        case "object":
            if(((o) instanceof Array)) {
                let str = "[";
                s += "\t";
                let _g = 0;
                let _g1 = o.length;
                while(_g < _g1) {
                    let i = _g++;
                    str += (i > 0 ? "," : "") + js_Boot.__string_rec(o[i],s);
                }
                str += "]";
                return str;
            }
            let tostr;
            try {
                tostr = o.toString;
            } catch( _g ) {
                return "???";
            }
            if(tostr != null && tostr != Object.toString && typeof(tostr) == "function") {
                let s2 = o.toString();
                if(s2 != "[object Object]") {
                    return s2;
                }
            }
            let str = "{\n";
            s += "\t";
            let hasp = o.hasOwnProperty != null;
            let k = null;
            for( k in o ) {
            if(hasp && !o.hasOwnProperty(k)) {
                continue;
            }
            if(k == "prototype" || k == "__class__" || k == "__super__" || k == "__interfaces__" || k == "__properties__") {
                continue;
            }
            if(str.length != 2) {
                str += ", \n";
            }
            str += s + k + " : " + js_Boot.__string_rec(o[k],s);
            }
            s = s.substring(1);
            str += "\n" + s + "}";
            return str;
        case "string":
            return o;
        default:
            return String(o);
        }
    }
}
js_Boot.__name__ = true;
String.__name__ = true;
Array.__name__ = true;
Date.__name__ = "Date";
js_Boot.__toStr = ({ }).toString;
BDAEngine.VALID_TIMES = [12,13];
BDAEngine.APPLICABLE_ITEMS = "[{\"id\":1001,\"name\":\"Hamburger\", \"price\": 9.99 },{\"id\":1002,\"name\":\"Taco\", \"price\": 5.99},{\"id\":1003,\"name\":\"Burrito\", \"price\": 8.99},{\"id\":1004,\"name\":\"Pizza\", \"price\": 2.99},{\"id\":1005,\"name\":\"Falafel\", \"price\": 3.99},{\"id\":1006,\"name\":\"Gyro\", \"price\": 4.99}]";
BDAEngine.main();
})(typeof exports != "undefined" ? exports : typeof window != "undefined" ? window : typeof self != "undefined" ? self : this, {});



We have ported our Haxe code to Javascript in a simple step, wonderful! If you want to invoke this class from Javascript, you can check in the scripts/js directory for a file called script.js which shows how to invoke it (make sure you have NodeJS installed in your system):

Script.js

//--------------------------------------
// Javascript test for our Haxe Engine!
// run from terminal as: node Script.js
//--------------------------------------
const BestDeal = require('./../../bin/bestdeal');
console.log(`Best Deal Result: $${BestDeal.BDAEngine.getBestDeal(1001, 9.99, 2)}`);

Output:

dwind@Daniels-Mac-Pro js % node Script.js
BDAEngine.hx:9: BDAEngine init!
Best Deal Result: $8.99

Pretty cool! We are now able to run this code in the browser or any NodeJS server code!

--------------------------------------------------------------------------------------------------------------

PHP Compilation

# Build PHP file
-main BDAEngine
-dce full
-php bin/bestdeal.php

First, we let Haxe know we want for it to compile our code to PHP by using the -php compiler argument. It receives the path where you want to store the resulting PHP files. It is worth noting that Haxe's PHP code generation is compatible with PHP version 7 and above so make sure your project supports this version at minimum. We then use the dce compiler argument to tell the Haxe compiler to use the Dead Code Elimination feature. It has several options (std, full, no), in our specific case we are using full.  Finally, we indicate Haxe that the main entry point of our code is the BDAEngine class by using the main compiler argument (-main).

More information about Haxe PHP found here: https://haxe.org/manual/target-php-getting-started.html

After running the build, you can view the generated PHP files in the bin directory. The main class port looks like the following:

*Contrary to Javascript, the PHP codebase is significantly bigger - several files are ported along - so you get a directory with all files in it

BDAEngine.php

<?php
/**
 * Generated by Haxe 4.1.3
 */


use \php\_Boot\HxAnon;
use \php\Boot;
use \haxe\Log;
use \haxe\Json;
use \haxe\Exception as HaxeException;


class BDAEngine {
    /**
     * @var string
     */
    static public $APPLICABLE_ITEMS = "[{\"id\":1001,\"name\":\"Hamburger\", \"price\": 9.99 },{\"id\":1002,\"name\":\"Taco\", \"price\": 5.99},{\"id\":1003,\"name\":\"Burrito\", \"price\": 8.99},{\"id\":1004,\"name\":\"Pizza\", \"price\": 2.99},{\"id\":1005,\"name\":\"Falafel\", \"price\": 3.99},{\"id\":1006,\"name\":\"Gyro\", \"price\": 4.99}]";
    /**
     * @var \Array_hx
     */
    static public $VALID_TIMES;


    /**
     * @param object $item
     * @param int $purchases
     * 
     * @return float
     */
    public static function applyBestDeal ($item, $purchases) {
        #BDAEngine.hx:43: characters 5-42
        return $item->price - 0.5 * $purchases;
    }


    /**
     * @param int $itemId
     * 
     * @return object
     */
    public static function checkItemIsEligible ($itemId) {
        #BDAEngine.hx:47: characters 5-63
        $items = Json::phpJsonDecode(BDAEngine::$APPLICABLE_ITEMS);
        #BDAEngine.hx:48: characters 28-67
        $result = [];
        $data = $items->arr;
        $_g_current = 0;
        $_g_length = count($data);
        $_g_data = $data;
        while ($_g_current < $_g_length) {
            $item = $_g_data[$_g_current++];
            if ($item->id === $itemId) {
                $result[] = $item;
            }
        }
        #BDAEngine.hx:48: characters 5-68
        $item = \Array_hx::wrap($result);
        #BDAEngine.hx:49: characters 5-44
        if ($item->length > 0) {
            #BDAEngine.hx:49: characters 27-41
            return ($item->arr[0] ?? null);
        }
        #BDAEngine.hx:50: characters 5-16
        return null;
    }


    /**
     * @param \Date $date
     * 
     * @return bool
     */
    public static function checkTimeIsLunch ($date = null) {
        #BDAEngine.hx:54: characters 5-28
        $today = \Date::now();
        #BDAEngine.hx:55: characters 5-33
        $time = $today->getHours();
        #BDAEngine.hx:56: characters 5-48
        if ($date !== null) {
            #BDAEngine.hx:56: characters 24-46
            $time = $date->getHours();
        }
        #BDAEngine.hx:57: lines 57-59
        if (($time >= (BDAEngine::$VALID_TIMES->arr[0] ?? null)) && ($time < (BDAEngine::$VALID_TIMES->arr[1] ?? null))) {
            #BDAEngine.hx:58: characters 7-18
            return true;
        }
        #BDAEngine.hx:60: characters 5-17
        return false;
    }


    /**
     * @param int $itemId
     * @param float $price
     * @param int $previousPurchases
     * 
     * @return float
     */
    public static function getBestDeal ($itemId, $price, $previousPurchases) {
        #BDAEngine.hx:13: lines 13-24
        try {
            #BDAEngine.hx:14: lines 14-17
            if (!BDAEngine::validateInput($itemId, $price, $previousPurchases)) {
                #BDAEngine.hx:15: characters 11-16
                (Log::$trace)("BDAEngine - getBestDeal | Validation failed. No best deal can be performed.", new HxAnon([
                    "fileName" => "BDAEngine.hx",
                    "lineNumber" => 15,
                    "className" => "BDAEngine",
                    "methodName" => "getBestDeal",
                ]));
                #BDAEngine.hx:16: characters 11-23
                return $price;
            }
            #BDAEngine.hx:18: characters 9-57
            return BDAEngine::process($itemId, $price, $previousPurchases);
        } catch(\Throwable $_g) {
            $e = HaxeException::caught($_g);
            #BDAEngine.hx:22: characters 9-14
            (Log::$trace)("BDAEngine - getBestDeal | Error while attempting to process best deal. Ignoring. Error: " . (\Std::string($e)??'null'), new HxAnon([
                "fileName" => "BDAEngine.hx",
                "lineNumber" => 22,
                "className" => "BDAEngine",
                "methodName" => "getBestDeal",
            ]));
            #BDAEngine.hx:23: characters 9-21
            return $price;
        }
    }


    /**
     * @return void
     */
    public static function main () {
        #BDAEngine.hx:9: characters 5-10
        (Log::$trace)("BDAEngine init!", new HxAnon([
            "fileName" => "BDAEngine.hx",
            "lineNumber" => 9,
            "className" => "BDAEngine",
            "methodName" => "main",
        ]));
    }


    /**
     * @param int $itemId
     * @param float $price
     * @param int $purchases
     * 
     * @return float
     */
    public static function process ($itemId, $price, $purchases) {
        #BDAEngine.hx:33: lines 33-38
        if (BDAEngine::checkTimeIsLunch()) {
            #BDAEngine.hx:34: characters 7-46
            $item = BDAEngine::checkItemIsEligible($itemId);
            #BDAEngine.hx:35: lines 35-37
            if ($item !== null) {
                #BDAEngine.hx:36: characters 9-46
                return BDAEngine::applyBestDeal($item, $purchases);
            }
        }
        #BDAEngine.hx:39: characters 5-17
        return $price;
    }


    /**
     * @param int $itemId
     * @param float $price
     * @param int $previousPurchases
     * 
     * @return bool
     */
    public static function validateInput ($itemId, $price, $previousPurchases) {
        #BDAEngine.hx:64: characters 7-52
        if ($previousPurchases <= 0) {
            #BDAEngine.hx:64: characters 37-49
            return false;
        }
        #BDAEngine.hx:65: characters 7-39
        if ($itemId <= 0) {
            #BDAEngine.hx:65: characters 24-36
            return false;
        }
        #BDAEngine.hx:66: characters 7-38
        if ($price <= 0) {
            #BDAEngine.hx:66: characters 23-35
            return false;
        }
        #BDAEngine.hx:67: characters 7-18
        return true;
    }


    /**
     * @internal
     * @access private
     */
    static public function __hx__init ()
    {
        static $called = false;
        if ($called) return;
        $called = true;



        self::$VALID_TIMES = \Array_hx::wrap([
            12,
            13,
        ]);
    }
}


Boot::registerClass(BDAEngine::class, 'BDAEngine');
BDAEngine::__hx__init();

We have ported our Haxe code to PHP in a single step, wonderful! If you want to invoke this class from your PHP codebase, you can check in the scripts/php directory for a file called Script.php which shows how to invoke it. Make sure you have PHP installed in your system!

Script.php

<?php
#-------------------------------------#
# PHP test for our Haxe Engine!
# run from terminal as: php Script.php
#-------------------------------------#
include __DIR__ .'/../../bin/bestdeal.php/index.php';
echo 'Best Deal Result: $'.strval(BDAEngine::getBestDeal(1001, 9.99, 2));

Note that we do not invoke the DBAEngine class directly, but rather we invoke an index.php file (which is also generated by Haxe, to use it as our main entry for the library). In this case, Haxe generated a directory (module) rather than a single file.

Output:

dwind@Daniels-Mac-Pro php % php Script.php 
BDAEngine.hx:9: BDAEngine init!
Best Deal Result: $8.99

Congratulations! we have a running PHP port of our code.

--------------------------------------------------------------------------------------------------------------

Python Compilation

# Build Python file
-main BDAEngine
-dce full
-python bin/bestdeal.py 

First, we let Haxe know we want for it to compile our code to Python by using the -python compiler argument. It receives the path where you want to store the resulting python files. It is worth noting that Haxe's Python code generation is compatible with Python version 3.3 and above so make sure your project supports this version at minimum. We then use the dce compiler argument to tell the Haxe compiler to use the Dead Code Elimination feature. It has several options (std, full, no), in our specific case we are using full.  Finally, we indicate Haxe that the main entry point of our code is the BDAEngine class by using the main compiler argument (-main).

More information about Haxe Python found here: https://haxe.org/manual/target-python-getting-started.html

After running the build, you can view the generated py file in the bin directory. It looks like the following (haxe lib port code omitted for brevity, please refer to the repository for the entire file):

# Generated by Haxe 4.1.3
# coding: utf-8
import sys


import math as python_lib_Math
import math as Math
import inspect as python_lib_Inspect
import sys as python_lib_Sys
import json as python_lib_Json
import traceback as python_lib_Traceback
from datetime import datetime as python_lib_datetime_Datetime
from datetime import timezone as python_lib_datetime_Timezone



class _hx_AnonObject:
    _hx_disable_getattr = False
    def __init__(self, fields):
        self.__dict__ = fields
    def __repr__(self):
        return repr(self.__dict__)
    def __contains__(self, item):
        return item in self.__dict__
    def __getitem__(self, item):
        return self.__dict__[item]
    def __getattr__(self, name):
        if (self._hx_disable_getattr):
            raise AttributeError('field does not exist')
        else:
            return None
    def _hx_hasattr(self,field):
        self._hx_disable_getattr = True
        try:
            getattr(self, field)
            self._hx_disable_getattr = False
            return True
        except AttributeError:
            self._hx_disable_getattr = False
            return False




class Enum:
    _hx_class_name = "Enum"
    __slots__ = ("tag", "index", "params")
    _hx_fields = ["tag", "index", "params"]
    _hx_methods = ["__str__"]


    def __init__(self,tag,index,params):
        self.tag = tag
        self.index = index
        self.params = params


    def __str__(self):
        if (self.params is None):
            return self.tag
        else:
            return self.tag + '(' + (', '.join(str(v) for v in self.params)) + ')'




class BDAEngine:
    _hx_class_name = "BDAEngine"
    __slots__ = ()
    _hx_statics = ["VALID_TIMES", "APPLICABLE_ITEMS", "main", "getBestDeal", "process", "applyBestDeal", "checkItemIsEligible", "checkTimeIsLunch", "validateInput"]


    @staticmethod
    def main():
        print("BDAEngine init!")


    @staticmethod
    def getBestDeal(itemId,price,previousPurchases):
        try:
            if (not BDAEngine.validateInput(itemId,price,previousPurchases)):
                print("BDAEngine - getBestDeal | Validation failed. No best deal can be performed.")
                return price
            return BDAEngine.process(itemId,price,previousPurchases)
        except BaseException as _g:
            e = haxe_Exception.caught(_g)
            print(("BDAEngine - getBestDeal | Error while attempting to process best deal. Ignoring. Error: " + Std.string(e)))
            return price


    @staticmethod
    def process(itemId,price,purchases):
        if BDAEngine.checkTimeIsLunch():
            item = BDAEngine.checkItemIsEligible(itemId)
            if (item is not None):
                return BDAEngine.applyBestDeal(item,purchases)
        return price


    @staticmethod
    def applyBestDeal(item,purchases):
        return (item.price - ((0.5 * purchases)))


    @staticmethod
    def checkItemIsEligible(itemId):
        items = python_lib_Json.loads(BDAEngine.APPLICABLE_ITEMS,**python__KwArgs_KwArgs_Impl_.fromT(_hx_AnonObject({'object_hook': python_Lib.dictToAnon})))
        def _hx_local_0(item):
            return (item.id == itemId)
        item = list(filter(_hx_local_0,items))
        if (len(item) > 0):
            return (item[0] if 0 < len(item) else None)
        return None


    @staticmethod
    def checkTimeIsLunch(date = None):
        today = Date.now()
        time = today.date.hour
        if (date is not None):
            time = date.date.hour
        if ((time >= python_internal_ArrayImpl._get(BDAEngine.VALID_TIMES, 0)) and ((time < python_internal_ArrayImpl._get(BDAEngine.VALID_TIMES, 1)))):
            return True
        return False


    @staticmethod
    def validateInput(itemId,price,previousPurchases):
        if (previousPurchases <= 0):
            return False
        if (itemId <= 0):
            return False
        if (price <= 0):
            return False
        return True



class Class: pass



class Date:
    _hx_class_name = "Date"
    __slots__ = ("date", "dateUTC")
    _hx_fields = ["date", "dateUTC"]
    _hx_statics = ["now", "makeLocal"]


    def __init__(self,year,month,day,hour,_hx_min,sec):
        self.dateUTC = None
        if (year < python_lib_datetime_Datetime.min.year):
            year = python_lib_datetime_Datetime.min.year
        if (day == 0):
            day = 1
        self.date = Date.makeLocal(python_lib_datetime_Datetime(year,(month + 1),day,hour,_hx_min,sec,0))
        self.dateUTC = self.date.astimezone(python_lib_datetime_Timezone.utc)


    @staticmethod
    def now():
        d = Date(2000,0,1,0,0,0)
        d.date = Date.makeLocal(python_lib_datetime_Datetime.now())
        d.dateUTC = d.date.astimezone(python_lib_datetime_Timezone.utc)
        return d


    @staticmethod
    def makeLocal(date):
        try:
            return date.astimezone()
        except BaseException as _g:
            None
            tzinfo = python_lib_datetime_Datetime.now(python_lib_datetime_Timezone.utc).astimezone().tzinfo
            return date.replace(**python__KwArgs_KwArgs_Impl_.fromT(_hx_AnonObject({'tzinfo': tzinfo})))




class Std:
    _hx_class_name = "Std"
    __slots__ = ()
    _hx_statics = ["isOfType", "string"]


    @staticmethod
    def isOfType(v,t):
        if ((v is None) and ((t is None))):
            return False
        if (t is None):
            return False
        if (t == Dynamic):
            return (v is not None)
        isBool = isinstance(v,bool)
        if ((t == Bool) and isBool):
            return True
        if ((((not isBool) and (not (t == Bool))) and (t == Int)) and isinstance(v,int)):
            return True
        vIsFloat = isinstance(v,float)
        tmp = None
        tmp1 = None
        if (((not isBool) and vIsFloat) and (t == Int)):
            f = v
            tmp1 = (((f != Math.POSITIVE_INFINITY) and ((f != Math.NEGATIVE_INFINITY))) and (not python_lib_Math.isnan(f)))
        else:
            tmp1 = False
        if tmp1:
            tmp1 = None
            try:
                tmp1 = int(v)
            except BaseException as _g:
                None
                tmp1 = None
            tmp = (v == tmp1)
        else:
            tmp = False
        if ((tmp and ((v <= 2147483647))) and ((v >= -2147483648))):
            return True
        if (((not isBool) and (t == Float)) and isinstance(v,(float, int))):
            return True
        if (t == str):
            return isinstance(v,str)
        isEnumType = (t == Enum)
        if ((isEnumType and python_lib_Inspect.isclass(v)) and hasattr(v,"_hx_constructs")):
            return True
        if isEnumType:
            return False
        isClassType = (t == Class)
        if ((((isClassType and (not isinstance(v,Enum))) and python_lib_Inspect.isclass(v)) and hasattr(v,"_hx_class_name")) and (not hasattr(v,"_hx_constructs"))):
            return True
        if isClassType:
            return False
        tmp = None
        try:
            tmp = isinstance(v,t)
        except BaseException as _g:
            None
            tmp = False
        if tmp:
            return True
        if python_lib_Inspect.isclass(t):
            cls = t
            loop = None
            def _hx_local_1(intf):
                f = (intf._hx_interfaces if (hasattr(intf,"_hx_interfaces")) else [])
                if (f is not None):
                    _g = 0
                    while (_g < len(f)):
                        i = (f[_g] if _g >= 0 and _g < len(f) else None)
                        _g = (_g + 1)
                        if (i == cls):
                            return True
                        else:
                            l = loop(i)
                            if l:
                                return True
                    return False
                else:
                    return False
            loop = _hx_local_1
            currentClass = v.__class__
            result = False
            while (currentClass is not None):
                if loop(currentClass):
                    result = True
                    break
                currentClass = python_Boot.getSuperClass(currentClass)
            return result
        else:
            return False


    @staticmethod
    def string(s):
        return python_Boot.toString1(s,"")



class Float: pass



class Int: pass



class Bool: pass



class Dynamic: pass



class python_HaxeIterator:
    _hx_class_name = "python.HaxeIterator"
    __slots__ = ("it", "x", "has", "checked")
    _hx_fields = ["it", "x", "has", "checked"]
    _hx_methods = ["next", "hasNext"]


    def __init__(self,it):
        self.checked = False
        self.has = False
        self.x = None
        self.it = it


    def next(self):
        if (not self.checked):
            self.hasNext()
        self.checked = False
        return self.x


    def hasNext(self):
        if (not self.checked):
            try:
                self.x = self.it.__next__()
                self.has = True
            except BaseException as _g:
                None
                if Std.isOfType(haxe_Exception.caught(_g).unwrap(),StopIteration):
                    self.has = False
                    self.x = None
                else:
                    raise _g
            self.checked = True
        return self.has



Math.NEGATIVE_INFINITY = float("-inf")
Math.POSITIVE_INFINITY = float("inf")
Math.NaN = float("nan")
Math.PI = python_lib_Math.pi


BDAEngine.VALID_TIMES = [12, 13]
BDAEngine.APPLICABLE_ITEMS = "[{\"id\":1001,\"name\":\"Hamburger\", \"price\": 9.99 },{\"id\":1002,\"name\":\"Taco\", \"price\": 5.99},{\"id\":1003,\"name\":\"Burrito\", \"price\": 8.99},{\"id\":1004,\"name\":\"Pizza\", \"price\": 2.99},{\"id\":1005,\"name\":\"Falafel\", \"price\": 3.99},{\"id\":1006,\"name\":\"Gyro\", \"price\": 4.99}]"
python_Boot.keywords = set(["and", "del", "from", "not", "with", "as", "elif", "global", "or", "yield", "assert", "else", "if", "pass", "None", "break", "except", "import", "raise", "True", "class", "exec", "in", "return", "False", "continue", "finally", "is", "try", "def", "for", "lambda", "while"])
python_Boot.prefixLength = len("_hx_")


BDAEngine.main()

We have ported our Haxe code to Python in a single step, wonderful! If you want to invoke this class from your ython codebase, you can check in the scripts/python directory for a file called Script.php which shows how to invoke it. Make sure you have Pyhon installed in your system!

Script.py

#----------------------------------------
# Python test for our Haxe Engine!
# run from terminal as: python3 Script.py
#----------------------------------------
import sys
import os
os.chdir('../../')
sys.path.insert(1, os.getcwd())
from bin.bestdeal import BDAEngine
print("Best Deal Result: $" + str(BDAEngine.getBestDeal(1001, 9.99, 2)))

Output:

dwind@Daniels-Mac-Pro python % python3 Script.py 
BDAEngine init!
Best Deal Result: $9.99

Congratulations! we have a running Python port of our code.

--------------------------------------------------------------------------------------------------------------

OK Awesome, you have reached the end of this tutorial. I must admit that it was pretty long, but I hope this tutorial gives you some insight into this hidden gem of cross-platform software. I must remark that the Haxe documentation although very solid when it comes to the language and its features, third party integration (with other languages and platforms) is not well-documented and finding examples can be challenging.

Thanks a lot for reading!

Carlos Roberto Rojas

Senior Software Engineer

2 年

Very nice article, since the last 2 or 3 years I've wanted to do something "professional" using Haxe, but I've always ended thinking that it's not suitable for such kind of endeavors; but now your article proves me wrong, and I'm glad about that. ??

要查看或添加评论,请登录

社区洞察

其他会员也浏览了