ということで、Haxe触ってみた。
今回はHaxeの単一ソースからNeko、HashLink、C++、C#、JavaScript(Node.js)、Python3、Lua、PHPに対するコンパイルを行った。
まず、C++、C#、Node.jsに対してコンパイルを行うにはhaxelibでパッケージを導入する必要がある。
インストーラーで導入してパスが通っている前提で、
C:\> haxelib install hxcpp -notimeout
C:\> haxelib install hxcs -notimeout
C:\> haxelib install hxnodejs -notimeout
C:\> haxelib install hxjava -notimeout
-notimeoutはつけておくとタイムアウトで失敗しなくなるのであったほうがいいかな…
あとはNode.jsの置換動作が一部気に入らないので修正。
"C:\HaxeToolkit\haxe\lib\hxnodejs\x,x,x\src"
辺りに入ってるSys.hxをテキストエディタで開いて下記を置換した。
import js.node.ChildProcess;
import js.Node.process;
@:dce
// @:coreApi
class Sys {
public static inline function print(v:Dynamic):Void {
process.stdout.write(v);
}
public static inline function println(v:Dynamic):Void {
process.stdout.write("\n");
process.stdout.write(v);
}
/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
↓
import js.Node.console;
import js.node.Util;
import js.node.ChildProcess;
import js.Node.process;
@:dce
// @:coreApi
class Sys {
public static inline function print(v:Dynamic):Void {
process.stdout.write(Util.format(v));
}
public static inline function println(v:Dynamic):Void {
console.log(v);
}
/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
各言語に対しては次のようにトランスパイル/コンパイルする。
ファイル名、クラス名は全てFbとした。
haxe -main Fb -neko Fb/Fb.n
haxe -main Fb -hl Fb/Fb.hl
haxe -main Fb -cpp Fb/Fb.cpp
haxe -main Fb -cs Fb/Fb.cs -D net-ver=45
haxe -main Fb -js Fb/Fb.js -lib hxnodejs
haxe -main Fb -python Fb/Fb.py
haxe -main Fb -lua Fb/Fb.lua
haxe -main Fb -php Fb/Fb.php
構成は次の通り。
C:\> haxe -cp ファイルパス -main メインクラス名及びファイル名 -言語 出力名
C#はDオプションをつけないとコンパイルバージョンが.Net2.0になり、
Node.jsはlibオプション付けないとそもそもNode.js用にコンパイルができない。
ここから本題。
適当な題材としてFizzBuzz問題を選んだ。
FizzBuzz
Haxe
class Fb{
static function main():Void {
for(i in 1...100+1){
if(i%3==0 && i%5==0){
Sys.println("Fizz Buzz");
}
else if(i%3==0){
Sys.println("Fizz");
}
else if(i%5==0){
Sys.println("Buzz");
}
else {
Sys.println(i);
}
}
}
}
以下にトランスパイルしたソース(※主要部分のみ)とネイティブ実装を併記する。
C++
トランスパイル
// Generated by Haxe 3.4.4
# include <hxcpp.h>
# ifndef INCLUDED_Fb
# include <Fb.h>
# endif
# ifndef INCLUDED_Sys
# include <Sys.h>
# endif
HX_LOCAL_STACK_FRAME(_hx_pos_42f183277727ffcb_3_main,"Fb","main",0x529cbc8b,"Fb.main","Fb.hx",3,0x8ef6c6e2)
void Fb_obj::__construct() { }
Dynamic Fb_obj::__CreateEmpty() { return new Fb_obj; }
void *Fb_obj::_hx_vtable = 0;
Dynamic Fb_obj::__Create(hx::DynamicArray inArgs)
{
hx::ObjectPtr< Fb_obj > _hx_result = new Fb_obj();
_hx_result->__construct();
return _hx_result;
}
bool Fb_obj::_hx_isInstanceOf(int inClassId) {
return inClassId==(int)0x00000001 || inClassId==(int)0x00003d5c;
}
void Fb_obj::main(){
HX_STACKFRAME(&_hx_pos_42f183277727ffcb_3_main)
HXDLIN( 3) int _g1 = (int)1;
HXDLIN( 3) int _g = (int)101;
HXDLIN( 3) while((_g1 < _g)){
HXDLIN( 3) _g1 = (_g1 + (int)1);
HXDLIN( 3) int i = (_g1 - (int)1);
HXLINE( 4) bool _hx_tmp;
HXDLIN( 4) if ((hx::Mod(i,(int)3) == (int)0)) {
HXLINE( 4) _hx_tmp = (hx::Mod(i,(int)5) == (int)0);
}
else {
HXLINE( 4) _hx_tmp = false;
}
HXDLIN( 4) if (_hx_tmp) {
HXLINE( 5) ::Sys_obj::println(HX_("Fizz Buzz",d0,f9,43,55));
}
else {
HXLINE( 7) if ((hx::Mod(i,(int)3) == (int)0)) {
HXLINE( 8) ::Sys_obj::println(HX_("Fizz",e3,06,95,2e));
}
else {
HXLINE( 10) if ((hx::Mod(i,(int)5) == (int)0)) {
HXLINE( 11) ::Sys_obj::println(HX_("Buzz",73,47,f9,2b));
}
else {
HXLINE( 14) ::Sys_obj::println(i);
}
}
}
}
}
STATIC_HX_DEFINE_DYNAMIC_FUNC0(Fb_obj,main,(void))
Fb_obj::Fb_obj()
{
}
bool Fb_obj::__GetStatic(const ::String &inName, Dynamic &outValue, hx::PropertyAccess inCallProp)
{
switch(inName.length) {
case 4:
if (HX_FIELD_EQ(inName,"main") ) { outValue = main_dyn(); return true; }
}
return false;
}
# if HXCPP_SCRIPTABLE
static hx::StorageInfo *Fb_obj_sMemberStorageInfo = 0;
static hx::StaticInfo *Fb_obj_sStaticStorageInfo = 0;
# endif
static void Fb_obj_sMarkStatics(HX_MARK_PARAMS) {
HX_MARK_MEMBER_NAME(Fb_obj::__mClass,"__mClass");
};
# ifdef HXCPP_VISIT_ALLOCS
static void Fb_obj_sVisitStatics(HX_VISIT_PARAMS) {
HX_VISIT_MEMBER_NAME(Fb_obj::__mClass,"__mClass");
};
# endif
hx::Class Fb_obj::__mClass;
static ::String Fb_obj_sStaticFields[] = {
HX_HCSTRING("main","\x39","\x38","\x56","\x48"),
::String(null())
};
void Fb_obj::__register()
{
hx::Object *dummy = new Fb_obj;
Fb_obj::_hx_vtable = *(void **)dummy;
hx::Static(__mClass) = new hx::Class_obj();
__mClass->mName = HX_HCSTRING("Fb","\x5c","\x3d","\x00","\x00");
__mClass->mSuper = &super::__SGetClass();
__mClass->mConstructEmpty = &__CreateEmpty;
__mClass->mConstructArgs = &__Create;
__mClass->mGetStaticField = &Fb_obj::__GetStatic;
__mClass->mSetStaticField = &hx::Class_obj::SetNoStaticField;
__mClass->mMarkFunc = Fb_obj_sMarkStatics;
__mClass->mStatics = hx::Class_obj::dupFunctions(Fb_obj_sStaticFields);
__mClass->mMembers = hx::Class_obj::dupFunctions(0 /* sMemberFields */);
__mClass->mCanCast = hx::TCanCast< Fb_obj >;
# ifdef HXCPP_VISIT_ALLOCS
__mClass->mVisitFunc = Fb_obj_sVisitStatics;
# endif
# ifdef HXCPP_SCRIPTABLE
__mClass->mMemberStorageInfo = Fb_obj_sMemberStorageInfo;
# endif
# ifdef HXCPP_SCRIPTABLE
__mClass->mStaticStorageInfo = Fb_obj_sStaticStorageInfo;
# endif
hx::_hx_RegisterClass(__mClass->mName, __mClass);
}
ネイティブ実装
# include<iostream>
int main(){
for(int i=1;i<=100;i++){
if(i%3==0 && i%5==0){
std::cout<<"Fizz Buzz"<<std::endl;
}
else if(i%3==0){
std::cout<<"Fizz"<<std::endl;
}
else if(i%5==0){
std::cout<<"Buzz"<<std::endl;
}
else {
std::cout<<i<<std::endl;
}
}
}
C#
トランスパイル
// Generated by Haxe 3.4.4
# pragma warning disable 109, 114, 219, 429, 168, 162
public class Fb : global::haxe.lang.HxObject {
public static void Main(){
global::cs.Boot.init();
{
global::Fb.main();
}
}
public Fb(global::haxe.lang.EmptyObject empty) {
}
public Fb() {
global::Fb.__hx_ctor__Fb(this);
}
public static void __hx_ctor__Fb(global::Fb __hx_this) {
}
public static void main() {
unchecked {
int _g1 = 1;
int _g = 101;
while (( _g1 < _g )) {
int i = _g1++;
if (( ( ( i % 3 ) == 0 ) && ( ( i % 5 ) == 0 ) )) {
global::System.Console.WriteLine(((object) ("Fizz Buzz") ));
}
else if (( ( i % 3 ) == 0 )) {
global::System.Console.WriteLine(((object) ("Fizz") ));
}
else if (( ( i % 5 ) == 0 )) {
global::System.Console.WriteLine(((object) ("Buzz") ));
}
else {
global::System.Console.WriteLine(((object) (i) ));
}
}
}
}
}
ネイティブ実装
using System;
class Program{
static void Main(){
for(var i=1;i<=100;i++){
if(i%3==0 && i%5==0){
Console.WriteLine("Fizz Buzz");
}
else if(i%3==0){
Console.WriteLine("Fizz");
}
else if(i%5==0){
Console.WriteLine("Buzz");
}
else {
Console.WriteLine(i);
}
}
}
}
Imports System
Module Program
Sub Main()
For i=1 To 100
If i Mod 3=0 And i Mod 5=0 Then
Console.WriteLine("Fizz Buzz")
ElseIf i Mod 3=0 Then
Console.WriteLine("Fizz")
ElseIf i Mod 5=0 Then
Console.WriteLine("Buzz")
Else
Console.WriteLine(i)
End If
Next
End Sub
End Module
JavaScript(Node.js)
トランスパイル
// Generated by Haxe 3.4.4
if (process.version < "v4.0.0") console.warn("Module " + (typeof(module) == "undefined" ? "" : module.filename) + " requires node.js version 4.0.0 or higher");
(function () { "use strict";
var Fb = function() { };
Fb.main = function() {
var _g1 = 1;
var _g = 101;
while(_g1 < _g) {
var i = _g1++;
if(i % 3 == 0 && i % 5 == 0) {
console.log("Fizz Buzz");
} else if(i % 3 == 0) {
console.log("Fizz");
} else if(i % 5 == 0) {
console.log("Buzz");
} else {
console.log(i);
}
}
};
var haxe_io_Bytes = function() { };
var js_node_buffer_Buffer = require("buffer").Buffer;
Fb.main();
})();
ネイティブ実装
(function(){
for(var i=1;i<=100;i++){
if(i%3==0 && i%5==0){
console.log("Fizz Buzz");
}
else if(i%3==0){
console.log("Fizz");
}
else if(i%5==0){
console.log("Buzz");
}
else {
console.log(i);
}
}
})();
Python
トランスパイル
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
class Fb:
_hx_class_name = "Fb"
__slots__ = ()
_hx_statics = ["main"]
@staticmethod
def main():
_g1 = 1
_g = 101
while (_g1 < _g):
i = _g1
_g1 = (_g1 + 1)
if ((HxOverrides.mod(i, 3) == 0) and ((HxOverrides.mod(i, 5) == 0))):
Sys.println("Fizz Buzz")
elif (HxOverrides.mod(i, 3) == 0):
Sys.println("Fizz")
elif (HxOverrides.mod(i, 5) == 0):
Sys.println("Buzz")
else:
Sys.println(i)
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
ネイティブ実装
if __name__=="__main__":
for i in range(1,100+1):
if i%3==0 and i%5==0:
print("Fizz Buzz")
elif i%3==0:
print("Fizz")
elif i%5==0:
print("Buzz")
else:
print(i)
Lua
トランスパイル
--[[~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~]]
Fb.new = {}
Fb.main = function()
local _g1 = 1;
local _g = 101;
while (_g1 < _g) do
_g1 = _g1 + 1;
local i = _g1 - 1;
if (((_G.math.fmod(i, 3)) == 0) and ((_G.math.fmod(i, 5)) == 0)) then
_G.print("Fizz Buzz");
else
if ((_G.math.fmod(i, 3)) == 0) then
_G.print("Fizz");
else
if ((_G.math.fmod(i, 5)) == 0) then
_G.print("Buzz");
else
_G.print(Std.string(i));
end;
end;
end;
end;
end
--[[~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~]]
ネイティブ実装
(function()
for i=1,100 do
if i%3==0 and i%5==0 then
print("Fizz Buzz");
elseif i%3==0 then
print("Fizz");
elseif i%5==0 then
print("Buzz");
else
print(i);
end
end
end)();
PHP
トランスパイル
<?php
// Generated by Haxe 3.4.4
class Fb {
public function __construct(){}
static function main() {
$_g1 = 1;
$_g = 101;
while($_g1 < $_g) {
$_g1 = $_g1 + 1;
$i = $_g1 - 1;
$tmp = null;
if(_hx_mod($i, 3) === 0) {
$tmp = _hx_mod($i, 5) === 0;
} else {
$tmp = false;
}
if($tmp) {
Sys::println("Fizz Buzz");
} else {
if(_hx_mod($i, 3) === 0) {
Sys::println("Fizz");
} else {
if(_hx_mod($i, 5) === 0) {
Sys::println("Buzz");
} else {
Sys::println($i);
}
}
}
unset($tmp,$i);
}
}
function __toString() { return 'Fb'; }
}
ネイティブ実装
<?php
(function(){
for($i=1;$i<=100;$i++){
if($i%3==0 && $i%5==0){
print "Fizz Buzz\n";
}
else if($i%3==0){
print "Fizz\n";
}
else if($i%5==0){
print "Buzz\n";
}
else {
print "${i}\n";
}
}
})();
?>
FizzBuzzまとめ
- JavaScriptは元となっているだけあって最も原型を保ち、コード量も少なかった。
- よくわからないけどC++凄い。
- Pythonの圧倒的長さ。1000行越えの超大作に。
FizzBuzz REPL(標準入力入りHaxe)
Haxe共通の標準入力は
Sys.stdin().readLine()
より取得できる、がjsは元より対応しておらず、
C#でも動作に支障があるため、そのまま使用するには難があった。
そこで同じソースからそれぞれ7言語に変換できるようにプリプロセッサ命令を用いてコンパイル分けを行ってみた。
題材として先ほどのFizzBuzz問題の上限を入力から与えて何度でも取得できる対話プログラムを制作した。
# if js
import js.Node.process;
import js.node.Readline;
# elseif cs
import cs.system.Console;
# else
import haxe.io.Input;
# end
class Fbio{
#if js
static var rl=Readline.createInterface(process.stdin,process.stdout);
#end
static function myPrompt(pt:String):Void {
#if js
rl.setPrompt(pt);
rl.prompt();
#else
Sys.print(pt);
#end
}
#if !js static function readln():String { #end
#if cs
return Console.ReadLine();
#elseif !js
var input:Input=Sys.stdin();
return input.readLine();
#end
#if !js } #end
static function main():Void {
var pt="> ";
#if js (function loop():Void {
#else while(true){
#end
myPrompt(pt);
#if js rl.once("line",(str:String)->{
#else var str=readln();
#end
var num=Std.parseInt(str);
if(!Std.is(num,Int)){
myPrompt("Exit Console? (y/n) >> ");
#if js rl.once("line",(str:String)->{
#else str=readln();
#end
if(str=="y") Sys.exit(0);
#if js loop(); }); #end
}
else {
fizzbuzz(num);
myPrompt(pt);
#if js loop(); #end
}
}
#if js ); })(); #end //rl.once, loop
}
static function fizzbuzz(n:Int):Void {
for(i in 1...n+1){
if(i%3==0 && i%5==0){
Sys.println("Fizz Buzz");
}
else if(i%3==0){
Sys.println("Fizz");
}
else if(i%5==0){
Sys.println("Buzz");
}
else {
Sys.println(i);
}
}
}
}
JavaScriptまでターゲットに含む場合はいかに非同期処理を抑え込むかが課題となると思う。
Generator FunctionもAsync/Awaitも使用不可能?であるため大変厳しい。
(Promiseは使えるっぽい?)
最後に比較用としてjsとVB・HSPの実装を載せておく。
Node.js FizzBuzz REPL
"use strict";
const rl=require("readline").createInterface(process.stdin,process.stdout);
function myPrompt(pt){
rl.setPrompt(pt);
rl.prompt();
}
function readln(){
return new Promise(resolve=>rl.once("line",resolve));
}
(async function main(){
const pt="> ";
for(;;){
myPrompt(pt);
var str=await readln();
var num=+str;
if(isNaN(num)){
myPrompt("Exit Console? (y/n) >> ");
str=await readln();
if(str=="y") process.exit();
}
else{
fizzbuzz(0|num);
}
}
})();
function fizzbuzz(n){
for(var i=1;i<=n;i=0|i+1){
if(i%3==0 && i%5==0){
console.log("Fizz Buzz");
}
else if(i%3==0){
console.log("Fizz");
}
else if(i%5==0){
console.log("Buzz");
}
else {
console.log(i);
}
}
}
VB.net
Option Strict On
Imports System
Module Fbio
Sub myPrompt(pt As String)
Console.Write(pt)
End Sub
Function readln() As String
return Console.ReadLine()
End Function
Sub Main()
Const pt="> "
Do
myPrompt(pt)
Dim str=readln()
Dim num As Integer
If Not Integer.TryParse(str,num) Then
myPrompt("Exit Console? (y/n) >> ")
str=readln()
If str="y" Then Environment.Exit(0)
Else
fizzbuzz(num)
End If
Loop
End Sub
Sub fizzbuzz(n As Integer)
For i=1 To n
If i Mod 3=0 And i Mod 5=0 Then
Console.WriteLine("Fizz Buzz")
ElseIf i Mod 3=0 Then
Console.WriteLine("Fizz")
ElseIf i Mod 5=0 Then
Console.WriteLine("Buzz")
Else
Console.WriteLine(i)
End If
Next
End Sub
End Module
HSP
# runtime "hsp3cl"
# cmpopt varinit 1
# module Fbio
#uselib "msvcrt"
#func printf "printf" str
#deffunc myPrompt str pt
printf pt
return
#defcfunc readln
sdim res
input res,,1
br=$0D,$0A
foreach br
res=strtrim(res,2,br(cnt))
loop
return res
#deffunc main
pt="> "
repeat
myPrompt pt
_str=readln()
num=int(_str)
if num=0 && _str!="0" {
myPrompt "Exit Console? (y/n) >> "
_str=readln()
mes _str
if _str="y" :end
}
else {
fizzbuzz num
}
loop
return
#deffunc fizzbuzz int n
repeat n,1
if cnt\3=0 && cnt\5=0 {
mes "Fizz Buzz"
}
else:if cnt\3=0 {
mes "Fizz"
}
else:if cnt\5=0 {
mes "Buzz"
}
else {
mes cnt
}
loop
return
# global
main