/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
Copyright (C) 2011-2012 Marchand Eric <ricoh51@free.fr>
This file is part of Freegressi.
Freegressi is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Freegressi is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Freegressi. If not, see <http://www.gnu.org/licenses/>.
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
package freegressi.parser;
import freegressi.tableur.Noeud;
import freegressi.tableur.Sym;
import java.util.ArrayList;
import java.util.List;
// @todo Transformer les Double en double dans freegressi.flex
/**
* Un noeud terminal est un Nombre, ou une expression entre parenthèses
* Un noeud non terminal est un opérateur +, -, *, / .
* Les noeuds non terminaux possèdent une priorité, par ex * possède une priorité
* plus forte que +, et ^ possède une priorité plus forte que *.
* Chaque noeud possède un fils gauche et un fils droit
* Un fils gauche est toujours créé seul, c'est le début d'une expression.
* Un opérateur est ajouté comme père d'un fils gauche.
* On ajoute donc toujours un noeud terminal comme fils droit.
* Les nombres sont codés en double (4.9e-324 à 1.7e308).
* Les valeurs sont données à 1e-15 près.
*/
public class Parser {
/** La liste des noeuds, fournie par le lexer */
private List<Noeud> liste;
/** Un index utilisé dans la méthode récursive parse */
private int indexListe = -1;
/**
* Constructeur de la classe Parser
* @param list : la liste des noeuds fournie par le lexer
*/
public Parser(List<Noeud> list) {
liste = list;
}
/**
* Vérifie la cohérence de l'arbre racine
* @param racine : la racine de l'arbre à vérifier
* @return null si l'arbre est cohérent, le noeud qui pose problème sinon
*/
private Noeud verifieCoherence(Noeud racine) {
// un arbre vide est cohérent
if (racine == null) {
return null;
}
int type = racine.getType();
int cat = racine.getCategorie();
// une erreur du lexer, ne devrait pas survenir ici!
if (type == Sym.ERROR) {
//racine.setTexteErreur("Erreur du lexer!");
racine.setTexteErreur("Caractère interdit!");
return racine;
}
// Une virgule ne peut pas être un noeud
if (racine.getType() == Sym.VIRGULE){
//if (racine.getNombreParametres() != Integer.MAX_VALUE && racine.getNombreParametres() != racine.getListeArguments().size()){
racine.setType(Sym.PARSER_COHERENCE_ERROR);
racine.setTexteErreur("Aucun paramètre attendu!");
return racine;
//}//
}
// une feuille est soit un Nombre, une Variable ou une constante ou un paramètre de tendance
if (type == Sym.NOMBRE || type == Sym.ID || type == Sym.PARAMETRE
|| cat == Sym.CATEGORIE_CONSTANTE ) {
if (!racine.estFeuille()) {
if (racine.getFilsD() != null){
racine = racine.getFilsD();
}
racine.setType(Sym.PARSER_COHERENCE_ERROR);
racine.setTexteErreur("Erreur de syntaxe!");
return racine;
}
return null;
}
// * / et ^ possèdent un fils gauche
if (type == Sym.FOIS || type == Sym.DIVISE || type == Sym.PUIS) {
if (racine.getFilsG() == null) {
racine.setType(Sym.PARSER_COHERENCE_ERROR);
racine.setTexteErreur("Erreur de syntaxe!");
return racine;
}
//return (racine.getFilsG() != null)? null: racine;
}
// un opérateur possède un fils droit
if (cat == Sym.CATEGORIE_OPERATEUR) {
if (racine.getFilsD() == null) {
racine.setType(Sym.PARSER_COHERENCE_ERROR);
racine.setTexteErreur("Erreur de syntaxe!");
return racine;
}
}
// une fonction possède le bon nombre d'arguments
// si nombreParametres == 1, il n'y a pas de liste d'arguments
// l'argument se trouve dans le fils gauche?
if (cat == Sym.CATEGORIE_FONCTION) {
int np = racine.getNombreParametres(); // nombre de paramètres obligatoires
int na = racine.getListeArguments() == null? 0 : racine.getListeArguments().size(); // nombre d'arguments
//System.out.println(racine.getTexteLexer() + " Nombre de paramètres : " + np + "Nombre d'arguments : " + na);
//racine.getRapport();
if (np == 0){ // ce cas n'existe pas encore (fonction sans paramètre)
} else if (np == 1){
if (racine.getFilsD() == null ){ // ne sert à rien?
racine.setType(Sym.PARSER_COHERENCE_ERROR);
racine.setTexteErreur("La fonction " + racine.getTexteLexer() +" nécessite 1 argument!");
return racine;
}
// if (na != 0){
// racine.setType(Sym.PARSER_COHERENCE_ERROR);
// racine.setTexteErreur("La fonction " + racine.getTexteLexer() +" nécessite 1 seul argument!");
// return racine;
// }
}
else if (np > 1 && np < Integer.MAX_VALUE) { // nombre limité de paramètres
if (np != na ) {
racine.setType(Sym.PARSER_COHERENCE_ERROR);
racine.setTexteErreur("La fonction " + racine.getTexteLexer() +" nécessite " + np + " argument(s)!");
return racine;
}
// if (racine.getNombreParametres() != racine.getListeArguments().size()) {
// racine.setType(Sym.PARSER_COHERENCE_ERROR);
// racine.setTexteErreur("Cette fonction n'a pas le bon nombres d'arguments!");
// return racine;
// }
} else { // nombre indéfini d'arguments, np == Integer.MAX_VALUE
if (racine.getNombreParametres() > 1 && racine.getListeArguments() == null) {
racine.setType(Sym.PARSER_COHERENCE_ERROR);
racine.setTexteErreur("La fonction " + racine.getTexteLexer() +" nécessite au moins 1 argument!");
return racine;
}
}
// Vérifier la cohérence des paramètres
if (na > 0){
for (int i = 0; i < racine.getListeArguments().size(); i++){
Noeud argument = verifieCoherence(racine.getListeArguments().get(i));
if (argument != null){
return argument;
}
}
// Vérifier la pertinence des paramètres
Noeud erreur;
switch (type){
case Sym.MOYENNE:
case Sym.RAND:
// Aucune vérification à faire
erreur = checkFilsDroit(racine);
if (erreur != null) return erreur;
break;
case Sym.DERIVEE:
// le deuxième paramètre doit être un nom de variable
erreur = checkFilsDroit(racine);
if (erreur != null) return erreur;
Noeud arg2 = racine.getListeArguments().get(1);
if (arg2.getType() != Sym.ID){
arg2.setType(Sym.PARSER_COHERENCE_ERROR);
arg2.setTexteErreur("La fonction " + racine.getTexteLexer() +
" nécessite une variable comme deuxième argument!");
return arg2;
}
break;
// retirée du lexer
case Sym.INTEGRALE:
erreur = checkFilsDroit(racine);
if (erreur != null) return erreur;
// le 4ème paramètre doit être un nom de variable
Noeud arg4 = racine.getListeArguments().get(3);
if (arg4.getType() != Sym.ID){
arg4.setType(Sym.PARSER_COHERENCE_ERROR);
arg4.setTexteErreur("La fonction " + racine.getTexteLexer() +
" nécessite une variable comme 4ème argument!");
return arg4;
}
break;
}
}
}
// // Le fils gauche du signe égal doit être un identificateur
// if (type == Sym.EGAL){
// if (racine.getFilsG() == null || racine.getFilsG().getType() != Sym.ID){
// racine.setType(Sym.PARSER_COHERENCE_ERROR);
// racine.setTexteErreur("Erreur : Nom = Expression!");
// return racine;
// }
// }
// Vérifier la cohérence du fils gauche
Noeud filsG = verifieCoherence(racine.getFilsG());
if (filsG != null) {
return filsG;
}
// Vérifier la cohérence du fils droit
Noeud filsD = verifieCoherence(racine.getFilsD());
if (filsD != null) {
return filsD;
}
return null;
}
/**
* Vérifie si le fils droit est bien null, utilisé dans la méthode parse
* @param racine
* @return
*/
private Noeud checkFilsDroit(Noeud racine){
// Cette fonction ne peut avoir de fils droit
Noeud filsD = racine.getFilsD();
if (filsD != null){
filsD.setType(Sym.PARSER_COHERENCE_ERROR);
filsD.setTexteErreur("Erreur de syntaxe!");
return filsD;
}
return null;
}
/**
* La méthode publique principale, lance le parsing de la liste de noeuds
* fournie au constructeur.
* @return
*/
public Noeud parser() {
Noeud noeud = parse(0, null);
if (noeud == null){
return noeud;
}
if (noeud.getType() == Sym.PARSER_COHERENCE_ERROR){
return noeud;
}
Noeud erreur = verifieCoherence(noeud);
if (erreur != null) {
noeud = erreur;
}
return noeud;
}
/**
* La méthode principale de la classe, elle est chargée de renvoyer
* un arbre correspondant à la liste de noeuds fournie par le lexer
* @param index du prochain noeud dans la liste
* @param parG est le noeud "(" si parse est appelée par une parenthèse ouvrante "("
* @return la racine de l'arbre
*/
private Noeud parse(int index, Noeud parG) {
Noeud noeud = getNoeudSuivant();
Noeud nCourant = null;
boolean attendArgument = false;
while (noeud != null) {
switch (noeud.getType()) {
case Sym.ERROR:
noeud.setTexteErreur("Erreur du lexer!");
return noeud;
case Sym.UNIT:
case Sym.COMMENT:
case Sym.COMMENT_TEXT:
case Sym.UNIT_TEXT:
attendArgument = false;
//nCourant = ajouteNoeudTerminal(noeud, nCourant);
break;
case Sym.EGAL:
attendArgument = false;
nCourant = ajouteNoeudNonTerminal(noeud, nCourant);
break;
case Sym.NOMBRE:
attendArgument = false;
nCourant = ajouteNoeudTerminal(noeud, nCourant);
//System.out.print("N");
break;
case Sym.PLUS:
case Sym.MOINS:
case Sym.FOIS:
case Sym.DIVISE:
attendArgument = false;
nCourant = ajouteNoeudNonTerminal(noeud, nCourant);
break;
case Sym.VIRGULE:
//System.out.println("Virgule : " + noeud.getTexteLexer());
if (attendArgument){
nCourant.setType(Sym.PARSER_COHERENCE_ERROR);
nCourant.setTexteErreur("Attente d'un argument");
return nCourant;
}
attendArgument = true;
nCourant = ajouteNoeudNonTerminal(noeud, nCourant);
break;
case Sym.PUIS:
attendArgument = false;
nCourant = ajouteNoeudNonTerminal(noeud, nCourant);
//System.out.print("^");
break;
case Sym.PUIS2:
attendArgument = false;
Noeud n = new Noeud(Sym.NOMBRE, Sym.CATEGORIE_SANS, Sym.PRIORITE_BASSE, 2.0, noeud.getLigne(), noeud.getColonne(), "");
noeud.setType(Sym.PUIS);
nCourant = ajouteNoeudNonTerminal(noeud, nCourant); // ajout du noeud PUIS
nCourant = ajouteNoeudTerminal(n, nCourant); // ajout du noeud NOMBRE : 2
//System.out.print("^2");
break;
case Sym.PUISN:
attendArgument = false;
Noeud n1 = new Noeud(Sym.NOMBRE, Sym.CATEGORIE_SANS, Sym.PRIORITE_BASSE, (double) noeud.getPuissance(), noeud.getLigne(), noeud.getColonne(), "");
//System.out.println(noeud.getPuissance());
noeud.setType(Sym.PUIS);
noeud.setCategorie(Sym.CATEGORIE_OPERATEUR);
noeud.setPriorite(Sym.PRIORITE_HAUTE);
nCourant = ajouteNoeudNonTerminal(noeud, nCourant); // ajout du noeud PUIS
nCourant = ajouteNoeudTerminal(n1, nCourant); // ajout du noeud NOMBRE : N
//System.out.print("^2");
break;
case Sym.ID:
case Sym.PARAMETRE:
attendArgument = false;
nCourant = ajouteNoeudTerminal(noeud, nCourant);
//System.out.print("Id : "+noeud.getNom());
break;
case Sym.SIN:
case Sym.COS:
case Sym.TAN:
case Sym.ASIN:
case Sym.ACOS:
case Sym.ATAN:
attendArgument = false;
nCourant = ajouteNoeudTerminal(noeud, nCourant);
break;
case Sym.LOG:
case Sym.LN:
case Sym.EXP:
case Sym.SQR:
case Sym.SQRT:
case Sym.ABS:
attendArgument = false;
nCourant = ajouteNoeudTerminal(noeud, nCourant);
break;
case Sym.PI:
case Sym.E:
attendArgument = false;
nCourant = ajouteNoeudTerminal(noeud, nCourant);
//System.out.print("ATAN");
break;
case Sym.MOYENNE:
attendArgument = false;
nCourant = ajouteNoeudTerminal(noeud, nCourant);
//System.out.print("ATAN");
break;
case Sym.DERIVEE:
case Sym.INTEGRALE:
case Sym.RAND:
attendArgument = false;
nCourant = ajouteNoeudTerminal(noeud, nCourant);
break;
case Sym.PARENG:
//attendArgument = true;
noeud = parse(indexListe + 1, noeud);
if (noeud != null && noeud.getType() == Sym.PARSER_COHERENCE_ERROR) {
return noeud;
}
// Mettre les arguments de fonction sous forme de liste
if (nCourant != null && nCourant.getCategorie() == Sym.CATEGORIE_FONCTION) {
if (nCourant.getNombreParametres() > 1) { // uniquement pour les fonctions à plusieurs paramètres
List<Noeud> listeP = new ArrayList<Noeud>();
int nombreParametres = getListeParametres(noeud, listeP);
if (nCourant.getNombreParametres() == Integer.MAX_VALUE || nombreParametres == nCourant.getNombreParametres()) {
ajouteNoeudTerminalArguments(listeP, nCourant);
break;
} else {
nCourant.setType(Sym.PARSER_COHERENCE_ERROR);
nCourant.setTexteErreur("Cette fonction nécessite " + nCourant.getNombreParametres() + " arguments!");
return nCourant;
}
} else { // fonction à 1 paramètre, vérifier qu'il n'y en a qu'un
List<Noeud> listeP = new ArrayList<Noeud>();
int nombreParametres = getListeParametres(noeud, listeP);
if (nombreParametres != 1){
nCourant.setType(Sym.PARSER_COHERENCE_ERROR);
nCourant.setTexteErreur("Cette fonction nécessite 1 seul argument!");
return nCourant;
}
}
}
nCourant = ajouteNoeudTerminal(noeud, nCourant);
attendArgument = false;
break;
case Sym.PAREND:
if (attendArgument){
nCourant.setType(Sym.PARSER_COHERENCE_ERROR);
nCourant.setTexteErreur("Attente d'un argument");
return nCourant;
}
if (parG == null) { // ici erreur : on détecte une parenthese droite mais il n'y a pas de gauche
noeud.setType(Sym.PARSER_COHERENCE_ERROR);
noeud.setTexteErreur("pas de parenthèse ouvrante associée!");
return noeud;
}
if (nCourant == null) {
Noeud erreur = new Noeud(Sym.PARSER_COHERENCE_ERROR, Sym.CATEGORIE_SANS, Sym.PRIORITE_HAUTE, 0, parG.getColonne(), "");
erreur.setTexteErreur("Expression vide!");
return erreur;
} else {
attendArgument = false;
return nCourant.getRacine();
}
//return (nCourant == null)? null : nCourant.getRacine();
default:
System.err.println("Erreur interne : Symbole non reconnu dans la méthode \"parse\"");
noeud.setType(Sym.PARSER_ERROR);
noeud.setTexteErreur("Symbole non reconnu!");
return noeud;
}
noeud = getNoeudSuivant();
}
//return liste.get(index).getRacine();
if (parG != null) { // on quitte parse sans avoir fermé la parenthese
parG.setType(Sym.PARSER_COHERENCE_ERROR);
parG.setTexteErreur("Parenthèse non fermée!");
return parG;
}
return (nCourant == null) ? null : nCourant.getRacine();
}
/**
* Ajoute listArg comme liste d'arguments à noeud
* @param listArg
* @param noeud
*/
private void ajouteNoeudTerminalArguments(List<Noeud> listArg, Noeud noeud) {
noeud.setListeArguments(listArg);
}
/* *************************************************************** */
//private List<Noeud> listeParametres;
private void donneParametres(Noeud noeud, List<Noeud> listeParametres) {
if (noeud.getType() != Sym.VIRGULE) {
listeParametres.add(noeud);
} else {
if (noeud.getFilsG() != null) {
donneParametres(noeud.getFilsG(), listeParametres);
}
if (noeud.getFilsD() != null) {
donneParametres(noeud.getFilsD(), listeParametres);
}
}
}
/**
* Remplit la listeParamètres avec les arguments trouvés dans l'arbre noeud.
* La liste doit être instanciée par l'appelant.
* @param noeud : le noeud racine des arguments
* @param listeParametres : la liste des arguments
* @return le nombre d'arguments trouvés
*/
private int getListeParametres(Noeud noeud, List<Noeud> listeParametres) {
listeParametres.clear();
if (noeud == null) {
return 0;
}
//if (noeud.getFilsD() == null) return 0;
donneParametres(noeud, listeParametres);
return listeParametres.size();
}
/* *************************************************************** */
/**
* renvoit le noeud suivant dans la liste de noeuds fournie par le lexer
* @return le noeud suivant dans la liste
*/
private Noeud getNoeudSuivant() {
if (liste == null) {
return null;
}
indexListe++;
if (indexListe < liste.size()) {
return liste.get(indexListe);
} else {
return null;
}
}
/**
* Ajoute noeud comme fils droit à nCourant
* @param noeud
*/
private Noeud ajouteNoeudTerminal(Noeud noeud, Noeud nCourant) {
if (noeud == null || nCourant == null) {
return noeud;
}
/*if (nCourant.estFilsGaucheLibre()) { // ne sert à rien sauf à détecter des erreurs pendant que je débugge
nCourant.setFisG(noeud);
noeud.setPere(nCourant);
System.err.println("<Parser.ajouteNoeudTerminal> : estFilsGaucheLibre!!");
}
else*/
if (nCourant.estFilsDroitLibre()) {
nCourant.setFilsD(noeud);
noeud.setPere(nCourant);
} else {
System.err.println("Erreur interne <Parser.ajouteNoeudTerminal>");
}
return noeud;
}
/**
* Ajoute le noeud au noeud courant en tenant compte de la priorité
* @param noeud
*/
private Noeud ajouteNoeudNonTerminal(Noeud noeud, Noeud nCourant) {
if (nCourant == null) {
nCourant = noeud;
} else {
Noeud n = getFilsAieulPrioritePlusFaible(noeud.getPriorite(), nCourant);
insereNoeudAuDessusDe(noeud, n);
}
return noeud;
}
/**
* Renvoit le fils de l'aieul de noeud nDepart qui possède une priorité inférieure
* à celle de noeud
*
* @return
*/
private Noeud getFilsAieulPrioritePlusFaible(int priorite, Noeud nDepart) {
Noeud n = nDepart;
while (true) { // pas très joli ça...
if (n.getPere() == null) { // on arrive à la racine de l'arbre
return n;
}
n = n.getPere();
if (n.getPriorite() < priorite) { // le pere de n a une priorité plus faible que priorite
return n.getFilsD();
}
}
}
/**
* Insere noeud entre noeudBas et noeudBas.getPere()
* @param noeud
*/
private void insereNoeudAuDessusDe(Noeud noeud, Noeud noeudBas) {
Noeud pere = noeudBas.getPere();
noeudBas.setPere(noeud);
noeud.setFisG(noeudBas);
noeud.setPere(pere);
if (pere != null) {
/*if (pere.estFilsGaucheLibre()) {
pere.setFisG(noeud);
System.err.println("Passe par estFilsGaucheLibre dans insereNoeudAuDessusDe");
}
else {*/
pere.setFilsD(noeud);
//}
}
//nCourant = noeud;
}
}