menu
  Home  ==>  articles  ==>  colibri_utilities  ==>  interprete_d_expression   

Interprète d'Expression - John COLIBRI.


1 - Introduction

Cet article présente un petit interprète d'expressions arithmétiques. Le but est de pouvoir calculer le résultat d'une expression arithmétique founie dans une STRING. Nous présentons plusieurs versions de l'interprète:
  • un calcul direct
  • un construction d'un arbre symbolique suivi de son interprétation
Ce programme n'a aucune prétention d'universalité. Je m'étais simplement engagé auprès d'un stagiaire à fournir le code d'un interprète simple, et donc voici le programme C'est bien connu, tous les formateurs Delphi fournissent des exemples avec explicatif, figure et code source après les stages qu'ils ont animés...


2 - Un premier interprète simple

2.1 - Principe

Nous avions jadis présenté dans Pascalissime le principe d'un mini-compilateur. Cet article comportait:
  • description de la notation EBNF
  • l'explication parallélisme entre EBNF et les procédure d'un analyseur
  • le principe d'une machine à pile
  • un programme effectuant l'analyse et l'exécution d'un petit programme
Le lecteur pourra se reporter à ces articles.

Ici nous partirons de la grammaire des expressions, puis nous générerons l'analyseur syntaxique et créerons l'interprète manuellement.

2.2 - Un Interprète direct simple

Supposons que nous souhaitions calculer:

    3+ 4*5

Il faudra:

  • analyser la chaîne et isoler les symboles "3", "+", "4", "*" et "5". Ce travail sera réalisé par un analyseur lexical
  • vérifier que l'expression est légale. Par exemple "3++ 5" serait illégal. La vérification sera effectuée par un analyseur syntaxique
  • évaluer l'expression: l'expression est transformée en une structure ou un programme qui évalue le résultat.
