L'analyse consiste en deux phases. Le premier est "lexing", qui convertit les chaînes de caractères brutes en quelque chose que le programme peut plus facilement comprendre (communément appelé jetons).
Exemple simple, lex convertirait:
si (a + b> 2) puis
pour:
IF_TOKEN LEFT_PAREN IDENTIFIER(a) PLUS_SIGN IDENTIFIER(b) GREATER_THAN NUMBER(2) RIGHT_PAREN THEN_TOKEN
Parse prend ce flux de jetons, et tente de faire encore plus de sens hors d'eux. Dans ce cas, il essayera de faire correspondre ces jetons à un IF_STATEMENT. Pour l'analyse syntaxique, le _STATEMENT IF pourrait bien ressembler à ceci:
IF (BOOLEAN_EXPRESSION) THEN
Lorsque le résultat de la phase de lexing est un flux symbolique, le résultat de la phase d'analyse syntaxique est un arbre Parse.
Ainsi, un analyseur peut convertir ci-dessus pour:
if_statement
|
v
boolean_expression.operator = GREATER_THAN
| |
| v
V numeric_constant.string="2"
expression.operator = PLUS_SIGN
| |
| v
v identifier.string = "b"
identifier.string = "a"
Ici, vous voyez, nous avons un IF_STATEMENT. Un IF_STATEMENT a un seul argument, qui est un BOOLEAN_EXPRESSION. Cela a été expliqué d'une certaine manière à l'analyseur. Lorsque l'analyseur convertit le flux de jetons, il "sait" à quoi ressemble une IF et sait à quoi ressemble BOOLEAN_EXPRESSION, de sorte qu'il peut effectuer les affectations appropriées lorsqu'il voit le code.
Par exemple, si vous avez juste:
si (a + b) puis
L'analyseur peut savoir que ce n'est pas une expression booléenne (parce que le + est arithmétique, pas un opérateur booléen) et l'analyse pourrait jeter une erreur à ce stade.
Ensuite, nous voyons qu'une BOOLEAN_EXPRESSION a 3 composants, l'opérateur (GREATER_THAN), et deux côtés, le côté gauche et le côté droit.
Sur le côté gauche, il pointe vers une autre expression, la "a + b", tandis que sur la droite pointe vers un NUMERIC_CONSTANT, dans ce cas la chaîne "2". Encore une fois, l'analyseur «sait» qu'il s'agit d'une constante NUMERIC parce que nous lui avons parlé de chaînes de nombres. Si ce n'était pas des chiffres, ce serait un IDENTIFICATEUR (comme "a" et "b" sont).
Notez que si nous avions quelque chose comme:
si (a + b> "XYZ") puis
Ce "parse" très bien (expression à gauche, constante chaîne à droite) . Nous ne savons pas en regardant cela si c'est une expression valide ou non. Nous ne savons pas si "a" ou "b" fait référence aux chaînes ou aux nombres à ce stade. Donc, c'est quelque chose que l'analyseur ne peut pas décider pour nous, ne peut pas signaler comme une erreur, car il ne sait tout simplement pas. Cela se produira lorsque nous évaluerons (exécuterons ou essayerons de compiler pour coder) l'instruction IF.
Si nous avons fait:
si [a> b) puis
L'analyseur peut facilement voir que l'erreur de syntaxe comme un problème, et une erreur. Cette chaîne de jetons ne ressemble à rien de ce qu'elle sait. Donc, le fait est que lorsque vous obtenez un arbre d'analyse complet, vous avez l'assurance qu'au premier coup, le "code a l'air bien". Maintenant, pendant l'exécution, d'autres erreurs peuvent survenir. Pour évaluer l'arbre d'analyse, il vous suffit de parcourir l'arbre. Vous aurez du code associé aux noeuds principaux de l'arbre d'analyse pendant la partie de compilation ou d'évaluation. Supposons que nous avons un interprète.
public void execute_if_statment(ParseTreeNode node) {
// We already know we have a IF_STATEMENT node
Value value = evaluate_expression(node.getBooleanExpression());
if (value.getBooleanResult() == true) {
// we do the "then" part of the code
}
}
public Value evaluate_expression(ParseTreeNode node) {
Value result = null;
if (node.isConstant()) {
result = evaluate_constant(node);
return result;
}
if (node.isIdentifier()) {
result = lookupIdentifier(node);
return result;
}
Value leftSide = evaluate_expression(node.getLeftSide());
Value rightSide = evaluate_expression(node.getRightSide());
if (node.getOperator() == '+') {
if (!leftSide.isNumber() || !rightSide.isNumber()) {
throw new RuntimeError("Must have numbers for adding");
}
int l = leftSide.getIntValue();
int r = rightSide.getIntValue();
int sum = l + r;
return new Value(sum);
}
if (node.getOperator() == '>') {
if (leftSide.getType() != rightSide.getType()) {
throw new RuntimeError("You can only compare values of the same type");
}
if (leftSide.isNumber()) {
int l = leftSide.getIntValue();
int r = rightSide.getIntValue();
boolean greater = l > r;
return new Value(greater);
} else {
// do string compare instead
}
}
}
Donc, vous pouvez voir que nous avons un évaluateur récursif ici. Vous voyez comment nous vérifions les types d'exécution et effectuons les évaluations de base.
Que se passera-t-il? Execute_if_statement évaluera son expression principale. Même si nous voulions seulement BOOLEAN_EXPRESION dans l'analyse, toutes les expressions sont la plupart du temps les mêmes pour nos objectifs. Ainsi, execute_if_statement appelle evaluate_expression.
Dans notre système, toutes les expressions ont un opérateur et un côté gauche et droit. Chaque côté d'une expression est AUSSI une expression, vous pouvez donc voir comment nous essayons immédiatement d'évaluer ceux-ci afin d'obtenir leur valeur réelle. La seule note est que si l'expression consiste en une constante, alors nous retournons simplement la valeur des constantes, si c'est un identifiant, nous la considérons comme une variable (et ce serait un bon endroit pour lancer un "Je ne trouve pas la variable 'a' "message), sinon nous sommes de retour sur le côté gauche/côté droit.
J'espère que vous pouvez voir comment un évaluateur simple peut fonctionner une fois que vous avez un flux de jeton d'un analyseur. Notez comment, lors de l'évaluation, les principaux éléments du langage sont en place, sinon nous aurions une erreur de syntaxe et nous ne serions jamais arrivés à cette phase. Nous pouvons simplement nous attendre à "savoir" que lorsque nous avons, par exemple, un opérateur PLUS, nous allons avoir 2 expressions, le côté gauche et le côté droit. Ou lorsque nous exécutons une instruction IF, nous avons déjà une expression booléenne à évaluer. L'analyse est ce qui fait ce gros travail pour nous.Débuter avec une nouvelle langue peut être un défi, mais vous trouverez une fois que vous obtenez, le reste devient assez simple et c'est presque "magique" que tout cela fonctionne à la fin.
Notez, pardonnez la mise en forme, mais les underscores gâchent les choses - j'espère que c'est toujours clair.
Pourriez-vous s'il vous plaît élaborer un peu. –