Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- //C++ Lexical Analyzer for Javascript, par Raphaël Dujardin (2014)
- #ifndef LEXICAL_H
- #define LEXICAL_H
- #include <vector>
- #include <string>
- #include <iostream>
- #include <map>
- //Définition des types de tokens existants en JavaScript
- enum TokenType
- {
- NULLTOKEN, //pour signifier une erreur de token inexistant
- INTEGER, //tokens à valeur, un nom est un identifiant (à ne pas confondre avec une chaîne de caractères)
- FLOATING,
- STRING,
- NAME,
- PLUS, //opérateurs
- MINUS,
- MULTIPLY,
- DIVIDE,
- MODULO,
- EQUAL,
- DBPLUS,
- DBMINUS,
- LPARENTHESIS, //délimiteurs
- RPARENTHESIS,
- LBRACKET,
- RBRACKET,
- LSQUARE,
- RSQUARE,
- COMMA, //ponctuations
- SEMICOLON,
- BREAK, //mots-clés
- CASE,
- CATCH,
- CONST,
- CONTINUE,
- DEBUGGER,
- DO,
- ELSE,
- FINALLY,
- FOR,
- FUNCTION,
- IF,
- IN,
- INSTANCEOF,
- LET,
- NEW,
- RETURN,
- SWITCH,
- THIS,
- THROW,
- TRY,
- TYPEOF,
- VAR,
- VOID,
- WHILE,
- INFERIOR, //opérateurs de comparaison
- SUPERIOR,
- DBEQUAL
- };
- //Structure Token : un token est caractérisé par son type et éventuellement sa valeur
- struct Token
- {
- TokenType type;
- std::string value;
- };
- //tostr : transforme un std::vector<char> en std::string
- std::string tostr(std::vector<char> buf)
- {
- std::string s="";
- for(unsigned int i=0;i<buf.size();i++)
- {
- s+=buf[i];
- }
- return s;
- }
- //numberbuf : analyse un cache de caractères et détermine s'il décrit ou non un nombre, si oui détermine s'il est entier ou flottant
- //si ce n'est pas un nombre, on renvoie NAME pour signifier que ce n'est pas un nombre, mais attention cela ne signifie pas que ce sera un NAME (cf. la fonction analyzebuf)
- TokenType numberbuf(std::vector<char> buf)
- {
- bool integer=true;
- for(unsigned int i=0;i<buf.size();i++)
- {
- if(buf[i]!='0' and buf[i]!='1' and buf[i]!='2' and buf[i]!='3' and buf[i]!='4' and buf[i]!='5' and buf[i]!='6' and buf[i]!='7' and buf[i]!='8' and buf[i]!='9')
- {
- if(integer && buf[i]=='.') integer=false;
- if((!integer) && buf[i]=='.') return NAME;
- if(buf[i]!='.') return NAME;
- }
- }
- if(integer) return INTEGER;
- else return FLOATING;
- }
- //analyzebuf : analyse un cache de caractères et détermine le type de token correspondant
- TokenType analyzebuf(std::vector<char> buf)
- {
- TokenType t=numberbuf(buf);
- if(t==INTEGER or t==FLOATING) return t;
- std::string s=tostr(buf);
- if(s=="==") return DBEQUAL;
- if(s=="break") return BREAK;
- if(s=="case") return CASE;
- if(s=="catch") return CATCH;
- if(s=="const") return CONST;
- if(s=="continue") return CONTINUE;
- if(s=="debugger") return DEBUGGER;
- if(s=="do") return DO;
- if(s=="else") return ELSE;
- if(s=="finally") return FINALLY;
- if(s=="for") return FOR;
- if(s=="function") return FUNCTION;
- if(s=="if") return IF;
- if(s=="in") return IN;
- if(s=="instanceof") return INSTANCEOF;
- if(s=="let") return LET;
- if(s=="new") return NEW;
- if(s=="return") return RETURN;
- if(s=="switch") return SWITCH;
- if(s=="this") return THIS;
- if(s=="throw") return THROW;
- if(s=="try") return TRY;
- if(s=="typeof") return TYPEOF;
- if(s=="var") return VAR;
- if(s=="void") return VOID;
- if(s=="while") return WHILE;
- else return NAME;
- }
- //printtokens : affiche le résultat d'une analyse lexicale dans un flux quelconque (std::cout, un fichier, etc.)
- void printtokens(std::ostream& str,std::vector<Token> tokens)
- {
- for(unsigned int i=0;i<tokens.size();i++)
- {
- switch(tokens[i].type)
- {
- case NULLTOKEN:
- str << "NULLTOKEN" << std::endl; break;
- case INTEGER:
- str << "INTEGER # " << tokens[i].value << std::endl; break;
- case FLOATING:
- str << "FLOATING # " << tokens[i].value << std::endl; break;
- case STRING:
- str << "STRING # " << tokens[i].value << std::endl; break;
- case NAME:
- str << "NAME # " << tokens[i].value << std::endl; break;
- case PLUS:
- str << "PLUS" << std::endl; break;
- case MINUS:
- str << "MINUS" << std::endl; break;
- case MULTIPLY:
- str << "MULTIPLY" << std::endl; break;
- case DIVIDE:
- str << "DIVIDE" << std::endl; break;
- case MODULO:
- str << "MODULO" << std::endl; break;
- case EQUAL:
- str << "EQUAL" << std::endl; break;
- case LPARENTHESIS:
- str << "LPARENTHESIS" << std::endl; break;
- case RPARENTHESIS:
- str << "RPARENTHESIS" << std::endl; break;
- case LBRACKET:
- str << "LBRACKET" << std::endl; break;
- case RBRACKET:
- str << "RBRACKET" << std::endl; break;
- case LSQUARE:
- str << "LSQUARE" << std::endl; break;
- case RSQUARE:
- str << "RSQUARE" << std::endl; break;
- case COMMA:
- str << "COMMA" << std::endl; break;
- case SEMICOLON:
- str << "SEMICOLON" << std::endl; break;
- case INFERIOR:
- str << "INFERIOR" << std::endl; break;
- case SUPERIOR:
- str << "SUPERIOR" << std::endl; break;
- case BREAK:
- str << "BREAK" << std::endl; break;
- case CASE:
- str << "CASE" << std::endl; break;
- case CATCH:
- str << "CATCH" << std::endl; break;
- case CONST:
- str << "CONST" << std::endl; break;
- case CONTINUE:
- str << "CONTINUE" << std::endl; break;
- case DEBUGGER:
- str << "DEBUGGER" << std::endl; break;
- case DO:
- str << "DO" << std::endl; break;
- case ELSE:
- str << "ELSE" << std::endl; break;
- case FINALLY:
- str << "FINALLY" << std::endl; break;
- case FOR:
- str << "FOR" << std::endl; break;
- case FUNCTION:
- str << "FUNCTION" << std::endl; break;
- case IF:
- str << "IF" << std::endl; break;
- case IN:
- str << "IN" << std::endl; break;
- case INSTANCEOF:
- str << "INSTANCEOF" << std::endl; break;
- case LET:
- str << "LET" << std::endl; break;
- case NEW:
- str << "NEW" << std::endl; break;
- case RETURN:
- str << "RETURN" << std::endl; break;
- case SWITCH:
- str << "SWITCH" << std::endl; break;
- case THIS:
- str << "THIS" << std::endl; break;
- case THROW:
- str << "THROW" << std::endl; break;
- case TRY:
- str << "TRY" << std::endl; break;
- case TYPEOF:
- str << "TYPEOF" << std::endl; break;
- case VAR:
- str << "VAR" << std::endl; break;
- case VOID:
- str << "VOID" << std::endl; break;
- case WHILE:
- str << "WHILE" << std::endl; break;
- case DBEQUAL:
- str << "DBEQUAL" << std::endl; break;
- case DBPLUS:
- str << "DBPLUS" << std::endl; break;
- case DBMINUS:
- str << "DBMINUS" << std::endl; break;
- }
- }
- }
- //lex : effectue l'analyse lexicale sur un code JavaScript donné et en renvoie la liste de tokens résultante
- std::vector<Token> lex(std::string& code)
- {
- //Initialisation des données (liste de tokens, code d'entrée, cache de caractères)
- std::vector<Token> tokens;
- const char* str=code.c_str();
- std::vector<char> buffer;
- //Initialisation de l'état (au sens d'un automate à états finis)
- bool multicommentary=false; //si on est actuellement dans un commentaire multi-ligne
- bool linecommentary=false; //si on est actuellement dans un commentaire mono-ligne
- bool quotestring=false; //si on est actuellement dans une chaîne de caractères délimitée par de simples apostrophes
- bool dbquotestring=false; //si on est actuellement dans une chaîne de caractères délimitée par des guillemets
- //Initialisation du caractère temporaire (servant à identifier les motifs à 2 caractères)
- char temp=0;
- //Itération sur les caractères du code d'entrée
- for(const char* it=str;*it;it++)
- {
- //Si on est actuellement dans un commentaire multi-ligne
- if(multicommentary)
- {
- //Si le caractère est / et l'immédiatement précédent était *, on sort du commentaire
- //On ignore les commentaires donc on n'ajoute pas de token
- //Remarque : JavaScript ne supporte pas l'imbrication des commentaires
- if(*it=='/' && temp=='*') { temp=0; multicommentary=false; }
- //Si le caractère est * on l'enregistre pour le cas où le suivant serait /
- if(*it=='*') temp=*it;
- else temp=0;
- }
- //Si on est actuellement dans un commentaire mono-ligne
- else if(linecommentary)
- {
- //Si on a un saut de ligne, on sort du commentaire
- //On ignore les commentaires donc on n'ajoute pas de token
- if(*it=='\n') { linecommentary=false; }
- }
- //Si on est actuellement dans une chaîne de caractères à simples apostrophes
- else if(quotestring)
- {
- //Si on a une simple apostrophe qui n'a pas été échappée par un antislash
- if(*it=='\'' && temp!='\\')
- {
- //Alors on sort de la chaîne et on enregistre le token STRING correspondant avec pour valeur les caractères mis en cache depuis le début de la chaîne
- temp=0; quotestring=false;
- Token tokenstr;
- tokenstr.type=STRING;
- tokenstr.value=tostr(buffer);
- tokens.push_back(tokenstr);
- //On vide le cache
- buffer.clear();
- }
- //Sinon cela signifie qu'on est toujours dans la chaîne
- else
- {
- //Si on a un antislash on l'enregistre au cas où le caractère suivant serait une simple apostrophe
- //Dans tous les cas on ajoute le caractère courant au cache car il appartient à la chaîne
- if(*it=='\\') { temp=*it; buffer.push_back(*it); }
- else { temp=0; buffer.push_back(*it); }
- }
- }
- //Si on est actuellement dans une chaîne de caractères à guillemets
- else if(dbquotestring)
- {
- //Si on a un guillemet qui n'a pas été échappé par un antislash
- if(*it=='"' && temp!='\\')
- {
- //Alors on sort de la chaîne et on enregistre le token STRING correspondant avec pour valeur les caractères mis en cache depuis le début de la chaîne
- temp=0; dbquotestring=false;
- Token tokenstr;
- tokenstr.type=STRING;
- tokenstr.value=tostr(buffer);
- tokens.push_back(tokenstr);
- //On vide le cache
- buffer.clear();
- }
- //Sinon cela signifie qu'on est toujours dans la chaîne
- else
- {
- //Si on a un antislash on l'enregistre au cas où le caractère suivant serait un guillemet
- if(*it=='\\') temp=*it;
- //Sinon on ajoute le caractère courant au cache car il appartient à la chaîne
- else { temp=0; buffer.push_back(*it); }
- }
- }
- //Sinon, si on est en état normal (ni en commentaire ni en chaîne de caractères)
- else
- {
- //Si on a un caractère qui correspond à l'un des tokens à un caractère
- if(*it=='<' or *it=='>' or *it==' ' or *it=='\n' or *it=='\r' or *it=='\t' or *it=='+' or *it=='-' or *it=='*' or *it=='/' or *it=='%' or *it=='=' or *it=='(' or *it==')' or *it=='{' or *it=='}' or *it=='[' or *it==']' or *it==',' or *it==';' or *it=='\'' or *it=='"')
- {
- //Si le cache n'est pas vide, notre token à un caractère vient de mettre fin à un token non encore identifié (typiquement un nombre ou un nom), on analyse donc ce dernier et on ajoute le token correspondant avant d'ajouter notre token à un caractère
- if(buffer.size()>0)
- {
- Token tokenb;
- tokenb.type=analyzebuf(buffer);
- tokenb.value=tostr(buffer);
- tokens.push_back(tokenb);
- //On vide le cache
- buffer.clear();
- }
- //On ajoute notre token à un caractère dont le type dépend du dit caractère
- //Si on a un caractère qui peut être doublé pour former un autre token (par ex + est un token PLUS mais ++ est un token DBPLUS), on vérifie si le token précédent correspondait au même caractère, si tel est le cas on remplace les deux par un seul token de type double
- //Cela vaut aussi pour les débuts de commentaires : si on a * on vérifie si le précédent était / pour former une seule entité /*, qui ne sera pas ajoutée comme token par contre l'état passe en état commentaire multi-ligne, de même pour / qui précédé de / donne // et ouvre donc un commentaire mono-ligne
- //Si on a une simple apostrophe ou un guillemet, on n'ajoute pas de token mais on se place en état chaîne de caractères (quote ou dbquote selon le caractère ouvrant rencontré)
- Token token;
- bool add=true;
- if(*it=='<') token.type=INFERIOR;
- if(*it=='>') token.type=SUPERIOR;
- if(*it=='+') { if(tokens.back().type==PLUS) { tokens[tokens.size()-1].type=DBPLUS; add=false; } else token.type=PLUS;}
- if(*it=='-') { if(tokens.back().type==MINUS) { tokens[tokens.size()-1].type=DBMINUS; add=false; } else token.type=MINUS;}
- if(*it=='*') { if(tokens.back().type==DIVIDE) { tokens.pop_back(); multicommentary=true; add=false; } else token.type=MULTIPLY; }
- if(*it=='/') { if(tokens.back().type==DIVIDE) { tokens.pop_back(); linecommentary=true; add=false; } else token.type=DIVIDE; }
- if(*it=='%') token.type=MODULO;
- if(*it=='\'') { quotestring=true; add=false; }
- if(*it=='"') { dbquotestring=true; add=false; }
- if(*it==',') token.type=COMMA;
- if(*it==';') token.type=SEMICOLON;
- if(*it=='(') token.type=LPARENTHESIS;
- if(*it==')') token.type=RPARENTHESIS;
- if(*it=='{') token.type=LBRACKET;
- if(*it=='}') token.type=RBRACKET;
- if(*it=='[') token.type=LSQUARE;
- if(*it==']') token.type=RSQUARE;
- if(*it=='=') { if(tokens.back().type==EQUAL) { tokens[tokens.size()-1].type=DBEQUAL; add=false; } else token.type=EQUAL;}
- //On ignore les espaces, les sauts de lignes et les tabulations
- if(*it!=' ' && *it!='\n' && *it!='\r' && *it!='\t' && add) tokens.push_back(token);
- }
- //Si notre caractère n'est aucun de ceux-là (tokens à un ou deux caractères), on l'ajoute au cache
- else buffer.push_back(*it);
- }
- }
- //Si à la fin le cache n'est pas vide on analyse son contenu et on ajoute le dernier token correspondant (même opération que quand on rencontre un token à un caractère au milieu du code)
- if(buffer.size()>0)
- {
- Token tokenb;
- tokenb.type=analyzebuf(buffer);
- tokenb.value=tostr(buffer);
- tokens.push_back(tokenb);
- buffer.clear();
- }
- //On renvoie la liste de tokens, résultat de l'analyse lexicale
- return tokens;
- }
- #endif // LEXICAL_H
- //Nota Bene : il y a une limitation temporaire : les multiplications doivent être explicites, au moins par un espace. 2*B ou 2 B mais pas 2B, sinon lex le considère comme un seul nom "2B", et ce que le nombre soit entier ou flottant.
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement