Advertisement
rdujardin

C++ Lexical Analyzer for Javascript par Raphaël Dujardin

Apr 27th, 2015
205
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
C++ 15.71 KB | None | 0 0
  1. //C++ Lexical Analyzer for Javascript, par Raphaël Dujardin (2014)
  2.  
  3. #ifndef LEXICAL_H
  4. #define LEXICAL_H
  5.  
  6. #include <vector>
  7. #include <string>
  8. #include <iostream>
  9. #include <map>
  10.  
  11. //Définition des types de tokens existants en JavaScript
  12. enum TokenType
  13. {
  14.     NULLTOKEN, //pour signifier une erreur de token inexistant
  15.  
  16.     INTEGER, //tokens à valeur, un nom est un identifiant (à ne pas confondre avec une chaîne de caractères)
  17.     FLOATING,
  18.     STRING,
  19.     NAME,
  20.  
  21.     PLUS, //opérateurs
  22.     MINUS,
  23.     MULTIPLY,
  24.     DIVIDE,
  25.     MODULO,
  26.     EQUAL,
  27.     DBPLUS,
  28.     DBMINUS,
  29.  
  30.     LPARENTHESIS, //délimiteurs
  31.     RPARENTHESIS,
  32.     LBRACKET,
  33.     RBRACKET,
  34.     LSQUARE,
  35.     RSQUARE,
  36.  
  37.     COMMA, //ponctuations
  38.     SEMICOLON,
  39.  
  40.     BREAK, //mots-clés
  41.     CASE,
  42.     CATCH,
  43.     CONST,
  44.     CONTINUE,
  45.     DEBUGGER,
  46.     DO,
  47.     ELSE,
  48.     FINALLY,
  49.     FOR,
  50.     FUNCTION,
  51.     IF,
  52.     IN,
  53.     INSTANCEOF,
  54.     LET,
  55.     NEW,
  56.     RETURN,
  57.     SWITCH,
  58.     THIS,
  59.     THROW,
  60.     TRY,
  61.     TYPEOF,
  62.     VAR,
  63.     VOID,
  64.     WHILE,
  65.    
  66.     INFERIOR, //opérateurs de comparaison
  67.     SUPERIOR,
  68.     DBEQUAL
  69. };
  70.  
  71. //Structure Token : un token est caractérisé par son type et éventuellement sa valeur
  72. struct Token
  73. {
  74.     TokenType type;
  75.     std::string value;
  76. };
  77.  
  78. //tostr : transforme un std::vector<char> en std::string
  79. std::string tostr(std::vector<char> buf)
  80. {
  81.     std::string s="";
  82.     for(unsigned int i=0;i<buf.size();i++)
  83.     {
  84.         s+=buf[i];
  85.     }
  86.     return s;
  87. }
  88.  
  89. //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
  90. //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)
  91. TokenType numberbuf(std::vector<char> buf)
  92. {
  93.     bool integer=true;
  94.     for(unsigned int i=0;i<buf.size();i++)
  95.     {
  96.         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')
  97.         {
  98.             if(integer && buf[i]=='.') integer=false;
  99.             if((!integer) && buf[i]=='.') return NAME;
  100.             if(buf[i]!='.') return NAME;
  101.         }
  102.     }
  103.     if(integer) return INTEGER;
  104.     else return FLOATING;
  105. }
  106.  
  107. //analyzebuf : analyse un cache de caractères et détermine le type de token correspondant
  108. TokenType analyzebuf(std::vector<char> buf)
  109. {
  110.     TokenType t=numberbuf(buf);
  111.     if(t==INTEGER or t==FLOATING) return t;
  112.  
  113.     std::string s=tostr(buf);
  114.     if(s=="==") return DBEQUAL;
  115.     if(s=="break") return BREAK;
  116.     if(s=="case") return CASE;
  117.     if(s=="catch") return CATCH;
  118.     if(s=="const") return CONST;
  119.     if(s=="continue") return CONTINUE;
  120.     if(s=="debugger") return DEBUGGER;
  121.     if(s=="do") return DO;
  122.     if(s=="else") return ELSE;
  123.     if(s=="finally") return FINALLY;
  124.     if(s=="for") return FOR;
  125.     if(s=="function") return FUNCTION;
  126.     if(s=="if") return IF;
  127.     if(s=="in") return IN;
  128.     if(s=="instanceof") return INSTANCEOF;
  129.     if(s=="let") return LET;
  130.     if(s=="new") return NEW;
  131.     if(s=="return") return RETURN;
  132.     if(s=="switch") return SWITCH;
  133.     if(s=="this") return THIS;
  134.     if(s=="throw") return THROW;
  135.     if(s=="try") return TRY;
  136.     if(s=="typeof") return TYPEOF;
  137.     if(s=="var") return VAR;
  138.     if(s=="void") return VOID;
  139.     if(s=="while") return WHILE;
  140.     else return NAME;
  141. }
  142.  
  143. //printtokens : affiche le résultat d'une analyse lexicale dans un flux quelconque (std::cout, un fichier, etc.)
  144. void printtokens(std::ostream& str,std::vector<Token> tokens)
  145. {
  146.     for(unsigned int i=0;i<tokens.size();i++)
  147.     {
  148.         switch(tokens[i].type)
  149.         {
  150.         case NULLTOKEN:
  151.             str << "NULLTOKEN" << std::endl; break;
  152.         case INTEGER:
  153.             str << "INTEGER # " << tokens[i].value << std::endl; break;
  154.         case FLOATING:
  155.             str << "FLOATING # " << tokens[i].value << std::endl; break;
  156.         case STRING:
  157.             str << "STRING # " << tokens[i].value << std::endl; break;
  158.         case NAME:
  159.             str << "NAME # " << tokens[i].value << std::endl; break;
  160.         case PLUS:
  161.             str << "PLUS" << std::endl; break;
  162.         case MINUS:
  163.             str << "MINUS" << std::endl; break;
  164.         case MULTIPLY:
  165.             str << "MULTIPLY" << std::endl; break;
  166.         case DIVIDE:
  167.             str << "DIVIDE" << std::endl; break;
  168.         case MODULO:
  169.             str << "MODULO" << std::endl; break;
  170.         case EQUAL:
  171.             str << "EQUAL" << std::endl; break;
  172.         case LPARENTHESIS:
  173.             str << "LPARENTHESIS" << std::endl; break;
  174.         case RPARENTHESIS:
  175.             str << "RPARENTHESIS" << std::endl; break;
  176.         case LBRACKET:
  177.             str << "LBRACKET" << std::endl; break;
  178.         case RBRACKET:
  179.             str << "RBRACKET" << std::endl; break;
  180.         case LSQUARE:
  181.             str << "LSQUARE" << std::endl; break;
  182.         case RSQUARE:
  183.             str << "RSQUARE" << std::endl; break;
  184.         case COMMA:
  185.             str << "COMMA" << std::endl; break;
  186.         case SEMICOLON:
  187.             str << "SEMICOLON" << std::endl; break;
  188.         case INFERIOR:
  189.             str << "INFERIOR" << std::endl; break;
  190.         case SUPERIOR:
  191.             str << "SUPERIOR" << std::endl; break;
  192.         case BREAK:
  193.             str << "BREAK" << std::endl; break;
  194.         case CASE:
  195.             str << "CASE" << std::endl; break;
  196.         case CATCH:
  197.             str << "CATCH" << std::endl; break;
  198.         case CONST:
  199.             str << "CONST" << std::endl; break;
  200.         case CONTINUE:
  201.             str << "CONTINUE" << std::endl; break;
  202.         case DEBUGGER:
  203.             str << "DEBUGGER" << std::endl; break;
  204.         case DO:
  205.             str << "DO" << std::endl; break;
  206.         case ELSE:
  207.             str << "ELSE" << std::endl; break;
  208.         case FINALLY:
  209.             str << "FINALLY" << std::endl; break;
  210.         case FOR:
  211.             str << "FOR" << std::endl; break;
  212.         case FUNCTION:
  213.             str << "FUNCTION" << std::endl; break;
  214.         case IF:
  215.             str << "IF" << std::endl; break;
  216.         case IN:
  217.             str << "IN" << std::endl; break;
  218.         case INSTANCEOF:
  219.             str << "INSTANCEOF" << std::endl; break;
  220.         case LET:
  221.             str << "LET" << std::endl; break;
  222.         case NEW:
  223.             str << "NEW" << std::endl; break;
  224.         case RETURN:
  225.             str << "RETURN" << std::endl; break;
  226.         case SWITCH:
  227.             str << "SWITCH" << std::endl; break;
  228.         case THIS:
  229.             str << "THIS" << std::endl; break;
  230.         case THROW:
  231.             str << "THROW" << std::endl; break;
  232.         case TRY:
  233.             str << "TRY" << std::endl; break;
  234.         case TYPEOF:
  235.             str << "TYPEOF" << std::endl; break;
  236.         case VAR:
  237.             str << "VAR" << std::endl; break;
  238.         case VOID:
  239.             str << "VOID" << std::endl; break;
  240.         case WHILE:
  241.             str << "WHILE" << std::endl; break;
  242.         case DBEQUAL:
  243.             str << "DBEQUAL" << std::endl; break;
  244.         case DBPLUS:
  245.             str << "DBPLUS" << std::endl; break;
  246.         case DBMINUS:
  247.             str << "DBMINUS" << std::endl; break;
  248.         }
  249.     }
  250. }
  251.  
  252. //lex : effectue l'analyse lexicale sur un code JavaScript donné et en renvoie la liste de tokens résultante
  253. std::vector<Token> lex(std::string& code)
  254. {
  255.     //Initialisation des données (liste de tokens, code d'entrée, cache de caractères)
  256.     std::vector<Token> tokens;
  257.     const char* str=code.c_str();
  258.     std::vector<char> buffer;
  259.  
  260.     //Initialisation de l'état (au sens d'un automate à états finis)
  261.     bool multicommentary=false; //si on est actuellement dans un commentaire multi-ligne
  262.     bool linecommentary=false; //si on est actuellement dans un commentaire mono-ligne
  263.  
  264.     bool quotestring=false; //si on est actuellement dans une chaîne de caractères délimitée par de simples apostrophes
  265.     bool dbquotestring=false; //si on est actuellement dans une chaîne de caractères délimitée par des guillemets
  266.  
  267.     //Initialisation du caractère temporaire (servant à identifier les motifs à 2 caractères)
  268.     char temp=0;
  269.  
  270.     //Itération sur les caractères du code d'entrée
  271.     for(const char* it=str;*it;it++)
  272.     {
  273.         //Si on est actuellement dans un commentaire multi-ligne
  274.         if(multicommentary)
  275.         {
  276.             //Si le caractère est / et l'immédiatement précédent était *, on sort du commentaire
  277.             //On ignore les commentaires donc on n'ajoute pas de token
  278.             //Remarque : JavaScript ne supporte pas l'imbrication des commentaires
  279.             if(*it=='/' && temp=='*') { temp=0; multicommentary=false; }
  280.  
  281.             //Si le caractère est * on l'enregistre pour le cas où le suivant serait /
  282.             if(*it=='*') temp=*it;
  283.             else temp=0;
  284.         }
  285.         //Si on est actuellement dans un commentaire mono-ligne
  286.         else if(linecommentary)
  287.         {
  288.             //Si on a un saut de ligne, on sort du commentaire
  289.             //On ignore les commentaires donc on n'ajoute pas de token
  290.             if(*it=='\n') { linecommentary=false; }
  291.         }
  292.         //Si on est actuellement dans une chaîne de caractères à simples apostrophes
  293.         else if(quotestring)
  294.         {
  295.             //Si on a une simple apostrophe qui n'a pas été échappée par un antislash
  296.             if(*it=='\'' && temp!='\\')
  297.             {
  298.                 //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
  299.                 temp=0; quotestring=false;
  300.                 Token tokenstr;
  301.                 tokenstr.type=STRING;
  302.                 tokenstr.value=tostr(buffer);
  303.                 tokens.push_back(tokenstr);
  304.                 //On vide le cache
  305.                 buffer.clear();
  306.             }
  307.             //Sinon cela signifie qu'on est toujours dans la chaîne
  308.             else
  309.             {
  310.                 //Si on a un antislash on l'enregistre au cas où le caractère suivant serait une simple apostrophe
  311.                 //Dans tous les cas on ajoute le caractère courant au cache car il appartient à la chaîne
  312.                 if(*it=='\\') { temp=*it; buffer.push_back(*it); }
  313.                 else { temp=0; buffer.push_back(*it); }
  314.             }
  315.         }
  316.         //Si on est actuellement dans une chaîne de caractères à guillemets
  317.         else if(dbquotestring)
  318.         {
  319.             //Si on a un guillemet qui n'a pas été échappé par un antislash
  320.             if(*it=='"' && temp!='\\')
  321.             {
  322.                 //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
  323.                 temp=0; dbquotestring=false;
  324.                 Token tokenstr;
  325.                 tokenstr.type=STRING;
  326.                 tokenstr.value=tostr(buffer);
  327.                 tokens.push_back(tokenstr);
  328.                 //On vide le cache
  329.                 buffer.clear();
  330.             }
  331.             //Sinon cela signifie qu'on est toujours dans la chaîne
  332.             else
  333.             {
  334.                 //Si on a un antislash on l'enregistre au cas où le caractère suivant serait un guillemet
  335.                 if(*it=='\\') temp=*it;
  336.                 //Sinon on ajoute le caractère courant au cache car il appartient à la chaîne
  337.                 else { temp=0; buffer.push_back(*it); }
  338.             }
  339.         }
  340.         //Sinon, si on est en état normal (ni en commentaire ni en chaîne de caractères)
  341.         else
  342.         {
  343.             //Si on a un caractère qui correspond à l'un des tokens à un caractère
  344.             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=='"')
  345.             {
  346.                 //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
  347.                 if(buffer.size()>0)
  348.                 {
  349.                     Token tokenb;
  350.                     tokenb.type=analyzebuf(buffer);
  351.                     tokenb.value=tostr(buffer);
  352.                     tokens.push_back(tokenb);
  353.                     //On vide le cache
  354.                     buffer.clear();
  355.                 }
  356.  
  357.                 //On ajoute notre token à un caractère dont le type dépend du dit caractère
  358.                 //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
  359.                 //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
  360.                 //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é)
  361.                 Token token;
  362.                 bool add=true;
  363.                 if(*it=='<') token.type=INFERIOR;
  364.                 if(*it=='>') token.type=SUPERIOR;
  365.                 if(*it=='+') { if(tokens.back().type==PLUS) { tokens[tokens.size()-1].type=DBPLUS; add=false; } else token.type=PLUS;}
  366.                 if(*it=='-') { if(tokens.back().type==MINUS) { tokens[tokens.size()-1].type=DBMINUS; add=false; } else token.type=MINUS;}
  367.                 if(*it=='*') { if(tokens.back().type==DIVIDE) { tokens.pop_back(); multicommentary=true; add=false; } else token.type=MULTIPLY; }
  368.                 if(*it=='/') { if(tokens.back().type==DIVIDE) { tokens.pop_back(); linecommentary=true; add=false; } else token.type=DIVIDE; }
  369.                 if(*it=='%') token.type=MODULO;
  370.                 if(*it=='\'') { quotestring=true; add=false; }
  371.                 if(*it=='"') { dbquotestring=true; add=false; }
  372.                 if(*it==',') token.type=COMMA;
  373.                 if(*it==';') token.type=SEMICOLON;
  374.                 if(*it=='(') token.type=LPARENTHESIS;
  375.                 if(*it==')') token.type=RPARENTHESIS;
  376.                 if(*it=='{') token.type=LBRACKET;
  377.                 if(*it=='}') token.type=RBRACKET;
  378.                 if(*it=='[') token.type=LSQUARE;
  379.                 if(*it==']') token.type=RSQUARE;
  380.                 if(*it=='=') { if(tokens.back().type==EQUAL) { tokens[tokens.size()-1].type=DBEQUAL; add=false; } else token.type=EQUAL;}
  381.  
  382.                 //On ignore les espaces, les sauts de lignes et les tabulations
  383.                 if(*it!=' ' && *it!='\n' && *it!='\r' && *it!='\t' && add) tokens.push_back(token);
  384.             }
  385.             //Si notre caractère n'est aucun de ceux-là (tokens à un ou deux caractères), on l'ajoute au cache
  386.             else buffer.push_back(*it);
  387.         }
  388.     }
  389.     //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)
  390.     if(buffer.size()>0)
  391.     {
  392.         Token tokenb;
  393.         tokenb.type=analyzebuf(buffer);
  394.         tokenb.value=tostr(buffer);
  395.         tokens.push_back(tokenb);
  396.         buffer.clear();
  397.     }
  398.  
  399.     //On renvoie la liste de tokens, résultat de l'analyse lexicale
  400.     return tokens;
  401. }
  402.  
  403.  
  404.  
  405. #endif // LEXICAL_H
  406.  
  407. //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