Developpez.com - Rubrique JavaScript

Le Club des Développeurs et IT Pro

Coder en JavaScript avec seulement six caractères

Un tutoriel de Sylvain Pollet-Villard

Le 2013-07-08 08:35:04, par vermine, Expert éminent sénior


Sylvain Pollet-Villard s'ennuyait sûrement le jour où il eut l'idée saugrenue de coder du JavaScript avec le moins de caractères possibles. Et pourtant, il l'a fait ! Il nous explique les différentes étapes de sa démarche qui aboutit à la rédaction d'un compilateur.

Coder en JavaScript avec seulement six caractères

N'hésitez pas à lui faire part de vos commentaires et remarques sur cette drôle de machinerie barbare.
  Discussion forum
9 commentaires
  • OPi
    Membre actif
    Pour Python cela me semble impossible, cela ne peut se faire qu'avec des langages à typage faible.

    J'ai généré automatiquement les codes bien parenthésés d'une certaine longueur qui sont évaluées correctement.
    Code :
    +!![]
    et
    Code :
    +!+[]
    donnent aussi le nombre 1.

    Code :
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    object		6	[[[]]] 
    object		4	[[]] 
    object		2	[] 
    undefined	undefined	6	[][[]] 
    string		5	[]+[] 
    string	false	6	[]+![] 
    object	0	5	[+[]] 
    object	false	5	[![]] 
    object	true	6	[!+[]] 
    object	true	6	[!![]] 
    number	0	5	+[[]] 
    number	0	3	+[] 
    string	0	6	+[]+[] 
    number	NaN	6	+[![]] 
    number	1	5	+!+[] 
    number	1	5	+!![] 
    boolean	false	5	![[]] 
    boolean	false	3	![] 
    string	false	6	![]+[] 
    boolean	true	6	!+[[]] 
    boolean	true	4	!+[] 
    boolean	true	4	!![]
    Plus de résultats dans les fichiers attachés.

    Code :
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    var CHARS = ['[', ']', '+', '!', '(', ')'];  /* changer ce tableau pour utiliser d'autres caractères */ 
     
     
     
    var NB_CHARS = CHARS.length; 
     
    var BALANCED_CHARS = {}; 
    var CLOSE_CHARS = []; 
    if ( CHARS.indexOf('[') >= 0 ) { 
        BALANCED_CHARS['['] = ']'; 
        CLOSE_CHARS.push(']'); 
    } 
    if ( CHARS.indexOf('(') >= 0 ) { 
        BALANCED_CHARS['('] = ')'; 
        CLOSE_CHARS.push(')'); 
    } 
    if ( CHARS.indexOf('{') >= 0 ) { 
        BALANCED_CHARS['{'] = '}'; 
        CLOSE_CHARS.push('}'); 
    } 
     
    var results = {}; 
     
     
     
    /* 
      Renvoie true 
      si s est bien parenthésée, 
      false sinon. 
    */ 
    function balanced(s) { 
        'use strict'; 
     
        var s_LENGTH = s.length; 
     
        var stack = []; 
     
        var i; 
        var c; 
     
        for (i = 0; i < s_LENGTH; i++) { 
            c = s[i]; 
            if ( c in BALANCED_CHARS ) { 
                stack.push(c); 
            } 
            else if ( CLOSE_CHARS.indexOf(c) >= 0 ) { 
                if ( BALANCED_CHARS[stack.pop()] !== c ) { 
                    return false; 
                } 
            } 
        } 
     
        return (stack.length === 0); 
    } 
     
     
    /* 
      Parcourt récursivement les chaînes jusqu'à une longueur num, 
      tente de les évaluer 
      et affiche les résultats non encore rencontrés ou déjà rencontrés mais au moins aussi long. 
     */ 
    function build_and_try_string(s, num) { 
        'use strict'; 
     
        if ( num > 0 ) { 
            var i; 
            var c; 
     
            for (i = 0; i < NB_CHARS; i++) { 
                c = CHARS[i]; 
     
                try_string(s + c); 
                build_and_try_string(s + c, num - 1); 
            } 
        } 
    } 
     
     
    /* 
      Affiche s 
    */ 
    function display(s) { 
        try {            // dans un navigateur 
            document.write(s + '\n'); 
        } 
        catch ( err ) {  // avec Rhino 
            print(s); 
        } 
    } 
     
     
    /* 
      Tente d'évaluer s. 
      Si l'évaluation s'est bien passée 
      et que le résultat n'a pas encore été obtenu ou a déjà été obtenu avec une chaîne au moins aussi longue, 
      alors affiche le résultat. 
     
      BORD: ajoute l'éventuel élément affiché à results 
    */ 
    function try_string(s) { 
        'use strict'; 
     
        if ( balanced(s) ) { 
            var v; 
     
            try { 
                v = eval(s); 
            } 
            catch ( err ) { 
                return; 
            } 
     
            var k = [typeof v, v]; 
     
            if ( !(k in results) || (s.length <= results[k]) ) { 
                results[k] = s.length; 
                display((typeof v) + '\t' + v + '\t' + s.length + '\t' + s); 
            } 
        } 
    } 
     
     
     
    /* Main */ 
    build_and_try_string('', 6);
  • Kaamo
    Membre émérite
    Pour information, voici le sujet de la réflexion.

    Sylvain, as-tu pu échanger avec le créateur de JSFuck ?
  • anykeyh
    Membre confirmé
    Bravo bon boulot!

    En plus de la considération ludique, je trouve que c'est très instructif ce genre d'exploit!
  • SylvainPV
    Rédacteur/Modérateur
    Good news everyone !

    Je vous disais dans cet article publié il y a presque trois ans qu'il serait peut-être possible de descendre à 5 caractères. Une nouvelle piste est apparue avec la norme ECMAScript 6 et la fonction Array.prototype.find ! En effet, nous disposons de toutes les lettres de "find" grâce à undefined.

    Cela permet donc de récupérer une fonction sans avoir besoin du caractère "!".

    Code :
    1
    2
    Array.prototype.find === [][[[]+[][+[]]][+[]][++[++[++[++[[]][+[]]][+[]]][+[]]][+[]]]+[[]+[][+[]]][+[]][++[++[++[++[++[[]][+[]]][+[]]][+[]]][+[]]][+[]]]+[[]+[][+[]]][+[]][++[[]][+[]]]+[[]+[][+[]]][+[]][++[++[[]][+[]]][+[]]]]
    et de la même façon que dans le chapitre V, le c, le o et le v, mais aussi le caractère espace ainsi que les crochets, les accolades et les parenthèses.

    C'est un gros bond en avant, mais je n'ai pas réussi pour le moment à aller plus loin avec les 3 caractères +[]. En effet, les lettres C O V ne nous débloquent pas vraiment. Les seules nouvelles fonctions accessibles grâce à ces lettres sont #String.concat ou #Array.concat.

    Pour aller plus loin, la piste la plus sérieuse serait de réussir à obtenir la valeur null, ce qui nous donnerait la lettre L puis la fonction #Function.call. A partir de là, tout est possible Mais aucune fonction accessible dans la norme ECMAScript actuelle ne nous renvoie null
  • SylvainPV
    Rédacteur/Modérateur
    Je lui ai lâché un message sur le github du projet, mais pas de réponse pour le moment

    Si on voulait vraiment pousser la réflexion jusqu'au bout, il faudrait travailler l'optimisation de toute la table de caractères, puis comparer la taille du code généré avec ou sans l'usage de String.fromCharCode

    Mais je crois que ça dépasse le seuil d'intérêt que l'on puisse légitimement prêter à ce problème
  • rambc
    Membre chevronné
    Très rigolo !!!

    Existe-t-il un équivalent pour le langage Python ou est-ce propre à JS ?
  • SylvainPV
    Rédacteur/Modérateur
    Je ne connais pas assez bien le Python pour répondre, mais je suis sûr que d'autres s'amuseraient beaucoup à faire la même chose sur d'autres langages

    Envoyé par Quentin de Serres-Justiniac

    Bonjour,

    Après m'être délecté de la lecture de cet article : http://sylvainpv.developpez.com/tuto...-6-caracteres/ qui m'a fait sourire bon nombre de fois.
    Je m'interrogeais sur l'utilité d'une telle approche. Hormis le côté ludique et la prouesse technique, est-ce que cela a un intérêt ?
    Une fois compressé le code est-il plus léger ? Vu le peu de caractères différent, je me dis qu'un bon GZ fera sans doute des ravages dans la taille du code. Ceci dit l'extrême lourdeur de l'écriture pourrait je pense alourdir de façon bien trop considérable le code pour que même minifié et gzippé ce soit encore trop lourd.
    (Je laisse de côté volontairement la portabilité).

    Bonne journée et merci pour cet article fun à lire le matin en arrivant au bureau
    En effet hormis le côté ludique, il n'y a strictement aucun intérêt pratique. Comme je le dis en avant-propos : "Si le défi ne présente pas d'intérêt en soi, il permet en revanche de manière très ludique d'appréhender divers aspects du langage sous un angle tout à fait inhabituel"

    Le code généré est largement plus long que l'original, comme tu as pu le constater si tu as essayé le compilateur. Eventuellement on pourrait s'y intéresser à des fins d'obfuscation de code extrême, mais je ne pense pas que ce soit la meilleure approche.
  • elias551
    Membre du Club
    Très instructif tout ça!

    En Perl aussi on peut utiliser les regex à outrance, ce qui peut donner ça
  • SylvainPV
    Rédacteur/Modérateur
    Voici une présentation en anglais parcourant de manière plus synthétique tout le cheminement pour arriver au compilateur:
    http://slides.com/sylvainpv/xchars-js/

    Le compilateur a également été mis à jour pour fonctionner avec Chrome, Firefox et Edge dans leurs dernières versions.

    Dernière chose, une démonstration du 1K chess de Óscar Toledo G.convertie via 6chars.js est jouable ici : http://syllab.fr/projets/experiments/sixcharsjs/nanochess.html

    Regardez la source