Python Language Partie 1: Tokenizing Input avec Lex


Exemple

Il y a deux étapes que le code de l' exemple 1 effectués: l' un a été l'entrée de jetons, ce qui signifie qu'il a cherché symboles qui constituent l'expression arithmétique, et la seconde étape est l' analyse, qui consiste à analyser les jetons extraits et évaluer le résultat.

Cette section fournit un exemple simple de tokenize de saisie utilisateur, puis la décompose ligne par ligne.

    import ply.lex as lex

    # List of token names. This is always required
    tokens = [
       'NUMBER',
       'PLUS',
       'MINUS',
       'TIMES',
       'DIVIDE',
       'LPAREN',
       'RPAREN',
    ]

    # Regular expression rules for simple tokens
    t_PLUS    = r'\+'
    t_MINUS   = r'-'
    t_TIMES   = r'\*'
    t_DIVIDE  = r'/'
    t_LPAREN  = r'\('
    t_RPAREN  = r'\)'

    # A regular expression rule with some action code
    def t_NUMBER(t):
        r'\d+'
        t.value = int(t.value)    
        return t

    # Define a rule so we can track line numbers
    def t_newline(t):
        r'\n+'
        t.lexer.lineno += len(t.value)

    # A string containing ignored characters (spaces and tabs)
    t_ignore  = ' \t'

    # Error handling rule
    def t_error(t):
        print("Illegal character '%s'" % t.value[0])
        t.lexer.skip(1)

    # Build the lexer
    lexer = lex.lex()

    # Give the lexer some input
    lexer.input(data)

    # Tokenize
    while True:
        tok = lexer.token()
        if not tok: 
            break      # No more input
        print(tok)

Enregistrez ce fichier sous le nom calclex.py . Nous l'utiliserons lors de la construction de notre analyseur Yacc.


Panne

  1. Importer le module en utilisant import ply.lex

  2. Tous les lexers doivent fournir une liste appelée tokens qui définit tous les noms de jetons possibles pouvant être produits par le lexer. Cette liste est toujours requise.

     tokens = [
        'NUMBER',
        'PLUS',
        'MINUS',
        'TIMES',
        'DIVIDE',
        'LPAREN',
        'RPAREN',
     ]
    

tokens peuvent aussi être un tuple de chaînes (plutôt qu'une chaîne), où chaque chaîne indique un jeton comme auparavant.

  1. La règle de regex pour chaque chaîne peut être définie comme une chaîne ou une fonction. Dans les deux cas, le nom de la variable doit être préfixé par t_ pour indiquer qu'il s'agit d'une règle pour les jetons correspondants.

    • Pour les jetons simples, l'expression régulière peut être spécifiée sous la forme de chaînes: t_PLUS = r'\+'

    • Si une action doit être exécutée, une règle de jeton peut être spécifiée en tant que fonction.

         def t_NUMBER(t):
             r'\d+'
             t.value = int(t.value)
             return t
      

      Notez que la règle est spécifiée comme une chaîne de caractères dans la fonction. La fonction accepte un argument qui est une instance de LexToken , effectue une action puis renvoie l'argument.

      Si vous souhaitez utiliser une chaîne externe comme règle d'expression régulière pour la fonction au lieu de spécifier une chaîne de document, prenez l'exemple suivant:

         @TOKEN(identifier)         # identifier is a string holding the regex
         def t_ID(t):
             ...      # actions
      
    • Une instance d'objet LexToken (appelons cet objet t ) a les attributs suivants:

      1. t.type qui est le type de jeton (sous forme de chaîne) (par exemple: 'NUMBER' , 'PLUS' , etc.). Par défaut, t.type est défini sur le nom suivant le préfixe t_ .
      2. t.value qui est le lexème (le texte réel correspondant)
      3. t.lineno qui est le numéro de ligne actuel (ce n'est pas automatiquement mis à jour, car le lexer ne sait rien des numéros de ligne). Mettez à jour lineno en utilisant une fonction appelée t_newline .

        def t_newline(t):
            r'\n+'
            t.lexer.lineno += len(t.value)
      

      1. t.lexpos qui est la position du jeton par rapport au début du texte saisi.
    • Si rien n'est renvoyé par une fonction de règle d'expression régulière, le jeton est ignoré. Si vous souhaitez ignorer un jeton, vous pouvez également ajouter le préfixe t_ignore_ à une variable de règle d'expression régulière au lieu de définir une fonction pour la même règle.

         def t_COMMENT(t):
             r'\#.*'
             pass
             # No return value. Token discarded
      

      ...Est le même que:

         t_ignore_COMMENT = r'\#.*'
      

      Ceci est bien sûr invalide si vous effectuez une action lorsque vous voyez un commentaire. Dans ce cas, utilisez une fonction pour définir la règle regex.

      Si vous n'avez pas défini de jeton pour certains caractères mais que vous souhaitez toujours l'ignorer, utilisez t_ignore = "<characters to ignore>" (ces préfixes sont nécessaires):

         t_ignore_COMMENT = r'\#.*'
         t_ignore  = ' \t'    # ignores spaces and tabs
      

    • Lors de la création de l'expression rationnelle principale, lex ajoutera les expressions régulières spécifiées dans le fichier comme suit:

      1. Les jetons définis par les fonctions sont ajoutés dans le même ordre qu'ils apparaissent dans le fichier.
      2. Les jetons définis par des chaînes sont ajoutés par ordre décroissant de la longueur de chaîne de la chaîne définissant l'expression régulière pour ce jeton.

      Si vous faites correspondre == et = dans le même fichier, profitez de ces règles.

    • Les littéraux sont des jetons renvoyés tels quels. t.type et t.value seront tous deux définis sur le caractère lui-même. Définir une liste de littéraux en tant que tels:

      literals = [ '+', '-', '*', '/' ]
      

      ou,

      literals = "+-*/"
      

      Il est possible d'écrire des fonctions de jeton qui effectuent des actions supplémentaires lorsque les littéraux sont mis en correspondance. Cependant, vous devrez définir le type de jeton de manière appropriée. Par exemple:

      literals = [ '{', '}' ]
      
      def t_lbrace(t):
          r'\{'
          t.type = '{'  # Set token type to the expected literal (ABSOLUTE MUST if this is a literal)
          return t
      

    • Gérer les erreurs avec la fonction t_error.

      # Error handling rule
      def t_error(t):
          print("Illegal character '%s'" % t.value[0])
          t.lexer.skip(1) # skip the illegal token (don't process it)
      

      En général, t.lexer.skip(n) ignore n caractères dans la chaîne d'entrée.

  2. Préparations finales:

    Construisez le lexer en utilisant lexer = lex.lex() .

    Vous pouvez également tout mettre dans une classe et appeler une instance use de la classe pour définir le lexer. Par exemple:

     import ply.lex as lex  
     class MyLexer(object):            
           ...     # everything relating to token rules and error handling comes here as usual 
    
           # Build the lexer
           def build(self, **kwargs):
               self.lexer = lex.lex(module=self, **kwargs)
    
           def test(self, data):
               self.lexer.input(data)
               for token in self.lexer.token():
                   print(token)
    
           # Build the lexer and try it out
    
     m = MyLexer()
     m.build()           # Build the lexer
     m.test("3 + 4")     #
    

    Fournit une entrée à l'aide de lexer.input(data) où data est une chaîne

    Pour obtenir les jetons, utilisez lexer.token() qui renvoie les jetons correspondants. Vous pouvez itérer sur lexer en boucle comme dans:

    for i in lexer: 
        print(i)