Pour l'analyseur lexicale:
  • nous définissons un énuméré qui représente tous les symboles qui nous intéressent. Dans notre cas:

    Pour une expression plus complexe, nous pourrions ajouter:

    • d'autres types e_float_litteral, e_string_litteral
    • d'autres opérateurs: e_SHR_symbol
    • le concept de variable e_identifier_symbol
    • les instruction (e_IF_symbol), les sous-programmes (e_PROCEDURE_symbol...)
  • nous isolons les symboles par une double boucle;
    • une boucle qui saute ce qui ne nous intéresse pas
    • une boucle qui concatène les caractères qui nous intéressent:
    • par exemple, pour récupérer un nombre entier:

                // -- throw away blanks
                while (m_buffer_indexm_buffer_sizeand (m_pt_buffer[m_buffer_index]= ' 'do
                  inc(m_buffer_index);

                // -- concatenate digits
                l_numeric_string:= '';
                while (m_buffer_indexm_buffer_sizeand (m_pt_buffer[m_buffer_indexin ['0'..'9']) do
                begin
                  l_numeric_string:= l_numeric_string+  m_pt_buffer[m_buffer_index];
                  inc(m_buffer_index);
                end;


  • voici alors la procédure qui récupère le type du symbole et son contenu:

        procedure c_scanner.read_symbol;
          const k_blanks= [' 'chr(9)];
                k_returns= [chr(13), chr(10)];
                k_blanks_returnsk_blanksk_returns;

                k_digits= ['0'..'9'];

          procedure get_blanks;
            var l_start_indexInteger;
            begin
              l_start_index:= m_buffer_index;
              while (m_buffer_indexm_buffer_sizeand (m_pt_buffer[m_buffer_indexin k_blanks_returnsdo
                inc(m_buffer_index);
            end// get_blanks

          procedure get_number;
            var l_start_indexInteger;
            begin
              l_start_index:= m_buffer_index;
              while (m_buffer_indexm_buffer_sizeand (m_pt_buffer[m_buffer_indexin k_digitsdo
                inc(m_buffer_index);

              m_symbol_type:= e_integer_symbol;
              m_symbol_string:= f_extract_string_start_end(l_start_indexm_buffer_index- 1);
              m_integer_value:= StrToInt(m_symbol_string);
            end// get_number

          procedure get_operator(p_operator_typet_symbol_type);
            begin
              m_symbol_type:= p_operator_type;
              m_symbol_string:= m_pt_buffer[m_buffer_index];
              Inc(m_buffer_index);
            end// get_operator

          begin // read_symbol
            get_blanks;

            if m_buffer_indexm_buffer_size
              then begin
                  case m_pt_buffer[m_buffer_indexof
                    '0'..'9' : get_number;

                    '(' : get_operator(e_opening_parenthesis_symbol);
                    ')' : get_operator(e_closing_parenthesis_symbol);
                    '+' : get_operator(e_plus_symbol);
                    '-' : get_operator(e_minus_symbol);
                    '*' : get_operator(e_times_symbol);
                    '/' : get_operator(e_slash_symbol);
                    else display_error('unknown character 'm_pt_buffer[m_buffer_index]);
                  end// case
                end
              else begin
                  m_symbol_type:= e_end_symbol;
                  m_symbol_string:= '';
                end;
          end// read_symbol


Pour l'analyse syntaxique:
  • nous commencerons par utiliser la grammaire simple suivante:

      start= expression .
        expression= [ '+' | '-' ] term { ( '+' | '-' ) term } .
            term= factor { ( '*' | '/' ) factor } .
                factor= NUMBER | '(' expression ')' .

    Sans rentrer dans le détail de ce formalisme, vous noterez que les règles les plus emboîtées sont exécutées en priorité. Dans le cas de l'expression:

        1+ 2* 3* 4

    le terme multiplicatif 2* 3* 4 est évalué en premier, puis l'expression additive 1+ 24. C'est le même principe que les boucles emboîtées:

    for pays:= 1 to 10 do
      for ville:= 1 to 100 do
        recense_population ;

    pour chaque pays, la boucle FOR ville est exécutée entièrement avant d'incrémenter pour le pays suivant. De même, pour notre grammaire, la multiplication a la précédence sur l'addition.

  • nous créons les procédures qui analysent l'expression conforme à cette grammaire:

    PROCEDURE parse_start;
      
      PROCEDURE parse_expression;
        
        PROCEDURE parse_term;
          
          PROCEDURE parse_factor;
            BEGIN
              CASE f_symbol_type OF
                e_NUMBER_symbol : 
                    read_symbol;
                e_opening_parenthesis_symbol : 
                    BEGIN
                      // -- skip "("
                      read_symbol;
                      parse_expression;
                      check(e_closing_parenthesis_symbol);
                      // -- skip ")"
                      read_symbol;
                    END// n
                ELSE display_error('NUMBER, (');
              END// case   
            END// parse_factor
        
          BEGIN // parse_term
            parse_factor;

            WHILE f_symbol_type IN [e_times_symbole_divide_symbolDO
            BEGIN
              read_symbol;
              parse_factor;
            END// WHILE {
          END// parse_term
      
        BEGIN // parse_expression
          IF f_symbol_type IN [e_plus_symbole_minus_symbol]
            THEN BEGIN
                // -- unary "+" and "-"
                read_symbol
              END// IF [
          parse_term;

          WHILE f_symbol_type IN [e_plus_symbole_minus_symbolDO
          BEGIN
            read_symbol;
            parse_term;
          END// WHILE {
        END// parse_expression

      BEGIN // parse_start
        parse_expression;
      END// parse_start

    Le parallélisme entre la grammaire et l'appel des procédures PASCAL est immédiat. Nous pouvons générer ce type de code manuellement, ou utiliser un "compilateur de compilateur", ce qui est le cas ici: à partir du texte de la grammaire, un con-compilateur génère automatiquement le texte ci-dessus.

    Dans ce texte:

    • read_symbol est une procédure qui appelle read_symbol de l'analyseur lexical
    • f_symbol_type est le type du symbole courant
    • check est une procédure qui vérifie que le symbole courant est bien du type souhaité et qui lit le symbole suivant si c'est le cas
    • display_error affiche l'erreur
Notez que le texte ci-dessus se contente de vérifier que le texte fourni est conforme à la grammaire. Il n'effectue aucun traitement ou calcul.

Pour l'interprétation

  • dans le cas de la grammaire simple, l'analyse syntaxique peut être accompagnée d'une évaluation:
    • chaque fois que nous trouvons une chaîne numérique nous calculons sa valeur binaire
    • ces valeurs numériques sont remontées par les procédures de l'analyseur qui retourne, en final, la valeur numérique:
  • voici un exemple pour calculer un terme:

            procedure parse_term(var pv_termInteger);

              procedure parse_factor(var pv_factorInteger);
                begin
                  with p_c_scanner do
                  begin
                    case m_symbol_type of
                      e_integer_symbol :
                          begin
                            pv_factor:= StrToInt(m_symbol_string);
                            read_symbol;
                          end;
                      e_opening_parenthesis_symbol :
                          begin
                            read_symbol;

                            parse_expression(pv_factor);

                            if m_symbol_typee_closing_parenthesis_symbol
                              then read_symbol
                              else display_error('manque )');
                          end;
                      else display_error('attend fact: k, var, (: 'm_symbol_string);
                    end// case
                  end// with p_c_scanner
                end// parse_factor

              var l_save_multiply_operatort_symbol_type;
                  l_next_factorInteger;

              begin // parse_term
                with p_c_scanner do
                begin
                  parse_factor(pv_term);

                  while m_symbol_type in [e_times_symbole_slash_symboldo
                  begin
                    l_save_multiply_operator:= m_symbol_type;
                    read_symbol;
                    parse_factor(l_next_factor);
                    case l_save_multiply_operator of
                      e_times_symbol : pv_term:= pv_terml_next_factor;
                      e_slash_symbol : pv_term:= pv_term div l_next_factor;
                    end// case l_save_multiply_operator
                  end// while m_symbol_type
                end// with p_c_scanner
              end// parse_term



3 - Un interprète plus complexe

3.1 - Objectif

Nous allons légèrement compliquer notre interprète. Nous souhaitons calculer:

    taille= 5 ;
    valeur= 4+ taille* 3.14 ;
    superieur= FALSE ;
    fini= ( taille * 7 > valeur ) OR superieur ;
    possible= NOT (valeur / 17 ) < SIN (- taille) ;

Il faut donc ajouter:

  • des opérateurs plus compliqués: "<", "<=" etc
  • la notion de variable: nous désignons par un nom une valeur
  • plusieurs types de variables: des entiers, des réels, des booléens
  • une suite d'instructions d'affectation
  • des fonctions prédéfinies, telles que SIN(30), EXP(3, 5)
Voici la grammaire dont nous sommes parti:

start= { assignment } .
  assignment= NAME ':=' expression ';' .
    expression= simple_expression
          [ ('=' | '<>' | '<' | '<=' | '>' | '>=' ) simple_expression ] .
      simple_expression= [ '+' | '-' ] term { ('+' | '-' | OR ) term } .
        term= factor { ('*' | '/' | DIV | AND ) factor } .
          factor= NUMBER | TRUE | FALSE | NAME | NOT factor | '(' expression ')'
              | [ SIN | COS ] '(' expression ')' | EXP '(' expression ',' expression ')' .

3.2 - Principe

Dans notre nouvelle version:
  • l'analyseur lexical est un peu plus complexe, puisqu'il doit à présent gérer le nom des variables, plusieurs types de valeurs littérales.
  • l'analyseur syntaxique a été dérivé à nouveau de notre grammaire et ne présente pas de grande nouveauté.
  • pour l'évaluation, cela se complique un peu:
    • il faut stocker les valeurs de nos variables
    • les opérations doivent tenir compte du type des données
Nous avons choisi de séparer l'analyse de l'évaluation:
  • l'analyseur syntaxique produit un arbre syntaxique
  • un évaluateur parcourt cet arbre et stocke les résultats dans une mémoire
L'arbre syntactique est simplement une représentation symbolique de notre calcul. Pour une expression simple telle que:

    4+ taille* 3.14 ;

notre arbre aurait l'allure suivante:

Si nous évaluons cet arbre de bas en haut,

  • nous commençons à la racine "+"
  • il faut évaluer les deux sous arbres
    • la valeur du sous arbre "4" est "4"
    • l'autre sous arbre est "*". Il faut évaluer d'abord ses sous-arbres
      • supposons que taille ait la valeur 5
      • l'autre sous-arbre a la valeur 3.14
      le noeud "*" a donc la valeur 15.7
    le noeud "+" a alors la valeur 19.7
La suite des affectation est alors stockée dans une simple liste chaînée dont chaque cellule comporte:
  • le nom de la variable à gauche de ":=" (left hand side, ou lhs)
  • l'expression qui calcule la valeur de la variable (right hand side ou rhs)
La suite d'affectations qui suit:

    taille= 5 ;
    valeur= 4+ taille* 3.14 ;
    superieur= FALSE ;
    fini= ( taille * 7 > valeur ) OR superieur ;

est alors transformés par notre analyseur syntaxique en:

Pour gérer nos variables:

  • nous créons une table qui va mémoriser la valeur de chaque variable
  • chaque cellule contient
    • le nom
    • le type (entier, réel, booléen)
    • la valeur de la variable
  • la structure permet:
    • l'ajout
    • le stockage du type et de la valeur
    • la recherche par nom
Le résultat de notre calcul sera alors la liste des variables avec leur valeur. Pour effectuer ce calcul:
  • nous balayons l'arbre syntaxique dans l'ordre des instructions
  • si une variable n'existe pas, nous l'allouons dans la table des variables
  • l'évaluation de l'expression de droite est effectués, exactement comme pour notre premier exemple, sauf que:
    • si un facteur est un nom de variable, nous allons chercher sa valeur dans la table
    • les calculs tiennent compte des types des variables:
      • + * etc ne sont permis que pour les valeurs numériques
      • AND OR NOT ne sont permis que pour les booléens
      • etc
Par exemple, après le début de l'évaluation de la troisième instruction, la situation serait la suivante:

Notez que:

  • les types et les valeurs des deux premières instructions ont été calculés
  • la troisième variable a été allouée, mais comme nous n'avons pas encore évalué la valeur du membre de droite, nous ne connaissons encore ni le type ni la valeur de la variable supérieur
Après l'évaluation des quatre instructions, nous aurions:

Lors de l'évaluation, les valeurs sont stockées à la fois dans l'arbre et dans la mémoire des variables. Il faut pouvoir:

  • stocker la valeur
  • lire la valeur
Comme nous devons le faire à la fois pour les noeuds de l'arbre et pour les variables, nous avons crée une classe c_value qui contient:
  • le type, défini comme un énuméré (e_integer_type, e_real_type, e_boolean_type)
  • des membres m_integer_value, m_real_value, m_boolean_value
  • les procédures de lecture (f_integer_value ...) et d'écriture (set_integer_value ...)

3.3 - Schéma du traitement

Notre traitement peut donc être schématisé de la façon suivante:

3.4 - Codage

Le projet comporte les unités
  • u_symbol_definition
  • u_c_scanner
  • u_c_data_value
  • u_c_ast_tree
  • u_c_instruction_list
  • u_c_parser
  • u_c_variable_list
  • u_c_interpreter

3.4.1 - Les symboles: u_symbol_definition

Cette unité contient la liste des symboles, ainsi que deux tables permettant:
  • la conversion entre une ponctuation "+" en son symbole e_plus_symbol
  • la conversion d'un mot clé DIV en son symbole e_DIV_symbol
La liste des symboles est la suivante:

TYPE t_symbol_type= (e_unknown_symbol,

            e_integer_litteral_symbole_real_litteral_symbol,

            e_semi_colon_symbol

            e_opening_parenthesis_symbole_closing_parenthesis_symbol,

            e_plus_symbole_minus_symbole_times_symbole_divide_symbol,
            e_less_symbole_greater_symbole_equal_symbol,

            e_becomes_symbol,
            e_less_or_equal_symbole_greater_or_equal_symbol,
            e_different_symbol,

            e_key_word_start,

              e_AND_symbole_OR_symbole_NOT_symbole_DIV_symbol,

              e_TRUE_symbole_FALSE_symbol,
              e_SIN_symbole_COS_symbole_EXP_symbol,

              e_identifier_symbol,

            e_last_symbol);


3.4.2 - L'analyseur lexical: u_c_lexical

La classe a la même structure que l'analyseur lexical simple, sauf qu'il faut analyser plus finement le source. Voici la procédure principale:

    procedure c_scanner.read_symbol;
      const k_blanks= [' 'chr(9)];
            k_returns= [chr(13), chr(10), chr(26)];
            k_blanks_returnsk_blanksk_returns;

            k_letters= ['a'..'z''A'..'Z'];
            k_accents= ['à''â''é''ç''ê''è''î''ô''ù''û'];
            k_digits= ['0'..'9'];
            k_identifiersk_lettersk_accentsk_digits+ ['_'];

      procedure skip_blanks;
        var l_start_indexInteger;
        begin
          l_start_index:= m_buffer_index;
          while (m_buffer_indexm_buffer_sizeand (m_pt_buffer[m_buffer_indexin k_blanks_returnsdo
            inc(m_buffer_index);
        end// skip_blanks

      procedure get_number;
        var l_start_indexInteger;
        begin
          l_start_index:= m_buffer_index;
          m_symbol_type:= e_integer_litteral_symbol;

          while (m_buffer_indexm_buffer_sizeand (m_pt_buffer[m_buffer_indexin ['0'..'9']) do
            inc(m_buffer_index);

          // -- decimal part ?
          if (m_buffer_indexm_buffer_sizeand (m_pt_buffer[m_buffer_index]= '.'and (m_pt_buffer[m_buffer_index+ 1]<> '.')
            then begin
                m_symbol_type:= e_real_litteral_symbol;
                inc(m_buffer_index);
                while (m_buffer_indexm_buffer_sizeand (m_pt_buffer[m_buffer_indexin ['0'..'9']) do
                  inc(m_buffer_index);
              end;

          // -- exponent part
          if (m_buffer_indexm_buffer_sizeand (m_pt_buffer[m_buffer_index]= 'e'or (m_pt_buffer[m_buffer_index]= 'E')
            then begin
                m_symbol_type:= e_real_litteral_symbol;
                inc(m_buffer_index);
                if (m_buffer_indexm_buffer_sizeand (m_pt_buffer[m_buffer_index]= '+'or (m_pt_buffer[m_buffer_index]= '-')
                  then inc(m_buffer_index);
                while (m_buffer_indexm_buffer_sizeand (m_pt_buffer[m_buffer_indexin ['0'..'9']) do
                  inc(m_buffer_index);
              end;

          m_symbol_string:= f_extract_string_start_end(l_start_indexm_buffer_index- 1);
        end// get_number

      procedure get_identifier;
        var l_start_indexInteger;
        begin
          l_start_index:= m_buffer_index;
          Inc(m_buffer_index);
          while (m_buffer_indexm_buffer_sizeand (m_pt_buffer[m_buffer_indexin k_identifiersdo
            inc(m_buffer_index);

          m_symbol_string:= f_extract_string_start_end(l_start_indexm_buffer_index- 1);
          m_symbol_type:= f_key_word_symbol(m_symbol_string);
        end// get_identifier

      procedure get_operator_1(p_operator_typet_symbol_type);
        begin
          m_symbol_type:= p_operator_type;
          m_symbol_string:= m_pt_buffer[m_buffer_index];
          Inc(m_buffer_index, 1);
        end// get_operator_1

      procedure get_operator_2(p_operator_typet_symbol_type);
        begin
          m_symbol_type:= p_operator_type;
          m_symbol_string:= m_pt_buffer[m_buffer_index]+ m_pt_buffer[m_buffer_index+ 1];
          Inc(m_buffer_index, 2);
        end// get_operator_2

      procedure get_operator;
        var l_operatorChar;
        begin
          l_operator:= m_pt_buffer[m_buffer_index];
          Inc(m_buffer_index);

          m_symbol_string:= l_operator;
          m_symbol_type:= g_1_character_punctuations[l_operator];
          if m_symbol_typee_unknown_symbol
            then display_error('unknown');
        end// get_operator

      begin // f_c_symbol
        m_symbol_type:= e_unknown_symbol;
        m_symbol_string:= '';

        skip_blanks;

        if m_buffer_indexm_buffer_size
          then begin
              case m_pt_buffer[m_buffer_indexof
                'a'..'z''A'..'Z''_',
                    'à''â''é''ç''ê''è''î''ô''ù''û'get_identifier;
                '0'..'9' : get_number;

                '/' : get_operator_1(e_divide_symbol);

                ':' : if (m_buffer_indexm_buffer_sizeand (m_pt_buffer[m_buffer_index+ 1]= '=')
                        then get_operator_2(e_becomes_symbol)
                        else display_error('unknown_symbol');

                '<' : if (m_buffer_indexm_buffer_sizeand (m_pt_buffer[m_buffer_index+ 1]= '=')
                        then get_operator_2(e_less_or_equal_symbol)
                        else
                          if (m_buffer_indexm_buffer_sizeand (m_pt_buffer[m_buffer_index+ 1]= '>')
                            then get_operator_2(e_different_symbol)
                            else get_operator_1(e_less_symbol);

                '>' : if (m_buffer_indexm_buffer_sizeand (m_pt_buffer[m_buffer_index+ 1]= '=')
                        then get_operator_2(e_greater_or_equal_symbol)
                        else get_operator_1(e_greater_symbol);

                '=''+''-''*' ,
                ';'',''('')' : get_operator;
                else
                  display_error('unknown_symbol');
              end// case
            end
          else m_symbol_type:= e_last_symbol;
      end// read_symbol

Prenons les deux instructions suivantes comme example:

    taille= 5 ;
    valeur= 4+ taille* 3.14 ;

la trace d'analyse est la suivante:

    6 >taille< identifier
    8 >:=< becomes
    10 >5< integer_litteral
    12 >;< semi_colon
    20 >valeur< identifier
    22 >:=< becomes
    24 >4< integer_litteral
    25 >+< plus
    32 >taille< identifier
    33 >*< times
    38 >3.14< real_litteral
    40 >;< semi_colon
    44 >< last

3.4.3 - L'arbre syntaxique: u_c_ast_tree

Les membres de droite (les expressions à droite de ":=") sont représentées par les classes suivantes:

    type c_nodeclass(c_basic_object)
                   m_c_left_nodem_c_right_nodec_node;
                   m_node_typet_symbol_type;

                   m_c_data_valuec_data_value;

                   Constructor create_node(p_nameStringp_node_typet_symbol_type);
                   function f_display_nodeStringVirtual;
                   procedure display_node;
                   procedure display_node_data_value;

                   Destructor DestroyOverride;
                 end// c_node

         c_identifier_nodeclass(c_node)
                              Constructor create_identifier_node(p_nameString;
                                  p_node_typet_symbol_type);
                            end// c_identifier_node

         c_litteral_nodeclass(c_node)
                            Constructor create_litteral_node(p_nameString;
                                p_node_typet_symbol_type);
                          end// c_litteral_node

         c_binary_operation_nodeclass(c_node)
                                    Constructor create_binary_operation_node(p_nameString;
                                        p_node_typet_symbol_type;
                                        p_c_operand_onep_c_operand_twoc_node);
                                  end// c_operation_node
         c_unary_operation_nodeclass(c_node)
                                   Constructor create_unary_operation_node(p_nameString;
                                       p_node_typet_symbol_typep_c_operandc_node);
                                 end// c_operation_node
         c_function_nodeClass(c_node)
                            Constructor create_function_node(p_nameString;
                               p_node_typet_symbol_typep_c_parameter_onep_c_parameter_twoc_node);
                          end// c_function_node

Les membres de c_node sont:

  • m_name (situé dans c_basic_object) contient le symbole lu:
    • pour les valeur littérales, cette STRING permettra de récupérer la valeur binaire
    • pour les variables, le nom permettra de rechercher la valeur de la variable dans la table des variables
  • m_node_type permettra de tester le type d'opération (plutôt que de comparer des chaînes)
  • m_c_data_value ne sera utilisé que lors de l'évaluation des valeur
Nous avons crée des types différents pour chaque catégorie syntaxique, mais en fait les données sont toutes dans c_node.

3.5 - La liste des instructions: u_c_instruction_list

Les classes utilisées pour représenter la liste d'instruction est la suivante:

    type c_instructionClass(c_basic_object)
                          m_c_next_instructionc_instruction;

                          m_c_rhs_expressionc_node;

                          Constructor create_instruction(p_nameStringp_c_rhs_expressionc_node);
                          procedure display_instruction;
                          procedure display_instruction_data_value;
                          function f_display_instructionString;
                          Destructor DestroyOverride;
                        end// c_instruction

         c_instruction_listClass(c_basic_object)
                               m_c_first_instructionc_instruction;
                               m_instruction_countInteger;

                               Constructor create_instruction_list(p_nameString);
                               function f_instruction_countInteger;

                               procedure add_instruction(p_c_instructionc_instruction);

                               procedure display_instruction_list;
                               procedure display_instruction_list_data_value;

                               Destructor DestroyOverride;
                             end// c_instruction_list

Nous avons utilisé une liste chaînée, car c'est la classe que nous avions utilisé pour présenter le pattern INTERPRETER des Gof. Nous utiliserions actuellement plutôt une tStringList pour ce type de codage.

Dans les deux cas, chaque c_instruction contient:

  • dans m_name le nom de la variable à gauche de ":="
  • dans m_c_rhs_expression la racine de l'arbre syntaxique correspondant au membre à droite de ":="

3.5.1 - L'analyse syntaxique: u_c_parser

La procédure read_symbol simple (sans la génération de l'arbre) est la suivant:

PROCEDURE parse_start;
  
  PROCEDURE parse_assignment;
    
    PROCEDURE parse_expression;
      
      PROCEDURE parse_simple_expression;
        
        PROCEDURE parse_term;
          
          PROCEDURE parse_factor;
            BEGIN
              CASE f_symbol_type OF
                e_NUMBER_symbol : 
                    read_symbol;
                e_TRUE_symbol : 
                    read_symbol;
                e_FALSE_symbol : 
                    read_symbol;
                e_IDENTIFIER_symbol : 
                    read_symbol;
                e_NOT_symbol : 
                    BEGIN
                      read_symbol;
                      parse_factor;
                    END// n
                e_opening_parenthesis_symbol : 
                    BEGIN
                      read_symbol;
                      parse_expression;
                      check(e_closing_parenthesis_symbol);
                      read_symbol;
                    END// n
                e_SIN_symbole_COS_symbol : 
                    BEGIN
                      read_symbol;
                      check(e_opening_parenthesis_symbol);
                      read_symbol;
                      parse_expression;
                      check(e_closing_parenthesis_symbol);
                      read_symbol;
                    END// n
                e_EXP_symbol : 
                    BEGIN
                      read_symbol;
                      check(e_opening_parenthesis_symbol);
                      read_symbol;
                      parse_expression;
                      check(e_comma_symbol);
                      read_symbol;
                      parse_expression;
                      check(e_closing_parenthesis_symbol);
                      read_symbol;
                    END// n
                ELSE display_error('NUMBER, TRUE, FALSE, NAME, NOT, (, EXP');
              END// case   
            END// parse_factor
        
          BEGIN // parse_term
           parse_factor;

           WHILE f_symbol_type IN k_multiplication_symbols DO
            BEGIN
              read_symbol;
              parse_factor;
            END// WHILE {
          END// parse_term
      
        BEGIN // parse_simple_expression
          IF f_symbol_type IN k_addition_symbols
            THEN read_symbol;
          parse_term;

          WHILE f_symbol_type IN k_addition_symbols DO
          BEGIN
            read_symbol;
            parse_term;
          END// WHILE {
        END// parse_simple_expression
    
      BEGIN // parse_expression
        parse_simple_expression;

        IF f_symbol_type IN k_comparison_symbols
          THEN BEGIN
              read_symbol;
              parse_simple_expression;
            END// IF [
      END// parse_expression
  
    BEGIN // parse_assignment
      check(e_IDENTIFIER_symbol);

      read_symbol;
      check(e_becomes_symbol);

      read_symbol;
      parse_expression;

      check(e_semi_colon_symbol);
      read_symbol;
    END// parse_assignment

  BEGIN // parse_start
    WHILE f_symbol_typee_IDENTIFIER_symbol DO
      parse_assignment;
  END// parse_start

Nous ajoutons alors manuellement la création des noeuds de l'arbre syntaxique. Le code pour les expressions, par exemple, est le suivant:

        function f_c_parse_expressionc_node;

          function f_c_parse_simple_expressionc_node;

            function f_c_parse_termc_node;

              function f_c_parse_factorc_node;
                var l_function_nameString;
                    l_function_typet_symbol_type;
                    l_c_operand_onec_node;
                BEGIN
                  CASE m_symbol_type OF
                    e_integer_litteral_symbol :
                        BEGIN
                          Result:= c_litteral_node.create_litteral_node(m_symbol_stringe_integer_litteral_symbol);
                          read_symbol;
                        END;
                    e_real_litteral_symbol :
                        BEGIN
                          Result:= c_litteral_node.create_litteral_node(m_symbol_stringe_real_litteral_symbol);
                          read_symbol;
                        END;
                    e_TRUE_symbol :
                        BEGIN
                          Result:= c_litteral_node.create_litteral_node(m_symbol_stringe_TRUE_symbol);
                          read_symbol;
                        END;
                    e_FALSE_symbol :
                        BEGIN
                          Result:= c_litteral_node.create_litteral_node(m_symbol_stringe_FALSE_symbol);
                          read_symbol;
                        END;
                    e_IDENTIFIER_symbol :
                        BEGIN
                          Result:= c_identifier_node.create_identifier_node(m_symbol_stringe_identifier_symbol);
                          read_symbol;
                        END;
                    e_NOT_symbol :
                        BEGIN
                          read_symbol;
                          Result:= c_unary_operation_node.create_unary_operation_node('NOT',
                              e_NOT_symbolf_c_parse_factor);
                        END// n
                    e_opening_parenthesis_symbol :
                        BEGIN
                          read_symbol;
                          Result:= f_c_parse_expression;
                          check(e_closing_parenthesis_symbol);
                          read_symbol;
                        END;
                    e_SIN_symbole_COS_symbole_EXP_symbol :
                        BEGIN
                          l_function_name:= m_symbol_string;
                          l_function_type:= m_symbol_type;

                          read_symbol;
                          check(e_opening_parenthesis_symbol);
                          read_symbol;

                          if l_function_typee_EXP_symbol
                            then begin
                                l_c_operand_one:= f_c_parse_expression;

                                check(e_comma_symbol);
                                read_symbol;

                                Result:= c_function_node.create_function_node(l_function_name,
                                  e_EXP_symboll_c_operand_onef_c_parse_expression);
                              end
                            else Result:= c_function_node.create_function_node(l_function_name,
                                l_function_typef_c_parse_expressionNil);

                          check(e_closing_parenthesis_symbol);
                          read_symbol;
                        END// n
                    ELSE display_error('NUMBER, TRUE, FALSE, NAME, NOT, (, EXP');
                  END// case
                END// f_c_parse_factor

              var l_operation_stringString;
                  l_operation_symbolt_symbol_type;

              BEGIN // f_c_parse_term
                Result:= f_c_parse_factor;

                WHILE m_symbol_type IN k_multiplication_symbols DO
                BEGIN
                  l_operation_string:= m_symbol_string;
                  l_operation_symbol:= m_symbol_type;

                  read_symbol;
                  Result:= c_binary_operation_node.create_binary_operation_node(l_operation_string,
                      l_operation_symbolResultf_c_parse_factor);
                END// WHILE {
              END// f_c_parse_term

            var l_operation_stringString;
                l_operation_symbolt_symbol_type;

            BEGIN // f_c_parse_simple_expression
              IF m_symbol_type IN k_addition_symbols
                THEN BEGIN
                    l_operation_string:= m_symbol_string;
                    l_operation_symbol:= m_symbol_type;

                    read_symbol;
                    Result:= c_unary_operation_node.create_unary_operation_node(
         &n