129 votes

Pouvez-vous ajouter de nouvelles instructions à la syntaxe de Python ?

Pouvez-vous ajouter de nouvelles déclarations (comme print , raise , with ) à la syntaxe de Python ?

Dis, pour permettre..

mystatement "Something"

Ou,

new_if True:
    print "example"

Pas tant que ça si vous debe mais plutôt si c'est possible (sans modifier le code de l'interpréteur python).

12 votes

Dans un autre ordre d'idées, un cas d'utilisation où il pourrait être pratique de créer de nouvelles instructions à la volée (par opposition à une "extension" sérieuse du langage) est celui des personnes qui utilisent l'interpréteur interactif comme une calculatrice, ou même un shell de système d'exploitation. Je crée souvent de petites fonctions à la volée pour faire quelque chose que je vais répéter, et dans ces situations, il serait agréable de créer des commandes très abrégées comme des macros ou des instructions plutôt que de taper les longs noms avec la syntaxe function(). Bien sûr, ce n'est pas vraiment le but de Py, mais les gens passent beaucoup de temps à l'utiliser de manière interactive.

5 votes

@Kilo il pourrait être intéressant de regarder ipython - il a beaucoup de fonctionnalités de type shell, par exemple vous pouvez utiliser les commandes régulières "ls" et "cd", la complétion de tabulation, beaucoup de fonctionnalités de type macro etc

0 votes

Certains langages sont exquisément extensibles, par exemple Forth et Smalltalk, mais leurs paradigmes de langage sont également différents de ceux utilisés par Python. Avec ces deux langages, tout nouveau mot (Forth) ou toute nouvelle méthode (Smalltalk) devient une partie intégrante et indissociable du langage pour cette installation. Ainsi, chaque installation Forth ou Smalltalk devient une création unique au fil du temps. De plus, Forth est basé sur RPN. Mais en pensant aux DSL, quelque chose comme cela devrait être réalisable en Python. Mais, comme d'autres l'ont dit ici, pourquoi ?

169voto

Eli Bendersky Points 82298

Vous pourriez trouver cela utile - Internes Python : ajouter une nouvelle déclaration à Python cité ici :


Cet article est une tentative pour mieux comprendre le fonctionnement de l'interface de Python. La simple lecture de la documentation et du code source peut être un peu ennuyeuse, c'est pourquoi j'adopte ici une approche pratique : Je vais ajouter un until à Python.

Tout le codage de cet article a été effectué sur la branche Py3k de pointe dans la bibliothèque de l'UE. Miroir du dépôt Mercurial de Python .

El until déclaration

Certains langages, comme Ruby, disposent d'un until qui est le complément de while ( until num == 0 est équivalent à while num != 0 ). En Ruby, je peux écrire :

num = 3
until num == 0 do
  puts num
  num -= 1
end

Et il sera imprimé :

3
2
1

Je souhaite donc ajouter une capacité similaire à Python. C'est-à-dire, être capable d'écrire :

num = 3
until num == 0:
  print(num)
  num -= 1

Une digression sur la défense des langues

Cet article n'a pas pour but de suggérer l'ajout d'une until à Python. Bien que je pense qu'une telle déclaration rendrait certains codes plus clairs, et que cet article montre combien il est facile de l'ajouter, je respecte totalement la philosophie de minimalisme de Python. Tout ce que j'essaie de faire ici, vraiment, c'est d'avoir un aperçu du fonctionnement interne de Python.

Modifier la grammaire

Python utilise un générateur d'analyseur personnalisé nommé pgen . Il s'agit d'un analyseur LL(1) qui convertit le code source Python en un arbre d'analyse. L'entrée du générateur de l'analyseur est le fichier Grammar/Grammar [1] . Il s'agit d'un simple fichier texte qui spécifie la grammaire de Python.

[1] : À partir d'ici, les références aux fichiers dans le source Python sont données par rapport à la racine de l'arbre source, qui est le répertoire où vous exécutez configure et make pour construire Python.

