Compiler (un sous-ensemble de) JavaScript ou AS2 à ActionScript 2 (Flash Player 8 Compatible) Assemblage bytecode compilable par Flasm. Extrêmement WIP.
Je modding un vieux jeu flash et je me suis fatigué d'écrire des bytecode ActionScript à la main. Heureusement, ActionScript 2 est extrêmement similaire à JavaScript (puisque AS2 est partiellement conforme à la spécification ECMAScript 4), je peux donc simplement utiliser @babel/parser pour analyser ma source en tant que JavaScript dans un AST, puis émettre le bytecode à partir de l'AST.
Le compilateur ne prend en charge que un sous-ensemble des fonctionnalités JS / AS2 dont j'ai besoin, et le projet se réserve le droit de boucher la sémantique de la langue pour le faciliter la mise en œuvre. Le développement ne progressera également que lorsque je trouverai des fonctionnalités que je souhaite mettre en œuvre pour me faciliter la vie.
Le compilateur simule également la pile pour documenter la pile actuelle en tant que commentaires à droite de chaque ligne pour faciliter la vérification manuelle. Si une branche ou une branche d'opcode est atteinte (un if-else ou une boucle), le simulateur saute le reste de la fonction / contexte. Les fonctions intérieures sont simulées avec leurs propres piles. Des instructions de code sont également ajoutées comme commentaires avant le bytecode de ladite déclaration.
@js2f/push-register-context , @js2f/pop-register-contextSource d'échantillon de travail actuel de travail:
outsideGlobalVar = globalVar2 = 123 ;
function gatherStats ( velocity ) {
var emptyLocal , emptyLocal2 , nonEmptyLocal3 ;
var localVar = 123 ;
globalVar = 5432 ;
globalVar = localVar = 1111 ;
globalVar = globalVar2 = 1111 ;
globalVar = globalVar2 = undefined ;
velocity = atv . velocity ;
globalVelocity = atv . velocity ;
atv . bar = 1 ;
this . foo = this . bar + 1 ;
atv . x = atv . velocityX - atv . x ;
localVar = "foo\nbar" ;
return '{"type":"velocity","data":' + ( velocity + 1 ) + "}" ;
}
global . enqueueStats ( gatherStats ( atvMC . velocity ) , 1 ) ;
enqueueStats ( gatherStats ( atvMC . velocity ) ) ;
emptyFunction ( ) ;
global . emptyFunction ( ) ;Émis:
// -- outsideGlobalVar = globalVar2 = 123;
// -- outsideGlobalVar = globalVar2 = 123
push ' outsideGlobalVar ' // ' outsideGlobalVar '
// -- globalVar2 = 123
push ' globalVar2 ' , 123 // ' outsideGlobalVar ' | ' globalVar2 ' | 123
setVariable // ' outsideGlobalVar '
push 123 // ' outsideGlobalVar ' | 123
setVariable // -- <empty>
function2 ' gatherStats ' ( r : 2 = ' velocity ' ) ( r : 1 = ' this ' )
// -- var emptyLocal, emptyLocal2, nonEmptyLocal3;
// -- var localVar = 123;
push 123 // 123
setRegister r : 6 /* local:localVar */ // 123
pop // -- <empty>
// -- globalVar = 5432;
// -- globalVar = 5432
push ' globalVar ' , 5432 // ' globalVar ' | 5432
setVariable // -- <empty>
// -- globalVar = localVar = 1111;
// -- globalVar = localVar = 1111
push ' globalVar ' // ' globalVar '
// -- localVar = 1111
push 1111 // ' globalVar ' | 1111
setRegister r : 6 /* local:localVar */ // ' globalVar ' | 1111
setVariable // -- <empty>
// -- globalVar = globalVar2 = 1111;
// -- globalVar = globalVar2 = 1111
push ' globalVar ' // ' globalVar '
// -- globalVar2 = 1111
push ' globalVar2 ' , 1111 // ' globalVar ' | ' globalVar2 ' | 1111
setVariable // ' globalVar '
push 1111 // ' globalVar ' | 1111
setVariable // -- <empty>
// -- globalVar = globalVar2 = undefined;
// -- globalVar = globalVar2 = undefined
push ' globalVar ' // ' globalVar '
// -- globalVar2 = undefined
push ' globalVar2 ' , UNDEF // ' globalVar ' | ' globalVar2 ' | UNDEF
setVariable // ' globalVar '
push UNDEF // ' globalVar ' | UNDEF
setVariable // -- <empty>
// -- velocity = atv.velocity;
// -- velocity = atv.velocity
push ' atv ' // ' atv '
getVariable // atv
push ' velocity ' // atv | ' velocity '
getMember // atv . velocity
setRegister r : velocity // atv . velocity
pop // -- <empty>
// -- globalVelocity = atv.velocity;
// -- globalVelocity = atv.velocity
push ' globalVelocity ' , ' atv ' // ' globalVelocity ' | ' atv '
getVariable // ' globalVelocity ' | atv
push ' velocity ' // ' globalVelocity ' | atv | ' velocity '
getMember // ' globalVelocity ' | atv . velocity
setVariable // -- <empty>
// -- atv.bar = 1;
// -- atv.bar = 1
push ' atv ' // ' atv '
getVariable // atv
push ' bar ' , 1 // atv | ' bar ' | 1
setMember // -- <empty>
// -- this.foo = this.bar + 1;
// -- this.foo = this.bar + 1
push r : this , ' foo ' , r : this , ' bar ' // r : this | ' foo ' | r : this | ' bar '
getMember // r : this | ' foo ' | r : this . bar
push 1 // r : this | ' foo ' | r : this . bar | 1
add // r : this | ' foo ' | r : this . bar + 1
setMember // -- <empty>
// -- atv.x = atv.velocityX - atv.x;
// -- atv.x = atv.velocityX - atv.x
push ' atv ' // ' atv '
getVariable // atv
push ' x ' , ' atv ' // atv | ' x ' | ' atv '
getVariable // atv | ' x ' | atv
push ' velocityX ' // atv | ' x ' | atv | ' velocityX '
getMember // atv | ' x ' | atv . velocityX
push ' atv ' // atv | ' x ' | atv . velocityX | ' atv '
getVariable // atv | ' x ' | atv . velocityX | atv
push ' x ' // atv | ' x ' | atv . velocityX | atv | ' x '
getMember // atv | ' x ' | atv . velocityX | atv . x
subtract // atv | ' x ' | atv . velocityX - atv . x
setMember // -- <empty>
// -- localVar = "foo\nbar";
// -- localVar = "foo\nbar"
push ' foo n bar ' // ' foo n bar '
setRegister r : 6 /* local:localVar */ // ' foo n bar '
pop // -- <empty>
// -- return '{"type":"velocity","data":' + (velocity + 1) + "}";
push ' {"type":"velocity","data": ' , r : velocity , 1 // ' {"type":"velocity","data": ' | r : velocity | 1
add // ' {"type":"velocity","data": ' | r : velocity + 1
add // ' {"type":"velocity","data": ' + ( r : velocity + 1 )
push ' } ' // ' {"type":"velocity","data": ' + ( r : velocity + 1 ) | ' } '
add // ( ' {"type":"velocity","data": ' + ( r : velocity + 1 )) + ' } '
return // ( ' {"type":"velocity","data": ' + ( r : velocity + 1 )) + ' } '
end // of function gatherStats
// -- global.enqueueStats(gatherStats(atvMC.velocity), 1);
push 1, ' atvMC ' // 1 | ' atvMC '
getVariable // 1 | atvMC
push ' velocity ' // 1 | atvMC | ' velocity '
getMember // 1 | atvMC . velocity
push 1 , ' gatherStats ' // 1 | atvMC . velocity | 1 | ' gatherStats '
callFunction // 1 | gatherStats ( atvMC . velocity )
push 2 , ' global ' // 1 | gatherStats ( atvMC . velocity ) | 2 | ' global '
getVariable // 1 | gatherStats ( atvMC . velocity ) | 2 | global
push ' enqueueStats ' // 1 | gatherStats ( atvMC . velocity ) | 2 | global | ' enqueueStats '
callMethod // global . enqueueStats ( gatherStats ( atvMC . velocity ), 1 )
pop // -- <empty>
// -- enqueueStats(gatherStats(atvMC.velocity));
push ' atvMC ' // ' atvMC '
getVariable // atvMC
push ' velocity ' // atvMC | ' velocity '
getMember // atvMC . velocity
push 1 , ' gatherStats ' // atvMC . velocity | 1 | ' gatherStats '
callFunction // gatherStats ( atvMC . velocity )
push 1 , ' enqueueStats ' // gatherStats ( atvMC . velocity ) | 1 | ' enqueueStats '
callFunction // enqueueStats ( gatherStats ( atvMC . velocity ))
pop // -- <empty>
// -- emptyFunction();
push 0 , ' emptyFunction ' // 0 | ' emptyFunction '
callFunction // emptyFunction ()
pop // -- <empty>
// -- global.emptyFunction();
push 0 , ' global ' // 0 | ' global '
getVariable // 0 | global
push ' emptyFunction ' // 0 | global | ' emptyFunction '
callMethod // global . emptyFunction ()
pop // -- <empty> Clone the Repo et exécutez npm i . Ensuite, exécutez npm start ou installez-le comme un module NPM relatif local et exécutez-le en tant que js-to-flasm-as2-compiler à l'intérieur package.json
js-to-flasm-as2-compiler [configFilePath]
$ npm start
> [email protected] start D: P rojects j s-to-flasm-compiler
> node ./bin/index.js
samples A .js - > dist A .flm
samples B .js - > dist B .flm
empty file samples C a.js
samples C aa.js - > dist C aa.flm
samples h ooks f oobar i nitialize-socket-hook.js - > dist h ooks f oobar i nitialize-socket-hook.flmSi vous êtes sur Linux, vous pouvez probablement juste
chmod u+x bin/index.js
ln -s $PWD /bin/index.js ./js-to-flasm-as2-compiler
./js-to-flasm-as2-compiler Le compilateur compilera les fichiers source JS dans samples/ , et émettra la sortie dans le dist/ Directory.
Le compilateur se bloque si vous utilisez des fonctionnalités JS que je n'ai pas implémentées.
Les commentaires sont également //-- et /*--[[ --]]*/ Parce que les commentaires de Flasm sont // et /* */ , mais j'utilise la syntaxe de Lua qui met en évidence pour FLM (c'est assez bon!) Et les Lua sont -- et --[[ --]] .
var est pris en charge (pas let ou const )if cela ne prend pas en charge les opérateurs binaires logiques&& ou || ), cependant ! estclass n'est pas implémentée, utilisez une programmation basée sur le prototype (faites une fonction qui est un constructeur et attribuez des fonctions à ctor.prototype)int() au lieu de parseInt()var foo = parseInt(bar) do var foo = int(bar)intfoo[bar] ou foo['bar'] ou foo[x + y + 1 + true + 0] est correct, mais foo[bar++] ou foo[selector()] ne parviendra pas à compiler++variable fonctionne donc mais ++foo.bar ou ++foo[bar] nevariable++ ou variable++ n'est pas implémenté Le compilateur recherche un fichier de configuration nommé js-to-flasm.config.json dans le répertoire actuel, ou à partir d'un chemin spécifié par le premier argument qui lui a été transmis.
Par exemple
js-to-flasm-as2-compiler ./config/js-to-flasm.config.json
Exemple de fichier de configuration:
{
"dist" : " dist/ " ,
"sourceRoot" : " samples/ "
} Aucune autre clé n'est prise en charge et les deux sont nécessaires. Les deux chemins de fichier peuvent être tout ce que fs.readdir() de Node comprend. Se terminer / est probablement facultatif.
Les directives du compilateur sont implémentées via des commentaires en une seule ligne. Les directives ne sont pas recherchées pour les commentaires en blocs intérieurs ou les commentaires JSDOC. Une directive est de forme:
// directive-name
ou s'il accepte les arguments, il est de forme:
// directive-with-args: arg1 arg2 arg3 argn
Les directives actuelles sont:
@js2f/push-register-context , @js2f/pop-register-context Vous permet de parler au compilateur des associations de variables Register <->. Par exemple, si vous compilez du code qui obtient #include 'd dans une fonction que vous ne contrôlez pas (c'est-à-dire son code démonté), et vous souhaitez this accéder à l'intérieur de la fonction, vous pouvez instruire le compilateur qu'il est dans le registre n.
Faites éclater le contexte une fois que vous n'en avez plus besoin.
Exemple 1
// @js2f/push-register-context: r:1=this
enqueueSocketJob ( '{"type": "cash", "data":' + this . cash + "}" ) ;
// @js2f/pop-register-contextExemple 2
// @js2f/push-register-context: r:3=socket
socket . onConnect = function ( ) {
// can use local variables here
var foobar = 123 ;
} ;
// @js2f/pop-register-context MIT, voir Licence.