par , 31/03/2021 à 03h13 (16917 Affichages)
Un convertisseur unix2dos
Les fichiers texte unix ont les fin de lignes indiqué avec le caractère de contrôle LF (line feed), tandis que ceux de windows sont les deux caractère
de contrôle CR (carriage return) et LF (line feed).
Pour convertir un fichier unix en fichier dos, rien de plus simple, il exste
des lignes de commandes pour cela. par exemple :
unix2dos -n fichier_entrée fichier_sortie
ou bien :
sed -i 's/\n/\r\n/g' fichier
Je vais vous proposer ici de faire la même chose, mais en un peu plus
compliqué, pour le fun. Il s'agit d'écrire un convertisseur unix2dos à la
manière d'un compilateur prar descente récursive prédictive suivant une
grammaire LL.
Ce convertisseur est constitué de 3 modules:
* Le scanneur, qui fournis à l'analyseur lexical les caractères du fichier
d'entrée.
* L'analyseur lexical, qui rassemble ces caractères en lexèmes et fournit
les unité lexicales pour l'analyseur syntaxique.
* L'analyseur syntaxique qui vérifie la bonne syntaxe du flot d'unité
lexical, puis émet une traduction.
le scanneur et l'analyse lexical
je ne m'attarderais pas sur le scanneur et l'analyseur lexical, car ce n'est
pas le but du sujet.
Sachez seulement que le scanneur lit les caractères du fichier source, et
les transmet,un par un, à l'analyseur lexical. Quant à ce dernier, il
regroupe ces caractères, en fait un lexème et retourne l'unité lexicale
correspondant à ce lexème, à l'analyseur syntaxique.
commun.hpp
1 2 3 4 5 6 7 8
|
#ifndef COMMUN_HPP
#define COMMUN_HPP
enum terminal{rc,car,dolar}; /*dolar est le terminal qui n'apparait
dans aucune grammaire. Il est utilisé pour
signifier la fin du fichier d'entrée*/
#endif |
unilex.hpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| #ifndef UNILEX_HPP
#define UNILEX_HPP
#include <string>
#include <vector>
#include "commun.hpp"
class unilex{
public:
terminal getlex();
void setlex(terminal const &x);
std::string getlexeme();
void ajouterlexeme(char const &c);
void raz();
protected:
terminal unitlex;
std::string lexeme;
};
#endif |
unilex.cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| #include <string>
#include "unilex.hpp"
#include "commun.hpp"
terminal unilex::getlex(){
return unitlex;
}
void unilex::setlex(terminal const &x){
unitlex=x;
}
std::string unilex::getlexeme(){
return lexeme;
}
void unilex::ajouterlexeme(char const &c){
lexeme+=c;
}
void unilex::raz(){
lexeme.clear();
} |
scanneur.hpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| #ifndef SCANNEUR_HPP
#define SCANNEUR_HPP
#include <fstream>
#include <string>
constexpr int taille=3000;
class scanneur{
public:
scanneur(std::string const& nomFichier);
char carsuiv();
void reculer(int const &n);
int getTailleFichier();
private:
std::ifstream fichier;
char tampon[taille+1];// +1 car sentinelle EOF
int Ntampon;
int enAvant;
int derniers;
int tailleFichier;
};
#endif |
scanneur.cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68
| #include <fstream>
#include <iostream>
#include "scanneur.hpp"
scanneur::scanneur(std::string const& nomFichier):Ntampon(1),enAvant(0),
derniers(0){
fichier.open(nomFichier.c_str(),std::ios::binary|std::ios::in);
fichier.seekg(0,std::ios_base::end);
tailleFichier=fichier.tellg();
fichier.seekg(0,std::ios_base::beg);
if(taille<=tailleFichier){
fichier.read(tampon,taille);
tampon[taille]=EOF;
}
else{
derniers=tailleFichier;
fichier.read(tampon,derniers);
Ntampon=1;
tampon[derniers]=EOF;
enAvant=0;
}
}
char scanneur::carsuiv(){
if(tampon[enAvant]==EOF)
if(enAvant!=taille)
return tampon[enAvant++];//enAvant++ car on pourrait devoir reculer
else if((Ntampon+1)*taille<=tailleFichier){//++ car tampon suivant
fichier.read(tampon,taille);
Ntampon++;
enAvant=0;
derniers=0;
return tampon[enAvant++];
}
else{
derniers=tailleFichier%(Ntampon*taille);
fichier.read(tampon,derniers);
Ntampon++;
tampon[derniers]=EOF;
enAvant=0;
return tampon[enAvant++];
}
else
return tampon[enAvant++];
}
void scanneur::reculer(int const &n){
for(int i=0;i<n;i++){
if(enAvant==0){
if(Ntampon>1){
fichier.seekg((Ntampon-2)*taille,std::ios_base::beg);
fichier.read(tampon,taille);
enAvant=taille-1;
}
else if(derniers!=0)
enAvant=derniers-1;
else
enAvant=taille-1;
Ntampon--;
}
else
enAvant--;
}
}
int scanneur::getTailleFichier(){
return tailleFichier;
} |
lexical.hpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| #ifndef LEXICAL_HPP
#define LEXICAL_HPP
#include <vector>
#include <string>
#include <map>
#include "unilex.hpp"
#include "scanneur.hpp"
class lexical{
public:
lexical(std::string const &nomfichier);
int getligne();
unilex analex();
private:
void obtenirrc();
void obtenircar1();
void obtenircar2();
void obtenircar3();
void obtenircar4();
void obtenirdolar();
std::vector<std::string>motscles;
std::map<std::string,terminal>dicolex;
int debutlex,etat,ligne;
char c;
unsigned char usc;
bool continuer,reussi;
unilex ulex;
std::string lexeme;
scanneur scan;
void echec();
};
#endif |
lexical.cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221
|
#include <iostream>
#include <string>
#include <vector>
#include <map>
#include "unilex.hpp"
#include "commun.hpp"
#include "lexical.hpp"
#include "scanneur.hpp"
lexical::lexical(std::string const &nomfichier):lexeme(""),debutlex(0),etat(0),
scan(nomfichier),ligne(1),
continuer(true) {/*corps vide*/}
void lexical::echec(){
scan.reculer(debutlex);
continuer=true;
debutlex=etat=0;
ulex.raz();
}
unilex lexical::analex(){
while(true){
continuer=true;
debutlex=etat=0;
ulex.raz();
obtenirrc();
if(reussi)
return ulex;
else
echec();
obtenircar1();
if(reussi)
return ulex;
else
echec();
obtenircar2();
if(reussi)
return ulex;
else
echec();
obtenircar3();
if(reussi)
return ulex;
else
echec();
obtenircar4();
if(reussi)
return ulex;
else
echec();
obtenirdolar();
if(reussi)
return ulex;
else echec();
std::cerr<<std::endl<<"ligne "<<getligne()<<": symbole hors langage"
<<std::endl<<scan.carsuiv()<<std::endl;
}
}
int lexical::getligne(){
return ligne;
}
void lexical::obtenirrc(){
while(continuer)
switch(etat){
case 0:
c=scan.carsuiv();
debutlex++;
if(c=='\n'){
ligne++;
etat=1;
}
else{
continuer=false;
reussi=false;
}
break;
case 1:
ulex.setlex(rc);
reussi=true;
continuer=false;
}
}
void lexical::obtenircar1(){
while(continuer)
switch(etat){
case 0:
c=scan.carsuiv();
debutlex++;
usc=c; //usc est non signé
if(usc<=0x7f){
ulex.ajouterlexeme(c);
etat=1;
}
else{
continuer=false;
reussi=false;
}
break;
case 1:
ulex.setlex(car);
reussi=true;
continuer=false;
}
}
void lexical::obtenircar2(){
while(continuer)
switch(etat){
case 0:
c=scan.carsuiv();
debutlex++;
usc=c;// usc est non signé
if(usc>=0xc2 && usc<=0xdf){
ulex.ajouterlexeme(c);
etat=1;
}
else{
continuer=false;
reussi=false;
}
break;
case 1:
ulex.setlex(car);
c=scan.carsuiv();
ulex.ajouterlexeme(c);
reussi=true;
continuer=false;
}
}
void lexical::obtenircar3(){
while(continuer)
switch(etat){
case 0:
c=scan.carsuiv();
debutlex++;
usc=c;//usc est non signé
if(usc>=0xe0 && usc<=0xef){
ulex.ajouterlexeme(c);
etat=1;
}
else{
continuer=false;
reussi=false;
}
break;
case 1:
ulex.setlex(car);
c=scan.carsuiv();
ulex.ajouterlexeme(c);
c=scan.carsuiv();
ulex.ajouterlexeme(c);
reussi=true;
continuer=false;
}
}
void lexical::obtenircar4(){
while(continuer)
switch(etat){
case 0:
c=scan.carsuiv();
debutlex++;
usc=c;//usc est non signé
if(usc>=0xf0 && usc<=0xf4){
ulex.ajouterlexeme(c);
etat=1;
}
else{
continuer=false;
reussi=false;
}
break;
case 1:
ulex.setlex(car);
c=scan.carsuiv();
ulex.ajouterlexeme(c);
c=scan.carsuiv();
ulex.ajouterlexeme(c);
c=scan.carsuiv();
ulex.ajouterlexeme(c);
reussi=true;
continuer=false;
}
}
void lexical::obtenirdolar(){
while(continuer)
switch(etat){
case 0:
c=scan.carsuiv();
debutlex++;
if(c==EOF){
etat=1;
}
else{
continuer=false;
reussi=false;
}
break;
case 1:
ulex.setlex(dolar);
ulex.ajouterlexeme(c);
reussi=true;
continuer=false;
}
} |
syntaxique.hpp:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| #ifndef SYNTAXIQUE_HPP
#define SYNTAXIQUE_HPP
#include <string>
#include "lexical.hpp"
#include "unilex.hpp"
#include "commun.hpp"
class syntaxique{
public:
std::string F();
syntaxique(std::string const &nomFichier);
private:
std::string RF(std::string const &h);
std::string P(std::string const &h);
void consommer(terminal s);
lexical pe;
unilex a;
};
#endif |
la grammaire hors contexte
la grammaire de ce compilateur est la suivante:
1 2 3 4 5
| fichier -> paragraphe resteFichier
resteFichier -> retourChariot paragraphe resteFichier
-> epsilon (epsilon est la production vide)
paragraphe -> caractère paragraphe
-> epsilon |
"caractères" et "retourChariot" sont les terminaux.
"fichier" et "resteFichier" constituent une liste associative à gauche, et
éventuelllement vide, de paragraphes séparés par un retour chariot
"paragraphe" est une liste associative à droite, et éventuellement vide, de
caractères, sans séparateur.
le STDS (schéma de traduction dirigé par la syntaxe)
nous allons d'abord simplifier les noms:
F = fichier
P = paragraphe
RF = resteFichier
car = caractère
rc = retourChariot
dolar = fin de fichier (est un élément de suivant(F))
le stds résultant est le suivant:
1 2 3 4 5 6
|
F -> {P.h=""} P {RF.h = P.s} RF {F.s = RF.s}
RF -> rc {P.h=RF.h + "\r\n"} P {RF1.h = P.s} RF1 {RF.s = RF1.s}
-> epsilon {RF.s = RF.h}
P -> car {P1.h = P.h + car.s} P1 {P.s = P1.s}
-> epsilon {P.s = P.h} |
les attributs se terminant par .s sont les attributs synthétisés et les
autres, se termaniant par .h, sont les attributs hérités.
dans un arbre syntaxique décoré (voir un exemple dans la figure suivante),
les attributs synthétisés d'un nœud se calculent avec les attributs venant
des nœuds frères de droite et des nœuds fils, du nœuds considéré.
Les attributs hérités d'un nœud, se calculent avec les attributs des nœud
frères de gauche et du nœud parent de ce nœud.
Les productions F et RF indiquent une traduction dirigée par la
syntaxe d'une liste de P séparés par "\n\r", qui est la fin de ligne pour
les fichiers texte brut Windows.
La production P décrit le paragraphe, qui est une suite
de caractères, différents de CR et de EOF, et qui n'utilise pas de séparateurs.