Deux modifications doivent être apportées au fichier de grammaire. La première consiste à ajouter une définition de l'élément until déclaration. J'ai trouvé où le while a été définie ( while_stmt ), et ajouté until_stmt en dessous de [2] :

compound_stmt: if_stmt | while_stmt | until_stmt | for_stmt | try_stmt | with_stmt | funcdef | classdef | decorated
if_stmt: 'if' test ':' suite ('elif' test ':' suite)* ['else' ':' suite]
while_stmt: 'while' test ':' suite ['else' ':' suite]
until_stmt: 'until' test ':' suite

[2] : Ceci démontre une technique courante que j'utilise lorsque je modifie un code source avec lequel je ne suis pas familier : travail par similitude . Ce principe ne résoudra pas tous vos problèmes, mais il peut certainement faciliter le processus. Puisque tout ce qui doit être fait pour while doit également être fait pour until mais il s'agit d'une bonne ligne directrice.

Notez que j'ai décidé d'exclure l'élément else de ma définition de until juste pour le rendre un peu différent (et parce que franchement, je n'aime pas la méthode de l'arbre à cames). else clause de boucles et ne pense pas que cela corresponde bien au Zen de Python).

Le deuxième changement consiste à modifier la règle pour compound_stmt d'inclure until_stmt comme vous pouvez le voir dans l'extrait ci-dessus. C'est juste après while_stmt encore.

Lorsque vous exécutez make après avoir modifié Grammar/Grammar , remarquez que le pgen est exécuté pour re-générer Include/graminit.h y Python/graminit.c et plusieurs fichiers sont alors recompilés.

Modifier le code de génération AST

Une fois que l'analyseur Python a créé un arbre d'analyse, cet arbre est converti en un AST, puisque les AST sont des systèmes de gestion de l'information. beaucoup plus simple à utiliser dans les étapes ultérieures du processus de compilation.

Donc, nous allons visiter Parser/Python.asdl qui définit la structure des AST de Python et ajoute un nœud AST pour notre nouvelle version de l'outil until encore une fois, juste en dessous de la while :

| While(expr test, stmt* body, stmt* orelse)
| Until(expr test, stmt* body)

Si vous exécutez maintenant make remarquez qu'avant de compiler un tas de fichiers, Parser/asdl_c.py est exécuté pour générer du code C à partir du fichier de définition AST. Ceci (comme Grammar/Grammar ) est un autre exemple du code source de Python qui utilise un mini-langage (en d'autres termes, un DSL) pour simplifier la programmation. Notez également que, puisque Parser/asdl_c.py est un script Python, c'est une sorte de bootstrapping - pour construire Python à partir de zéro, il faut déjà que Python soit disponible.

Alors que Parser/asdl_c.py a généré le code pour gérer notre nœud AST nouvellement défini (dans les fichiers Include/Python-ast.h y Python/Python-ast.c ), nous devons encore écrire à la main le code qui convertit un nœud d'arbre d'analyse en un tel nœud. Ceci est fait dans le fichier Python/ast.c . Là, une fonction nommée ast_for_stmt convertit les nœuds de l'arbre d'analyse pour les déclarations en nœuds AST. Encore une fois, guidé par notre vieil ami while on saute directement dans le grand switch pour le traitement des déclarations composées et ajouter une clause pour until_stmt :

case while_stmt:
    return ast_for_while_stmt(c, ch);
case until_stmt:
    return ast_for_until_stmt(c, ch);

Maintenant, nous devons mettre en œuvre ast_for_until_stmt . C'est ici :

static stmt_ty
ast_for_until_stmt(struct compiling *c, const node *n)
{
    /* until_stmt: 'until' test ':' suite */
    REQ(n, until_stmt);

    if (NCH(n) == 4) {
        expr_ty expression;
        asdl_seq *suite_seq;

        expression = ast_for_expr(c, CHILD(n, 1));
        if (!expression)
            return NULL;
        suite_seq = ast_for_suite(c, CHILD(n, 3));
        if (!suite_seq)
            return NULL;
        return Until(expression, suite_seq, LINENO(n), n->n_col_offset, c->c_arena);
    }

    PyErr_Format(PyExc_SystemError,
                 "wrong number of tokens for 'until' statement: %d",
                 NCH(n));
    return NULL;
}

