Il y a déjà beaucoup de bonnes réponses, mais comme vous n'êtes pas informé sur les grammaires, les analyseurs syntaxiques et les compilateurs, etc., laissez-moi vous le démontrer par un exemple.
Tout d'abord, le concept de grammaire est assez intuitif. Imaginez un ensemble de règles :
S -> a T
T -> b G t
T -> Y d
b G -> a Y b
Y -> c
Y -> lambda (nothing)
Et imaginez que vous commencez avec S
. Les lettres majuscules sont des non-terminaux et les lettres minuscules des terminaux. Cela signifie que si vous obtenez une phrase composée de tous les terminaux, vous pouvez dire que la grammaire a généré cette phrase comme un "mot" dans la langue. Imaginez de telles substitutions avec la grammaire ci-dessus (La phrase entre *phrase* est celle qui est remplacée) :
*S* -> a *T* -> a *b G* t -> a a *Y* b t -> a a b t
Donc, je pourrais créer aabt
avec cette grammaire.
Ok, retour à la ligne principale.
Supposons un langage simple. Vous avez des nombres, deux types (int et string) et des variables. Vous pouvez faire des multiplications sur des entiers et des additions sur des chaînes de caractères mais pas l'inverse.
La première chose dont vous avez besoin, c'est un lexer. C'est-à-dire généralement une grammaire régulière (ou égale à celle-ci, une DFA, ou égale à une expression régulière) qui correspond aux tokens du programme. Il est courant de les exprimer en expressions régulières. Dans notre exemple :
(J'invente ces syntaxes)
number: [1-9][0-9]* // One digit from 1 to 9, followed by any number
// of digits from 0-9
variable: [a-zA-Z_][a-zA-Z_0-9]* // You get the idea. First a-z or A-Z or _
// then as many a-z or A-Z or _ or 0-9
// this is similar to C
int: 'i' 'n' 't'
string: 's' 't' 'r' 'i' 'n' 'g'
equal: '='
plus: '+'
multiply: '*'
whitespace: (' ' or '\n' or '\t' or '\r')* // to ignore this type of token
Donc, maintenant vous avez une grammaire régulière, qui tokenise votre entrée, mais elle ne comprend rien de la structure.
Alors vous avez besoin d'un analyseur syntaxique. L'analyseur syntaxique, c'est généralement une grammaire sans contexte. Une grammaire sans contexte signifie que, dans la grammaire, il n'y a que des non-terminaux simples sur le côté gauche des règles de grammaire. Dans l'exemple du début de cette réponse, la règle
b G -> a Y b
rend la grammaire contextuelle sensible parce que sur la gauche, vous avez b G
et pas seulement G
. Qu'est-ce que cela signifie ?
Eh bien, lorsque vous écrivez une grammaire, chacun des non terminaux a une signification. Écrivons une grammaire sans contexte pour notre exemple (| signifie ou. Comme si on écrivait plusieurs règles sur la même ligne) :
program -> statement program | lambda
statement -> declaration | executable
declaration -> int variable | string variable
executable -> variable equal expression
expression -> integer_type | string_type
integer_type -> variable multiply variable |
variable multiply number |
number multiply variable |
number multiply number
string_type -> variable plus variable
Maintenant cette grammaire peut accepter ce code :
x = 1*y
int x
string y
z = x+y
Grammaticalement, ce code est correct. Donc, revenons à ce que signifie "sans contexte". Comme vous pouvez le voir dans l'exemple ci-dessus, lorsque vous développez executable
vous générez une déclaration de la forme variable = operand operator operand
sans tenir compte de la partie du code dans laquelle vous vous trouvez. Que ce soit au tout début ou au milieu, que les variables soient définies ou non, ou que les types correspondent, vous ne savez pas et vous vous en moquez.
Ensuite, il faut faire de la sémantique. C'est là que les grammaires sensibles au contexte entrent en jeu. Tout d'abord, laissez-moi vous dire qu'en réalité, personne n'écrit une grammaire sensible au contexte (parce que l'analyser est trop difficile), mais plutôt des bouts de code que l'analyseur syntaxique appelle lorsqu'il analyse l'entrée (appelés routines d'action, bien que ce ne soit pas la seule façon). Formellement, cependant, vous pouvez définir tout ce dont vous avez besoin. Par exemple, pour être sûr de définir une variable avant de l'utiliser, au lieu de ceci
executable -> variable equal expression
vous devez avoir quelque chose comme :
declaration some_code executable -> declaration some_code variable equal expression
plus complexe cependant, pour s'assurer que le variable
dans la déclaration correspond à celle qui est calculée.
Bref, je voulais juste vous donner l'idée. Donc, toutes ces choses sont sensibles au contexte :
- Vérification du type
- Nombre d'arguments pour la fonction
- valeur par défaut de la fonction
- si
member
existe dans obj
en code : obj.member
- Presque tout ce qui n'est pas comme : disparu
;
o }
J'espère que vous avez une idée des différences (si ce n'est pas le cas, je serai ravi de vous expliquer).
Donc en résumé :
- Le lexer utilise une grammaire régulière pour tokeniser l'entrée.
- L'analyseur utilise une grammaire sans contexte pour s'assurer que le programme a une structure correcte.
- L'analyseur sémantique utilise une grammaire contextuelle pour effectuer la vérification des types, la correspondance des paramètres, etc.
Mais ce n'est pas forcément toujours le cas. Cela montre simplement que chaque niveau doit devenir plus puissant pour pouvoir faire plus de choses. Cependant, chacun des niveaux de compilateur mentionnés pourrait en fait être plus puissant.
Par exemple, un langage dont je ne me souviens plus, utilisait la souscription de tableau et l'appel de fonction à la fois avec des parenthèses et donc il fallait que l'analyseur syntaxique aille chercher le type (choses liées au contexte) de la variable et détermine quelle règle (appel de fonction ou substitution de tableau) prendre.
Si vous concevez un langage dont le lexer comporte des expressions régulières qui se chevauchent, vous devrez également consulter le contexte pour déterminer le type de jeton correspondant.
Pour en venir à votre question ! Avec l'exemple que vous avez mentionné, il est clair que la grammaire c++ n'est pas sans contexte. Le langage D, je n'en ai absolument aucune idée, mais vous devriez être capable de raisonner à ce sujet maintenant. Pensez-y de cette façon : Dans une grammaire sans contexte, un non-terminal peut s'étendre sans prendre en considération quoi que ce soit, MAIS la structure du langage. Comme vous l'avez dit, il s'étend, sans "regarder" ailleurs.
Un exemple familier serait celui des langues naturelles. Par exemple, en anglais, on dit :
sentence -> subject verb object clause
clause -> .... | lambda
Bien, sentence
y clause
sont des non terminaux ici. Avec cette grammaire, vous pouvez créer ces phrases :
I go there because I want to
ou
I jump you that I is air
Comme vous pouvez le voir, le second a la structure correcte, mais n'a pas de sens. Tant qu'une grammaire sans contexte est concernée, le sens n'a pas d'importance. Il s'étend simplement verb
à un verbe quelconque sans "regarder" le reste de la phrase.
Donc si vous pensez que D doit à un moment donné vérifier comment quelque chose a été défini ailleurs, juste pour dire que le programme est structurellement correcte, alors sa grammaire n'est pas sans contexte. Si vous isolez n'importe quelle partie du code et qu'il peut toujours dire qu'il est structurellement correct, alors il est sans contexte.
0 votes
Alors, qu'est-ce que cela a à voir avec le C++ ?
4 votes
@VJo : Il s'agit de comparer la grammaire de D à celle de C++ (context-free vs context-dependent). J'ai tagué C++ au lieu d'un langage différent parce que le site de D se compare à C++.
0 votes
Ah, ça me rappelle les langages formels et la théorie des automates à l'université.
0 votes
stackoverflow.com/questions/898489/
0 votes
Je ne pense pas que int[T] et int[0] soient ambigus - les identifiants ne peuvent probablement pas commencer par des chiffres, et vous ne pouvez probablement pas utiliser de variables à l'intérieur, donc c'est soit type[type (identifiant)] ou type[taille (littéral constant)], résoluble avec un seul token lookahead. Ce n'est pas à l'analyseur syntaxique de déterminer si cela a un sens par rapport aux types concrets.
5 votes
Parle-t-on des modèles ou de la syntaxe en général ? Je pense que D a à la fois des pointeurs et des multiplications, alors qu'est-ce que c'est ?
a * b
?1 votes
En D, il est tout à fait valide pour un type d'utiliser toute expression évaluable au moment de la compilation.
0 votes
Puisque cela a été mentionné, voici le lien : digitalmars.com/d/archives/digitalmars/D/learn/
0 votes
Les modèles et les macros C++ sont utiles, mais rendent le compilateur beaucoup plus difficile à mettre en œuvre.
1 votes
@Bo Persson ; En tant que déclaration
a*b;
ya*b=exp;
les deux sont interprétés comme des déclarations de pointeurs. Si vous metteza*b
dans une position de valeur r, il devient une expression de multiplication. Bien que cela ne soit pas montré dans la grammaire documentée, c'est bien défini et pourrait être résolu au niveau de la grammaire.3 votes
@BCS - Ok, donc la grammaire est sans contexte, mais vous ne pouvez pas le dire à partir de la grammaire ? Je comprends pourquoi cette question a été posée :-)
1 votes
@Bo Persson : vous ne pouvez pas le dire de la documenté grammaire. :b
7 votes
C'est amusant de voir comment chacun présente sa propre théorie de l'informatique ici. Je suis impatient de savoir qui a raison. Je n'en sais rien du tout. Je ferais mieux de ne pas deviner.
4 votes
Nous devons demander à Walter de répondre à cette question.
0 votes
Le problème ici est que la définition informelle de context-free du PO et l'idée de ce qu'est une grammaire ne sont pas assez strictes. Nous ne pouvons pas non plus introduire de "sens". Les grammaires formelles sont des concepts mathématiques qui ne savent absolument rien du sens et la définition d'un langage de programmation a beaucoup plus à voir avec une grammaire formelle. D'un certain point de vue, une grammaire est un ensemble de règles qui vous permettent de décider si une longue chaîne de caractères (le texte du programme) est syntaxiquement valide ou non. Si elle est valide, il se peut qu'elle n'ait pas de sens, qu'elle ne compile pas, mais si elle n'est pas valide, c'est définitivement de la merde.
0 votes
Vu dans le sens inverse, une grammaire formelle vous indique comment générer chaque programme syntaxiquement correct. Pour ce faire, vous prenez des symboles abstraits mentionnés à gauche d'une des règles de la grammaire et vous les remplacez par un élément expansé de votre choix conforme à la règle d'expansion à droite de la règle, et il y a un symbole comme le signe égal entre les deux. Le remplacement est comme une recherche et un remplacement dans un éditeur de texte. Le texte de remplacement peut être ce que vous voulez tant qu'il respecte le modèle qui est la droite de cette règle.
0 votes
Si vous êtes familier avec les expressions régulières dans les fonctions de recherche et de remplacement de votre traitement de texte préféré ou d'un autre éditeur, ou dans les langages de programmation, vous avez déjà vu ceci. Vous savez maintenant tout ce qu'il y a à savoir sur les grammaires formelles. Une grammaire sans contexte est une grammaire où les règles ne peuvent être que de ce type de substitution très simple, et où il n'y a pas de conditions autorisées où vous pourriez dire "substituez de cette façon si la lhs se produit dans ce contexte syntaxique" mais substituez autre chose si dans un autre contexte syntaxique.
0 votes
Exemple : une règle sans contexte ressemblera toujours à X -> Y, ce qui signifie "chaque fois que vous voyez un X, vous pouvez le remplacer par un Y". Lors de la génération d'un programme, une règle sans contexte pourrait ressembler à a X b -> Y, mais c X d -> Z pourrait vous indiquer comment développer X, c'est-à-dire "par quoi remplacer X", lors de la génération d'un programme valide, et elle dit que dans un contexte, lorsque X est précédé d'un a et suivi d'un b, il faut le remplacer par un Y, mais que dans le second contexte, il faut le remplacer par un Z. Ainsi, lorsque vous voyez un X, vous devez regarder autour de lui pour savoir ce qu'il faut faire.
0 votes
Maintenant, lorsque vous analysez un programme comme dans la compilation, vous devez d'une certaine manière travailler dans l'autre sens, regarder votre texte, voir s'il ressemble à une rhs potentielle dans une règle particulière, s'il correspond à cette rhs, vous remplacez votre texte par la lhs. Dans une grammaire sans contexte, vous devez vous soucier que les rhs soient dans le bon contexte, ce qui rend le traitement plus difficile. Les grammaires sans contexte sont populaires car elles sont faciles à mettre en œuvre.
0 votes
J'aurais dû préciser plus tôt que les rhs pourraient être des cordes plus longues. Il doit toujours y avoir des règles appelées "bornes". Elles indiquent que certaines lhs sont égales à une seule chaîne littérale. Lorsque vous générez un programme, vous avez terminé lorsque vous avez tout remplacé par des bornes. Lors de l'analyse, vous commencez par faire correspondre des éléments du texte de votre programme à chacun des terminaux, puis vous effectuez des remplacements inverses rhs->lhs par rapport aux règles jusqu'à ce que vous arriviez à la lhs d'une règle "racine", si vous voulez, cette lhs pourrait s'appeler par exemple PROGRAMME. Vous avez alors terminé l'analyse et le programme est valide.
0 votes
Si, à un moment donné, vous ne trouvez aucune règle dont la rhs correspond à votre texte, vous êtes mort. Votre programme a une erreur de syntaxe et n'est pas valide selon la grammaire formelle. Dans le cas d'une grammaire sans contexte, le contexte donné dans la rhs doit également correspondre à la situation. Vous pouvez avoir des grammaires de plus en plus générales dans le sens où elles permettent des règles plus puissantes, complexes et expressives. C'est ce qu'on appelle la hiérarchie de Chomsky. Des niveaux croissants de méchanceté en termes de défi de "parsing" (analyse syntaxique), jusqu'à devenir un véritable fardeau pour l'auteur du compilateur.
0 votes
Mes excuses, je me sens mieux maintenant. J'espère que cela sera utile à quelqu'un en tout cas. Wikipedia est votre ami. J'espère que la plupart de mes informations sont correctes, mais j'ai omis beaucoup de détails.