déscente récursive prédictive
pour implémenter un stds par la méthode de la déscente récursive
prédictive, nous associerons les non terminaux à des fonctions.
Les terminaux seront représentés par un appel à la fonction "consommer" qui
vérifie que le lexème courrant est bien celui attendu, puis fournit l'unité
lexicale suivante. L'attribut synthétisé d'un non terminal est la
valeur de retour des fonctions qui représente ce non terminal. Les
attributs hérités sont les parmètres d'entrée de ces fonctions.
Pour éxecuter ces fonctions, on choisit dans cette fonction la séquence qui
correspond au premier symbole du non terminal. Ce symbole est indiqué par
la fonction (une fonction mathématique et non une fonction C++) appelé
"premier" dont la valeur dépend du non terminal courrant. Voici ces
valeurs:
premier(F)={car;rc;dolar}
premier(RF)={rc;epsilon}
premier(P)={car,epsilon}
la fonction F() devra s'écrire comme cela:
1 2 3 4 5 6 7 8 9 10 11 12 13
| std::string syntaxique::F(){
std::string RFh,Ps,Fs,RFs;
terminal x;
x=a.getLex();
if(x==car||x==rc||x==dolar){
Ps=P("");
RFh=Ps;
RFs=RF(RFh);
Fs=RFs;
consommer(dolar);
}
return Fs;
} |
voici le fichier syntaxique.cpp:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64
| #include <string>
#include <iostream>
#include "syntaxique.hpp"
syntaxique::syntaxique(std::string const &nomFichier):pe(nomFichier){
//pe est l'objet de la classe "lexical"
a=pe.analex(); //ici, a est la première unité lexicale
}
std::string syntaxique::F(){
std::string RFh,Ps,Fs,RFs,retour;
terminal x;
x=a.getlex();
if(x==car||x==rc||x==dolar){
Ps=P("");
RFh=Ps;
RFs=RF(RFh);
Fs=RFs;
consommer(dolar);
}
else{
std::cerr<<"erreur ligne "<<pe.getligne();
exit(1);
}
return Fs;
}
std::string syntaxique::RF(std::string const &h){
std::string retour,Ps,RF1s,RF1h,RFs,Ph;
if(a.getlex()==rc){ //unité lexicale rc
consommer(rc);
Ph=h+"\r\n";
Ps=P(Ph);
RF1h=Ps;
RF1s=RF(RF1h);
RFs=RF1s;
}
else
RFs=h;
return RFs;
}
std::string syntaxique::P(std::string const &h){
std::string retour,cars,P1h,Ps,P1s;
if(a.getlex()==car){
cars=a.getlexeme();//getlexeme est le lexeme
consommer(car);
P1h=h+cars;
P1s=P(P1h);
Ps=P1s;
}
else
Ps=h;
return Ps;
}
void syntaxique::consommer(terminal s){
if(a.getlex()==s){ // si l'unité lexicale lu est celle attendu
if(s!=dolar)
a=pe.analex();//unité lexicale suivante
}
else
std::cerr<<"erreur à la ligne "<<pe.getligne()<<std::endl;
} |
le fichier main.cpp:
1 2 3 4 5 6 7 8
| #include <iostream>
#include "syntaxique.hpp"
int main(int argc,char *argv[]){
syntaxique S(argv[1]);
std::cout<<S.F();
} |
Par exemple:
1 2
| $ ./a.out fichier_entrée > fichier_sortie
$ xxd fichier_sortie |