Encore une fois, ceci a été codé en regardant de près l'équivalent ast_for_while_stmt avec la différence que pour until J'ai décidé de ne pas soutenir le else clause. Comme prévu, l'AST est créé de manière récursive, en utilisant d'autres fonctions de création d'AST telles que ast_for_expr pour l'expression de la condition et ast_for_suite pour le corps de la until déclaration. Enfin, un nouveau nœud nommé Until est renvoyé.

Notez que nous accédons au nœud de l'arbre d'analyse syntaxique n en utilisant des macros comme NCH y CHILD . Ils méritent d'être compris - leur code est dans Include/node.h .

Digression : Composition de l'AST

J'ai choisi de créer un nouveau type d'AST pour les until mais ce n'est pas nécessaire. J'aurais pu économiser un peu de travail et mettre en œuvre la nouvelle fonctionnalité en utilisant la composition de nœuds AST existants, puisque :

until condition:
   # do stuff

Est fonctionnellement équivalent à :

while not condition:
  # do stuff

Au lieu de créer le Until nœud dans ast_for_until_stmt j'aurais pu créer un Not avec un While en tant qu'enfant. Comme le compilateur AST sait déjà comment traiter ces nœuds, les étapes suivantes du processus peuvent être ignorées.

Compilation des AST en bytecode

L'étape suivante consiste à compiler l'AST en bytecode Python. La compilation a un résultat intermédiaire qui est un CFG (Control Flow Graph), mais puisque le même code le gère, j'ignorerai ce détail pour le moment et le laisserai pour un autre article.

Le code que nous allons examiner ensuite est Python/compile.c . Suivant l'exemple de while on trouve la fonction compiler_visit_stmt qui est responsable de la compilation des instructions en bytecode. Nous ajoutons une clause pour Until :

case While_kind:
    return compiler_while(c, s);
case Until_kind:
    return compiler_until(c, s);

Si vous vous demandez ce que Until_kind c'est une constante (en fait une valeur de l'option _stmt_kind ) généré automatiquement à partir du fichier de définition de l'AST en Include/Python-ast.h . Quoi qu'il en soit, nous appelons compiler_until qui, bien sûr, n'existe toujours pas. J'y reviendrai dans un instant.

Si vous êtes curieux comme moi, vous remarquerez que compiler_visit_stmt est particulier. Aucune quantité de grep -La recherche dans l'arbre source révèle l'endroit où il est appelé. Lorsque c'est le cas, il ne reste qu'une seule option - le macro-fu C. En effet, une courte investigation nous conduit à la VISIT définie dans Python/compile.c :

