|
Interprète d'Expression - John COLIBRI.
|
- mots clé:interprète - compilateur - analyse syntaxique et lexicale -
grammaire - BNF, EBNF
- logiciel utilisé: Windows XP, Delphi 6.0
- matériel utilisé: Pentium 1.400Mhz, 256 M de mémoire
- champ d'application: Delphi 1 à 6 sur Windows, Kylix
- niveau: débutant en Pascal et Delphi
- uses:
- plan:
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:
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:
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:
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_symbol, e_divide_symbol] DO
BEGIN
read_symbol;
parse_factor;
END; // WHILE {
END; // parse_term
BEGIN // parse_expression
IF f_symbol_type IN [e_plus_symbol, e_minus_symbol]
THEN BEGIN
// -- unary "+" and "-"
read_symbol
END; // IF [
parse_term;
WHILE f_symbol_type IN [e_plus_symbol, e_minus_symbol] DO
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_term: Integer);
procedure parse_factor(var pv_factor: Integer);
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_type= e_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_operator: t_symbol_type;
l_next_factor: Integer;
begin // parse_term
with p_c_scanner do
begin
parse_factor(pv_term);
while m_symbol_type in [e_times_symbol, e_slash_symbol] do
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_term* l_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:
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_symbol, e_real_litteral_symbol,
e_semi_colon_symbol,
e_opening_parenthesis_symbol, e_closing_parenthesis_symbol,
e_plus_symbol, e_minus_symbol, e_times_symbol, e_divide_symbol,
e_less_symbol, e_greater_symbol, e_equal_symbol,
e_becomes_symbol,
e_less_or_equal_symbol, e_greater_or_equal_symbol,
e_different_symbol,
e_key_word_start,
e_AND_symbol, e_OR_symbol, e_NOT_symbol, e_DIV_symbol,
e_TRUE_symbol, e_FALSE_symbol,
e_SIN_symbol, e_COS_symbol, e_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_returns= k_blanks+ k_returns;
k_letters= ['a'..'z', 'A'..'Z'];
k_accents= ['à', 'â', 'é', 'ç', 'ê', 'è', 'î', 'ô', 'ù', 'û'];
k_digits= ['0'..'9'];
k_identifiers= k_letters+ k_accents+ k_digits+ ['_'];
procedure skip_blanks;
var l_start_index: Integer;
begin
l_start_index:= m_buffer_index;
while (m_buffer_index< m_buffer_size) and (m_pt_buffer[m_buffer_index] in k_blanks_returns) do
inc(m_buffer_index);
end; // skip_blanks
procedure get_number;
var l_start_index: Integer;
begin
l_start_index:= m_buffer_index;
m_symbol_type:= e_integer_litteral_symbol;
while (m_buffer_index< m_buffer_size) and (m_pt_buffer[m_buffer_index] in ['0'..'9']) do
inc(m_buffer_index);
// -- decimal part ?
if (m_buffer_index< m_buffer_size) and (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_index< m_buffer_size) and (m_pt_buffer[m_buffer_index] in ['0'..'9']) do
inc(m_buffer_index);
end;
// -- exponent part
if (m_buffer_index< m_buffer_size) and (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_index< m_buffer_size) and (m_pt_buffer[m_buffer_index]= '+') or (m_pt_buffer[m_buffer_index]= '-')
then inc(m_buffer_index);
while (m_buffer_index< m_buffer_size) and (m_pt_buffer[m_buffer_index] in ['0'..'9']) do
inc(m_buffer_index);
end;
m_symbol_string:= f_extract_string_start_end(l_start_index, m_buffer_index- 1);
end; // get_number
procedure get_identifier;
var l_start_index: Integer;
begin
l_start_index:= m_buffer_index;
Inc(m_buffer_index);
while (m_buffer_index< m_buffer_size) and (m_pt_buffer[m_buffer_index] in k_identifiers) do
inc(m_buffer_index);
m_symbol_string:= f_extract_string_start_end(l_start_index, m_buffer_index- 1);
m_symbol_type:= f_key_word_symbol(m_symbol_string);
end; // get_identifier
procedure get_operator_1(p_operator_type: t_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_type: t_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_operator: Char;
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_type= e_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_index< m_buffer_size
then begin
case m_pt_buffer[m_buffer_index] of
'a'..'z', 'A'..'Z', '_',
'à', 'â', 'é', 'ç', 'ê', 'è', 'î', 'ô', 'ù', 'û': get_identifier;
'0'..'9' : get_number;
'/' : get_operator_1(e_divide_symbol);
':' : if (m_buffer_index< m_buffer_size) and (m_pt_buffer[m_buffer_index+ 1]= '=')
then get_operator_2(e_becomes_symbol)
else display_error('unknown_symbol');
'<' : if (m_buffer_index< m_buffer_size) and (m_pt_buffer[m_buffer_index+ 1]= '=')
then get_operator_2(e_less_or_equal_symbol)
else
if (m_buffer_index< m_buffer_size) and (m_pt_buffer[m_buffer_index+ 1]= '>')
then get_operator_2(e_different_symbol)
else get_operator_1(e_less_symbol);
'>' : if (m_buffer_index< m_buffer_size) and (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_node= class(c_basic_object)
m_c_left_node, m_c_right_node: c_node;
m_node_type: t_symbol_type;
m_c_data_value: c_data_value;
Constructor create_node(p_name: String; p_node_type: t_symbol_type);
function f_display_node: String; Virtual;
procedure display_node;
procedure display_node_data_value;
Destructor Destroy; Override;
end; // c_node
c_identifier_node= class(c_node)
Constructor create_identifier_node(p_name: String;
p_node_type: t_symbol_type);
end; // c_identifier_node
c_litteral_node= class(c_node)
Constructor create_litteral_node(p_name: String;
p_node_type: t_symbol_type);
end; // c_litteral_node
c_binary_operation_node= class(c_node)
Constructor create_binary_operation_node(p_name: String;
p_node_type: t_symbol_type;
p_c_operand_one, p_c_operand_two: c_node);
end; // c_operation_node
c_unary_operation_node= class(c_node)
Constructor create_unary_operation_node(p_name: String;
p_node_type: t_symbol_type; p_c_operand: c_node);
end; // c_operation_node
c_function_node= Class(c_node)
Constructor create_function_node(p_name: String;
p_node_type: t_symbol_type; p_c_parameter_one, p_c_parameter_two: c_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_instruction= Class(c_basic_object)
m_c_next_instruction: c_instruction;
m_c_rhs_expression: c_node;
Constructor create_instruction(p_name: String; p_c_rhs_expression: c_node);
procedure display_instruction;
procedure display_instruction_data_value;
function f_display_instruction: String;
Destructor Destroy; Override;
end; // c_instruction
c_instruction_list= Class(c_basic_object)
m_c_first_instruction: c_instruction;
m_instruction_count: Integer;
Constructor create_instruction_list(p_name: String);
function f_instruction_count: Integer;
procedure add_instruction(p_c_instruction: c_instruction);
procedure display_instruction_list;
procedure display_instruction_list_data_value;
Destructor Destroy; Override;
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_symbol, e_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_type= e_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_expression: c_node;
function f_c_parse_simple_expression: c_node;
function f_c_parse_term: c_node;
function f_c_parse_factor: c_node;
var l_function_name: String;
l_function_type: t_symbol_type;
l_c_operand_one: c_node;
BEGIN
CASE m_symbol_type OF
e_integer_litteral_symbol :
BEGIN
Result:= c_litteral_node.create_litteral_node(m_symbol_string, e_integer_litteral_symbol);
read_symbol;
END;
e_real_litteral_symbol :
BEGIN
Result:= c_litteral_node.create_litteral_node(m_symbol_string, e_real_litteral_symbol);
read_symbol;
END;
e_TRUE_symbol :
BEGIN
Result:= c_litteral_node.create_litteral_node(m_symbol_string, e_TRUE_symbol);
read_symbol;
END;
e_FALSE_symbol :
BEGIN
Result:= c_litteral_node.create_litteral_node(m_symbol_string, e_FALSE_symbol);
read_symbol;
END;
e_IDENTIFIER_symbol :
BEGIN
Result:= c_identifier_node.create_identifier_node(m_symbol_string, e_identifier_symbol);
read_symbol;
END;
e_NOT_symbol :
BEGIN
read_symbol;
Result:= c_unary_operation_node.create_unary_operation_node('NOT',
e_NOT_symbol, f_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_symbol, e_COS_symbol, e_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_type= e_EXP_symbol
&n | |