#define VISIT(C, TYPE, V) {\
    if (!compiler_visit_ ## TYPE((C), (V))) \
        return 0; \

Il est utilisé pour invoquer compiler_visit_stmt en compiler_body . Mais revenons à nos affaires...

Comme promis, voici compiler_until :

static int
compiler_until(struct compiler *c, stmt_ty s)
{
    basicblock *loop, *end, *anchor = NULL;
    int constant = expr_constant(s->v.Until.test);

    if (constant == 1) {
        return 1;
    }
    loop = compiler_new_block(c);
    end = compiler_new_block(c);
    if (constant == -1) {
        anchor = compiler_new_block(c);
        if (anchor == NULL)
            return 0;
    }
    if (loop == NULL || end == NULL)
        return 0;

    ADDOP_JREL(c, SETUP_LOOP, end);
    compiler_use_next_block(c, loop);
    if (!compiler_push_fblock(c, LOOP, loop))
        return 0;
    if (constant == -1) {
        VISIT(c, expr, s->v.Until.test);
        ADDOP_JABS(c, POP_JUMP_IF_TRUE, anchor);
    }
    VISIT_SEQ(c, stmt, s->v.Until.body);
    ADDOP_JABS(c, JUMP_ABSOLUTE, loop);

    if (constant == -1) {
        compiler_use_next_block(c, anchor);
        ADDOP(c, POP_BLOCK);
    }
    compiler_pop_fblock(c, LOOP, loop);
    compiler_use_next_block(c, end);

    return 1;
}

J'ai une confession à faire : ce code n'a pas été écrit sur la base d'une compréhension profonde du bytecode Python. Comme le reste de l'article, il a été fait en imitant le kin compiler_while fonction. Cependant, en le lisant attentivement, en gardant à l'esprit que la VM Python est basée sur la pile, et en jetant un coup d'œil à la documentation de la fonction dis qui a une liste de bytecodes Python avec des descriptions, il est possible de comprendre ce qui se passe.

C'est ça, on a fini... N'est-ce pas ?

Après avoir effectué tous les changements et exécuté make nous pouvons exécuter le Python nouvellement compilé et essayer notre nouveau until déclaration :

>>> until num == 0:
...   print(num)
...   num -= 1
...
3
2
1

Voila, ça marche ! Voyons le bytecode créé pour la nouvelle instruction en utilisant la fonction dis comme suit :

import dis

def myfoo(num):
    until num == 0:
        print(num)
        num -= 1

dis.dis(myfoo)

Voici le résultat :

4           0 SETUP_LOOP              36 (to 39)
      >>    3 LOAD_FAST                0 (num)
            6 LOAD_CONST               1 (0)
            9 COMPARE_OP               2 (==)
           12 POP_JUMP_IF_TRUE        38

5          15 LOAD_NAME                0 (print)
           18 LOAD_FAST                0 (num)
           21 CALL_FUNCTION            1
           24 POP_TOP

6          25 LOAD_FAST                0 (num)
           28 LOAD_CONST               2 (1)
           31 INPLACE_SUBTRACT
           32 STORE_FAST               0 (num)
           35 JUMP_ABSOLUTE            3
      >>   38 POP_BLOCK
      >>   39 LOAD_CONST               0 (None)
           42 RETURN_VALUE

L'opération la plus intéressante est la numéro 12 : si la condition est vraie, nous sautons à la fin de la boucle. C'est une sémantique correcte pour until . Si le saut n'est pas exécuté, le corps de la boucle continue de fonctionner jusqu'à ce qu'il revienne à la condition de l'opération 35.

En me sentant bien avec mon changement, j'ai ensuite essayé d'exécuter la fonction (en exécutant myfoo(3) ) au lieu de montrer son bytecode. Le résultat était moins qu'encourageant :

Traceback (most recent call last):
  File "zy.py", line 9, in
    myfoo(3)
  File "zy.py", line 5, in myfoo
    print(num)
SystemError: no locals when loading 'print'

Whoa... ça ne peut pas être bon. Alors qu'est-ce qui a mal tourné ?

Le cas du tableau des symboles manquants

L'une des étapes que le compilateur Python effectue lors de la compilation de l'AST est la création d'une table de symboles pour le code qu'il compile. L'appel à PySymtable_Build en PyAST_Compile appelle le module de la table des symboles ( Python/symtable.c ), qui parcourt l'AST d'une manière similaire aux fonctions de génération de code. Le fait de disposer d'une table de symboles pour chaque portée aide le compilateur à déterminer certaines informations clés, telles que les variables globales et les variables locales à une portée.

Pour résoudre le problème, nous devons modifier le fichier symtable_visit_stmt fonction dans Python/symtable.c l'ajout de code pour la gestion des until après le code similaire pour while déclarations [3] :

case While_kind:
    VISIT(st, expr, s->v.While.test);
    VISIT_SEQ(st, stmt, s->v.While.body);
    if (s->v.While.orelse)
        VISIT_SEQ(st, stmt, s->v.While.orelse);
    break;
case Until_kind:
    VISIT(st, expr, s->v.Until.test);
    VISIT_SEQ(st, stmt, s->v.Until.body);
    break;

[3] : A propos, sans ce code, il y a un avertissement du compilateur pour Python/symtable.c . Le compilateur remarque que le Until_kind n'est pas prise en compte dans l'instruction switch de l'option symtable_visit_stmt et se plaint. Il est toujours important de vérifier les avertissements du compilateur !

Et maintenant, nous avons vraiment terminé. La compilation de la source après ce changement rend l'exécution de myfoo(3) fonctionnent comme prévu.

Conclusion

Dans cet article, je vous ai montré comment ajouter une nouvelle instruction à Python. Bien que nécessitant un peu de bricolage dans le code du compilateur Python, le changement n'a pas été difficile à mettre en œuvre, car j'ai utilisé une instruction similaire et existante comme ligne directrice.

Le compilateur Python est un logiciel sophistiqué, et je ne prétends pas en être un expert. Cependant, je suis vraiment intéressé par les aspects internes de Python, et en particulier par son interface. Par conséquent, j'ai trouvé cet exercice très utile pour compléter l'étude théorique des principes et du code source du compilateur. Il servira de base à de futurs articles qui approfondiront le compilateur.

Références

J'ai utilisé quelques excellentes références pour la construction de cet article. Les voici, sans ordre particulier :

  • PEP 339 : Conception du compilateur CPython - probablement la pièce la plus importante et la plus complète de officiel la documentation du compilateur Python. Très court, il illustre douloureusement la rareté d'une bonne documentation sur les éléments internes de Python.
  • "Internes du compilateur Python" - un article de Thomas Lee
  • "Python : Conception et mise en œuvre" - une présentation de Guido van Rossum
  • Machine virtuelle Python (2.5), une visite guidée - une présentation de Peter Tröger

source originale

55voto

Brian Points 48423

Une façon de faire ce genre de choses est de prétraiter la source et de la modifier, en traduisant votre déclaration ajoutée en python. Cette approche pose divers problèmes et je ne la recommanderais pas pour un usage général, mais pour l'expérimentation du langage ou la métaprogrammation à des fins spécifiques, elle peut parfois être utile.

Par exemple, disons que nous voulons introduire une instruction "myprint" qui, au lieu d'imprimer à l'écran, enregistre dans un fichier spécifique :

myprint "This gets logged to file"

serait équivalent à

print >>open('/tmp/logfile.txt','a'), "This gets logged to file"

Il existe plusieurs options quant à la manière de procéder au remplacement, de la substitution par regex à la génération d'un AST, en passant par l'écriture de votre propre analyseur syntaxique en fonction de la proximité de votre syntaxe avec le python existant. Une bonne approche intermédiaire consiste à utiliser le module tokenizer. Cela devrait vous permettre d'ajouter de nouveaux mots-clés, des structures de contrôle, etc. tout en interprétant la source de manière similaire à l'interpréteur python, évitant ainsi les ruptures que les solutions de regex grossières provoqueraient. Pour le "myprint" ci-dessus, vous pourriez écrire le code de transformation suivant :

import tokenize

LOGFILE = '/tmp/log.txt'
def translate(readline):
    for type, name,_,_,_ in tokenize.generate_tokens(readline):
        if type ==tokenize.NAME and name =='myprint':
            yield tokenize.NAME, 'print'
            yield tokenize.OP, '>>'
            yield tokenize.NAME, "open"
            yield tokenize.OP, "("
            yield tokenize.STRING, repr(LOGFILE)
            yield tokenize.OP, ","
            yield tokenize.STRING, "'a'"
            yield tokenize.OP, ")"
            yield tokenize.OP, ","
        else:
            yield type,name

(Cela fait de myprint un mot-clé, donc l'utiliser comme variable ailleurs causera probablement des problèmes).

Le problème est alors de savoir comment l'utiliser pour que votre code soit utilisable depuis python. Une solution serait d'écrire votre propre fonction d'importation et de l'utiliser pour charger du code écrit dans votre langage personnalisé :

import new
def myimport(filename):
    mod = new.module(filename)
    f=open(filename)
    data = tokenize.untokenize(translate(f.readline))
    exec data in mod.__dict__
    return mod

Cela exige toutefois que vous traitiez votre code personnalisé différemment des modules python normaux, c'est-à-dire " some_mod = myimport("some_mod.py") " plutôt que " import some_mod "

Une autre solution assez soignée (bien que bricolée) consiste à créer un encodage personnalisé (voir PEP 263 ) comme este La recette démontre. Vous pourriez l'implémenter comme :

import codecs, cStringIO, encodings
from encodings import utf_8

class StreamReader(utf_8.StreamReader):
    def __init__(self, *args, **kwargs):
        codecs.StreamReader.__init__(self, *args, **kwargs)
        data = tokenize.untokenize(translate(self.stream.readline))
        self.stream = cStringIO.StringIO(data)

def search_function(s):
    if s!='mylang': return None
    utf8=encodings.search_function('utf8') # Assume utf8 encoding
    return codecs.CodecInfo(
        name='mylang',
        encode = utf8.encode,
        decode = utf8.decode,
        incrementalencoder=utf8.incrementalencoder,
        incrementaldecoder=utf8.incrementaldecoder,
        streamreader=StreamReader,
        streamwriter=utf8.streamwriter)

codecs.register(search_function)

Maintenant, une fois que ce code est exécuté (par exemple, vous pouvez le placer dans votre .pythonrc ou site.py), tout code commençant par le commentaire "# coding : mylang" sera automatiquement traduit par l'étape de prétraitement ci-dessus. par exemple.

# coding: mylang
myprint "this gets logged to file"
for i in range(10):
    myprint "so does this : ", i, "times"
myprint ("works fine" "with arbitrary" + " syntax" 
  "and line continuations")

Mises en garde :

L'approche du préprocesseur présente des problèmes, comme vous le savez probablement si vous avez travaillé avec le préprocesseur C. Le principal problème est le débogage. Tout ce que voit python est le fichier prétraité, ce qui signifie que le texte imprimé dans la trace de la pile, etc. fera référence à ce fichier. Si vous avez effectué une traduction importante, ce texte peut être très différent de votre texte source. L'exemple ci-dessus ne change pas les numéros de ligne, etc., et ne sera donc pas trop différent, mais plus vous le modifiez, plus il sera difficile de le comprendre.

14 votes

Joli coup ! Au lieu de dire "c'est pas possible", vous donnez quelques bonnes réponses (qui se résument à "vous ne voulez vraiment pas faire ça") Upvote.

0 votes

Je ne suis pas sûr de comprendre comment fonctionne le premier exemple - en essayant d'utiliser myimport sur un module qui contient simplement print 1 car sa seule ligne de code rapporte =1 ... SyntaxError: invalid syntax

0 votes

@noam : je ne suis pas sûr de ce qui échoue pour vous - ici j'obtiens juste "1" imprimé comme prévu. (C'est avec les 2 blocs commençant par "import tokenize" et "import new" ci-dessus mis dans le fichier a.py, ainsi que " b=myimport("b.py") ", et b.py ne contenant que " print 1 ". Y a-t-il autre chose dans cette erreur (trace de la pile, etc.) ?

21voto

Constantin Points 12185

Oui, dans une certaine mesure, c'est possible. Il existe un module qui utilise sys.settrace() pour mettre en œuvre goto y comefrom "keywords" :

from goto import goto, label
for i in range(1, 10):
  for j in range(1, 20):
    print i, j
    if j == 3:
      goto .end # breaking out from nested loop
label .end
print "Finished"

4 votes

Ce n'est pas vraiment une nouvelle syntaxe... ça y ressemble juste.

5 votes

-1 : La page liée a ce titre : "Le module 'goto' était une blague de poisson d'avril, publiée le 1er avril 2004. Oui, il fonctionne, mais c'est néanmoins une blague. Veuillez ne pas l'utiliser dans du vrai code !"

7 votes

@Jim pourrait reconsidérer un -1. Il vous donne des indications sur le mécanisme de mise en œuvre. une bonne chose pour commencer.

14voto

paxdiablo Points 341644

Il ne suffit pas de modifier et de recompiler le code source (qui es possible avec l'open source), changer la langue de base n'est pas vraiment possible.

Même si vous recompilez les sources, il ne s'agira pas de python, mais seulement de votre version modifiée et bidouillée dans laquelle vous devez faire très attention à ne pas introduire de bogues.

Cependant, je ne suis pas sûr de savoir pourquoi vous le voudriez. Les caractéristiques orientées objet de Python font qu'il est assez simple d'obtenir des résultats similaires avec le langage tel qu'il est.

2 votes

Je ne suis pas d'accord sur un point. Si vous ajouter de nouveaux mots-clés, je pense que ce serait toujours Python. Si vous changement des mots-clés existants, alors c'est juste du bidouillage, comme vous dites.

11 votes

Si vous ajoutez de nouveaux mots-clés, il s'agira d'un langage dérivé de Python. Si vous changez les mots-clés, il s'agira d'un langage incompatible avec Python.

1 votes

Si vous ajoutez des mots-clés, vous risquez de passer à côté de l'intérêt de la "syntaxe simple et facile à apprendre" et des "bibliothèques étendues". Je pense que les caractéristiques d'un langage sont presque toujours une erreur (les exemples incluent COBOL, Perl et PHP).

12voto

Réponse générale : vous devez prétraiter vos fichiers sources.

Réponse plus spécifique : installer EasyExtend et suivez les étapes suivantes

i) Créer un nouveau langlet (langue d'extension)

import EasyExtend
EasyExtend.new_langlet("mystmts", prompt = "my> ", source_ext = "mypy")

Sans spécification supplémentaire, un ensemble de fichiers sera créé sous EasyExtend/langlets/mystmts/ .

ii) Ouvrez mystmts/parsedef/Grammar.ext et ajoutez les lignes suivantes

small_stmt: (expr_stmt | print_stmt  | del_stmt | pass_stmt | flow_stmt |
             import_stmt | global_stmt | exec_stmt | assert_stmt | my_stmt )

my_stmt: 'mystatement' expr

Cela suffit pour définir la syntaxe de votre nouvelle déclaration. Le non-terminal small_stmt fait partie de la grammaire Python et c'est l'endroit où la nouvelle instruction est accrochée. L'analyseur syntaxique reconnaîtra maintenant la nouvelle instruction, c'est-à-dire qu'un fichier source qui la contient sera analysé. Le compilateur la rejettera cependant car elle doit encore être transformée en Python valide.

iii) Il faut maintenant ajouter la sémantique de la déclaration. Pour cela, il faut éditer msytmts/langlet.py et ajouter un visiteur de noeud my_stmt.

 def call_my_stmt(expression):
     "defines behaviour for my_stmt"
     print "my stmt called with", expression

 class LangletTransformer(Transformer):
       @transform
       def my_stmt(self, node):
           _expr = find_node(node, symbol.expr)
           return any_stmt(CST_CallFunc("call_my_stmt", [_expr]))

 __publish__ = ["call_my_stmt"]

iv) faites un cd dans langlets/mystmts et tapez

python run_mystmts.py

Maintenant, une session doit être lancée et la déclaration nouvellement définie peut être utilisée :

__________________________________________________________________________________

 mystmts

 On Python 2.5.1 (r251:54863, Apr 18 2007, 08:51:08) [MSC v.1310 32 bit (Intel)]
 __________________________________________________________________________________

 my> mystatement 40+2
 my stmt called with 42

Pas mal d'étapes pour arriver à une déclaration banale, non ? Il n'existe pas encore d'API permettant de définir des choses simples sans avoir à se soucier des grammaires. Mais EE est très fiable, à l'exception de quelques bogues. Ce n'est donc qu'une question de temps pour qu'une API émerge et permette aux programmeurs de définir des choses pratiques comme des opérateurs infixes ou de petites déclarations en utilisant simplement une programmation OO pratique. Pour des choses plus complexes comme l'intégration de langages entiers dans Python par la construction d'un langlet, il n'y a aucun moyen de contourner l'approche de la grammaire complète.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X