23-02.識別子
driverクラスの修正
この項では変数を実装してみます。ここで変数とは、任意の名前を持ち、int型やdouble型の値をセットできるオブジェクトとします。まず、driver.hを書き換えます。
#ifndef DRIVER_H #define DRIVER_H #include <iostream> #include <fstream> #include <string> #include <vector> using namespace std; enum class valType{ intType, doubleType, stringType, boolType, }; union value { int m_intVal; double m_doubleVal; bool m_boolVal; string m_strVal; value():m_intVal(0){} ~value(){} }; struct ObjBase{ protected: ObjBase(){} public: ~ObjBase(){} }; struct Expression : public ObjBase{ Expression(): ObjBase() {} Expression(const Expression& other): ObjBase() { m_valType = other.m_valType; m_variable = other.m_variable; switch(m_valType){ case valType::intType: m_value.m_intVal = other.m_value.m_intVal; break; case valType::doubleType: m_value.m_doubleVal = other.m_value.m_doubleVal; break; case valType::boolType: m_value.m_boolVal = other.m_value.m_boolVal; break; case valType::stringType: m_value.m_strVal = other.m_value.m_strVal; break; } } valType m_valType; value m_value; string m_variable; }; class Driver { Driver (); ~Driver (); vector<ObjBase*> m_pObjPool; public: void output(Expression* a); Expression* createDOUBLE(const string& str); Expression* createINT(const string& str); Expression* createVARIABLE(const string& str); Expression* copyExp(Expression* other); Expression* assExp(Expression* dest,Expression* src); Expression* calcADD(Expression* left,Expression* right); Expression* calcSUB(Expression* left,Expression* right); Expression* calcMUL(Expression* left,const Expression* right); Expression* calcDIV(Expression* left,Expression* right); int parse (const string& f); string m_file; static Driver* get(); }; #endif // ! DRIVER_H前項から大きく変わったところにExpressionクラス(構造体)があります。expression(式)の意味です。式というと数学での
y = a * x + bのようなものを思い浮かべるかもしれませんが、プログラム言語における式の概念はちょっと違います。何かの操作をして結果を 返すものという感じでしょうか。例えば
"hello world"という文字列があった場合、これも文字列リテラル式というりっぱな式です。
C言語の代入
val = 30も代入式という式です。valという変数に3を代入して、代入した値そのもの3を返します。そういう風に、プログラム言語の式はかなり広い意味で使われます。
数値や文字列、あるいはbool値などを、保存しておくのにExpressionクラスは使われます。ですから内部に
valType m_valType; value m_value; string m_variable;といったメンバ変数を持っています。valTypeは、上部に定義されている
enum class valType{ intType, doubleType, stringType, boolType, };の変数ですenum classですからどういう型かを指定します。valueは、値です。
union value { int m_intVal; double m_doubleVal; bool m_boolVal; string m_strVal; value():m_intVal(0){} ~value(){} };と定義されています。
Expressionクラスは、親クラスとしてObjBaseクラスを持ちます。このクラスは直接インスタンスを作成できない形としプロテクトコンストラクタを持ちます。
この項で実装するExpressionクラス以外でも、ObjBaseクラスから継承すれば、ObjBaseクラスのポインタの配列である。
vector<ObjBase*> m_pObjPool;に追加することができます。
また、この項からDriverクラスと、先頭を大文字にしています。
driver.cpp
続いて、driver.cppを書き換えます。driver.hに宣言された関数の実体を記述します。#include "driver.h" Driver::Driver () { } Driver::~Driver () { for(auto& v : m_pObjPool){ delete v; } m_pObjPool.clear(); } void Driver::output(Expression* a){ switch(a->m_valType){ case valType::intType: cout << ">>" << a->m_value.m_intVal << std::endl; break; case valType::doubleType: cout << ">>" << a->m_value.m_doubleVal << std::endl; break; case valType::boolType: cout << ">>" << a->m_value.m_boolVal << std::endl; break; case valType::stringType: cout << ">>" << a->m_value.m_strVal << std::endl; break; } } Expression* Driver::createDOUBLE(const string& str){ double d = (double)strtof(str.c_str(),nullptr); Expression* temp = new Expression(); temp->m_valType = valType::doubleType; temp->m_value.m_doubleVal = d; m_pObjPool.push_back(temp); return temp; } Expression* Driver::createINT(const string& str){ int i = (int)strtod(str.c_str(),nullptr); Expression* temp = new Expression(); temp->m_valType = valType::intType; temp->m_value.m_intVal = i; m_pObjPool.push_back(temp); return temp; } Expression* Driver::copyExp(Expression* other){ Expression* temp = new Expression(); temp->m_valType = other->m_valType; temp->m_variable = other->m_variable; switch(temp->m_valType){ case valType::intType: temp->m_value.m_intVal = other->m_value.m_intVal; break; case valType::doubleType: temp->m_value.m_doubleVal = other->m_value.m_doubleVal; break; case valType::boolType: temp->m_value.m_boolVal = other->m_value.m_boolVal; break; case valType::stringType: temp->m_value.m_strVal = other->m_value.m_strVal; break; } m_pObjPool.push_back(temp); return temp; } Expression* Driver::assExp(Expression* dest,Expression* src){ dest->m_valType = src->m_valType; switch(src->m_valType){ case valType::intType: dest->m_value.m_intVal = src->m_value.m_intVal; break; case valType::doubleType: dest->m_value.m_doubleVal = src->m_value.m_doubleVal; break; case valType::boolType: dest->m_value.m_boolVal = src->m_value.m_boolVal; break; case valType::stringType: dest->m_value.m_strVal = src->m_value.m_strVal; break; } return dest; } #define MAX_NUM_BUFF 100 Expression* Driver::calcADD(Expression* left,Expression* right){ char buff[MAX_NUM_BUFF]; switch(left->m_valType){ case valType::intType: switch(right->m_valType){ case valType::intType: left->m_value.m_intVal += right->m_value.m_intVal; break; case valType::doubleType: left->m_value.m_intVal += (int)right->m_value.m_doubleVal; break; case valType::boolType: //何もしない break; case valType::stringType: left->m_value.m_intVal += atoi(right->m_value.m_strVal.c_str()); break; } break; case valType::doubleType: switch(right->m_valType){ case valType::intType: left->m_value.m_doubleVal += (double)right->m_value.m_intVal; break; case valType::doubleType: left->m_value.m_doubleVal += right->m_value.m_doubleVal; break; case valType::boolType: //何もしない break; case valType::stringType: left->m_value.m_doubleVal += atof(right->m_value.m_strVal.c_str()); break; } break; case valType::boolType: //何もしない break; case valType::stringType: switch(right->m_valType){ case valType::intType: snprintf(buff,MAX_NUM_BUFF,"%d",right->m_value.m_intVal); left->m_value.m_strVal += buff; break; case valType::doubleType: snprintf(buff,MAX_NUM_BUFF,"%f",right->m_value.m_doubleVal); left->m_value.m_strVal += buff; break; case valType::boolType: //何もしない break; case valType::stringType: left->m_value.m_strVal += right->m_value.m_strVal; break; } left->m_value.m_strVal += right->m_value.m_strVal; break; } return left; } Expression* Driver::calcSUB(Expression* left,Expression* right){ char buff[MAX_NUM_BUFF]; switch(left->m_valType){ case valType::intType: switch(right->m_valType){ case valType::intType: left->m_value.m_intVal -= right->m_value.m_intVal; break; case valType::doubleType: left->m_value.m_intVal -= (int)right->m_value.m_doubleVal; break; case valType::boolType: //何もしない break; case valType::stringType: left->m_value.m_intVal -= atoi(right->m_value.m_strVal.c_str()); break; } break; case valType::doubleType: switch(right->m_valType){ case valType::intType: left->m_value.m_doubleVal -= (double)right->m_value.m_intVal; break; case valType::doubleType: left->m_value.m_doubleVal -= right->m_value.m_doubleVal; break; case valType::boolType: //何もしない break; case valType::stringType: left->m_value.m_doubleVal -= atof(right->m_value.m_strVal.c_str()); break; } break; case valType::boolType: //何もしない break; case valType::stringType: //何もしない break; } return left; } Expression* Driver::calcMUL(Expression* left,const Expression* right){ char buff[MAX_NUM_BUFF]; switch(left->m_valType){ case valType::intType: switch(right->m_valType){ case valType::intType: left->m_value.m_intVal *= right->m_value.m_intVal; break; case valType::doubleType: left->m_value.m_intVal *= (int)right->m_value.m_doubleVal; break; case valType::boolType: //何もしない break; case valType::stringType: left->m_value.m_intVal *= atoi(right->m_value.m_strVal.c_str()); break; } break; case valType::doubleType: switch(right->m_valType){ case valType::intType: left->m_value.m_doubleVal *= (double)right->m_value.m_intVal; break; case valType::doubleType: left->m_value.m_doubleVal *= right->m_value.m_doubleVal; break; case valType::boolType: //何もしない break; case valType::stringType: left->m_value.m_doubleVal *= atof(right->m_value.m_strVal.c_str()); break; } break; case valType::boolType: //何もしない break; case valType::stringType: //何もしない break; } return left; } Expression* Driver::calcDIV(Expression* left,Expression* right){ char buff[MAX_NUM_BUFF]; switch(left->m_valType){ case valType::intType: switch(right->m_valType){ case valType::intType: left->m_value.m_intVal /= right->m_value.m_intVal; break; case valType::doubleType: left->m_value.m_intVal /= (int)right->m_value.m_doubleVal; break; case valType::boolType: //何もしない break; case valType::stringType: left->m_value.m_intVal /= atoi(right->m_value.m_strVal.c_str()); break; } break; case valType::doubleType: switch(right->m_valType){ case valType::intType: left->m_value.m_doubleVal /= (double)right->m_value.m_intVal; break; case valType::doubleType: left->m_value.m_doubleVal /= right->m_value.m_doubleVal; break; case valType::boolType: //何もしない break; case valType::stringType: left->m_value.m_doubleVal /= atof(right->m_value.m_strVal.c_str()); break; } break; case valType::boolType: //何もしない break; case valType::stringType: //何もしない break; } return left; } Expression* Driver::createVARIABLE(const string& str){ Expression* temp = new Expression(); //intに初期化 temp->m_valType = valType::intType; temp->m_value.m_intVal = 0; temp->m_variable = str; m_pObjPool.push_back(temp); return temp; } Driver* Driver::get(){ static Driver* instance = nullptr; if(!instance){ instance = new Driver(); } return instance; } int Driver::parse (const string &f) { extern int yyparse(void); extern FILE* yyin; m_file = f; if ((yyin = fopen(m_file.c_str(), "r")) == NULL) { cerr << "ファイル読み込みに失敗しました" << endl; return 1; } if (yyparse()) { cout << "プログラム終了" << endl; return 1; } return 0; }
数値リテラル
まず、数値リテラルの処理です
Expression* Driver::createDOUBLE(const string& str){
double d = (double)strtof(str.c_str(),nullptr);
Expression* temp = new Expression();
temp->m_valType = valType::doubleType;
temp->m_value.m_doubleVal = d;
m_pObjPool.push_back(temp);
return temp;
}
前項ではdriver::createNUM()関数として実装されていました。前項では式ではなくdouble型の値をやり取りしていました。この項ではExpression*をやり取りするので書き直さなければなりません。また今回はintとdoubleを分けたいと考えたので、関数名も変わっています。
double d = (double)strtof(str.c_str(),nullptr);で、数字文字列からdouble型の値を作成します。その後Expression* temp;をnewで作成し、そのメンバを初期化します。
赤くなっているところは、作成したtempをメモリ上に保持する操作です。temp変数はローカル変数なので、この関数を抜けると消滅してしまいます。ここで作成したExpression*のインスタンスは、今後も使用するので、どこかにとっておかなければなりません。
Driverクラスには
vector<ObjBase*> m_pObjPool;というObjBase*型の配列があります。この配列にとっておくわけです。
int型のリテラルの処理も同様です。Driver::createINT()関数で処理しています。
代入先のExpression*の作成
続くDriver::copyExp()関数は代入先のExpression*の作成です。例えば、parser.yに
primary_expression : identifier_expressionという記述があります。これは
primary_expression : identifier_expression { $$ = $1; }の略であり、この$$側は初めて出てくるので、Expression*のインスタンスを作成しなければなりません。前項のようにdouble型といったC/C++の組み込み型変数ならいいのですがExpression*はユーザー定義型なので、その作成方法を記述しなければならないのです。その処理は以下です。
Expression* Driver::copyExp(Expression* other){ Expression* temp = new Expression(); temp->m_valType = other->m_valType; temp->m_variable = other->m_variable; switch(temp->m_valType){ case valType::intType: temp->m_value.m_intVal = other->m_value.m_intVal; break; case valType::doubleType: temp->m_value.m_doubleVal = other->m_value.m_doubleVal; break; case valType::boolType: temp->m_value.m_boolVal = other->m_value.m_boolVal; break; case valType::stringType: temp->m_value.m_strVal = other->m_value.m_strVal; break; } m_pObjPool.push_back(temp); return temp; }新しいExpression*型の変数tempをnewで作成してm_pObjPool配列に追加し、それを返します。
代入式の作成
続くExpression* Driver::assExp(Expression* dest,Expression* src){ dest->m_valType = src->m_valType; switch(src->m_valType){ case valType::intType: dest->m_value.m_intVal = src->m_value.m_intVal; break; case valType::doubleType: dest->m_value.m_doubleVal = src->m_value.m_doubleVal; break; case valType::boolType: dest->m_value.m_boolVal = src->m_value.m_boolVal; break; case valType::stringType: dest->m_value.m_strVal = src->m_value.m_strVal; break; } return dest; }は代入式です。C言語のように=で代入できるようにしました。引数のsrcからdestにコピーされます。destは変数式が入ることを想定しています。値が代入されたdestはそのままreturnされます。
四則演算
続く#define MAX_NUM_BUFF 100 Expression* Driver::calcADD(Expression* left,Expression* right){ char buff[MAX_NUM_BUFF]; switch(left->m_valType){ case valType::intType: switch(right->m_valType){ case valType::intType: left->m_value.m_intVal += right->m_value.m_intVal; break; case valType::doubleType: left->m_value.m_intVal += (int)right->m_value.m_doubleVal; break; case valType::boolType: //何もしない break; case valType::stringType: left->m_value.m_intVal += atoi(right->m_value.m_strVal.c_str()); break; } break; case valType::doubleType: switch(right->m_valType){ case valType::intType: left->m_value.m_doubleVal += (double)right->m_value.m_intVal; break; case valType::doubleType: left->m_value.m_doubleVal += right->m_value.m_doubleVal; break; case valType::boolType: //何もしない break; case valType::stringType: left->m_value.m_doubleVal += atof(right->m_value.m_strVal.c_str()); break; } break; case valType::boolType: //何もしない break; case valType::stringType: switch(right->m_valType){ case valType::intType: snprintf(buff,MAX_NUM_BUFF,"%d",right->m_value.m_intVal); left->m_value.m_strVal += buff; break; case valType::doubleType: snprintf(buff,MAX_NUM_BUFF,"%f",right->m_value.m_doubleVal); left->m_value.m_strVal += buff; break; case valType::boolType: //何もしない break; case valType::stringType: left->m_value.m_strVal += right->m_value.m_strVal; break; } left->m_value.m_strVal += right->m_value.m_strVal; break; } return left; }は足し算です。左辺の形に合わせるように型変換を行います。これについては、大きいほうに合わせるのほうがいいかもしれませんね。つまりintとdoubleの計算であれば、doubleにする形です。後ほど検討しましょう。
文字列型の場合は、後ろに追加します。演算の結果は左辺にまとめられ、その左辺を返します。
この後
Expression* Driver::calcSUB(Expression* left,Expression* right){ //中略 } Expression* Driver::calcMUL(Expression* left,const Expression* right){ //中略 } Expression* Driver::calcDIV(Expression* left,Expression* right){ //中略 }と残りの四則演算が続きます。文字列の追加は+でできますが、ほかの演算では何もしない形になります。
変数の追加
続くDriver::createVARIABLE()関数が、この項のテーマになっている変数の追加です。Expression* Driver::createVARIABLE(const string& str){ Expression* temp = new Expression(); //intに初期化 temp->m_valType = valType::intType; temp->m_value.m_intVal = 0; temp->m_variable = str; m_pObjPool.push_back(temp); return temp; }ここでも新しくExpression* temp;と作成し、temp->m_variableに変数名をセットし、m_pObjPoolに保存します。
parser.y
続いてBisonのファイルであるparser.yです。ここでの注目点はprimary_expressionの処理とidentifier_expressionの処理です。
%{ #include <stdio.h> #include <stdlib.h> #include <iostream> #include "driver.h" #define YYDEBUG 1 extern int yylex(void); int yyerror(char const *str) { extern char *yytext; fprintf(stderr, "parser error near %s\n", yytext); return 0; } %} %code requires { #include "driver.h" } %union { const char* identString; Expression* exp_value; const char* literal_value; } %token <identString> IDENTIFIER %token <literal_value> DOUBLE_LITERAL INT_LITERAL %token ADD "+" SUB "-" MUL "*" DIV "/" SEMICOLON ";" ASS "=" ; %type <exp_value> constart_expression identifier_expression primary_expression mul_expression add_expression %type <exp_value> expression %right ASS %left ADD SUB %left MUL DIV %% statement_list : statement | statement_list statement ; statement : expression ";" { Driver::get()->output($1); } expression : add_expression | identifier_expression ASS expression { $$ = Driver::get()->assExp($1,$3); } ; add_expression : mul_expression | add_expression "+" mul_expression { $$ = Driver::get()->calcADD($1,$3); } | add_expression "-" mul_expression { $$ = Driver::get()->calcSUB($1,$3); } ; mul_expression : primary_expression | mul_expression "*" primary_expression { $$ = Driver::get()->calcMUL($1,$3); } | mul_expression "/" primary_expression { $$ = Driver::get()->calcDIV($1,$3); } ; primary_expression : identifier_expression { $$ = Driver::get()->copyExp($1); } | constart_expression { $$ = Driver::get()->copyExp($1); } ; identifier_expression : IDENTIFIER { $$ = Driver::get()->createVARIABLE($1); } constart_expression : DOUBLE_LITERAL { $$ = Driver::get()->createDOUBLE($1); } | INT_LITERAL { $$ = Driver::get()->createINT($1); } ; %%
代入先のExpression*の作成
primary_expressionのアクションは以下のようになります。アクションとは非終端記号(ここではprimary_expression)を作り出すための終端記号(ここではidentifier_expressionやconstart_expression)における動作の定期です。{と}に挟んで書きます。primary_expression : identifier_expression { $$ = Driver::get()->copyExp($1); } | constart_expression { $$ = Driver::get()->copyExp($1); } ;となります。Driver::get()->copyExp()関数はDriverクラスの関数です。driver.cppで紹介した関数で、新しいexpのインスタンスを作って、引数の内容をコピーし、そのインスタンスを返します。
ここで作成しておくと、上位の処理である
mul_expression : primary_expressionの処理の時に、デフォルトの動作である
mul_expression : primary_expression { $$ = $1; }が、安全に実行されることになります。
変数の追加
変数はidentifier_expression : IDENTIFIER { $$ = Driver::get()->createVARIABLE($1); }で追加します。Driver::createVARIABLE()関数を使います。
四則演算
四則演算もDriverクラスで定義した関数を呼び出します。前項ではdouble型をやり取りしたので、そのまま記述できましたが、今項からはExpression*を使いますので、それ専用の関数を呼び出します。文と出力
変数式は、その内容が勝手に出力されます。今項から文(statement)の概念を取り入れました。この言語はC言語のように ; (セミコロン)で文が終わります。通常文は命令であり出力はprint i;のような形で実装されますが、現時点ではこの言語は命令の言う概念を持っていません。ですので、
statement : expression ";" { Driver::get()->output($1); }のように、式の最後に ; (セミコロン)がついていれば、実行(出力)します。
ちなみにexpression ";"というのは式文といって、プログラム言語のほとんどを占める文の形態です。順次処理というのは式文が並んだものであり、それとは違うのが、今後出てくるif文やループ文ということになります。
scanner.l
続いてscanner.lです。%{ #include <stdio.h> #include "parser.hpp" #include "driver.h" #define YY_SKIP_YYWRAP 1 int gLine = 1; int yywrap() { return 1; } %} %s COMMENT %s LINE_COMMENT %s STR_LITERAL_ST_D %s STR_LITERAL_ST_S blank [ \t\r] int [1-9][0-9]* double [0-9]*\.[0-9]* ident [A-Za-z_][A-Za-z_0-9]* %% <INITIAL>{ {blank}+ ; {int} { yylval.literal_value = yytext; return INT_LITERAL; } {double} { yylval.literal_value = yytext; return DOUBLE_LITERAL; } ";" return SEMICOLON; "+" return ADD; "-" return SUB; "*" return MUL; "/" return DIV; "=" return ASS; {ident} { yylval.identString = yytext; return IDENTIFIER; } "/*" { BEGIN(COMMENT);} [/][/]+ { BEGIN(LINE_COMMENT);} "\"" { BEGIN(STR_LITERAL_ST_D); } "\'" { BEGIN(STR_LITERAL_ST_S); } "\n" { gLine++; } . { return 0; } } <COMMENT>{ "\n" { gLine++;} "*/" { BEGIN(INITIAL); } . ; } <LINE_COMMENT>{ "\n" { gLine++; BEGIN(INITIAL); } . ; } <STR_LITERAL_ST_D>{ "\n" { gLine++; } "\"" { BEGIN(INITIAL);} . { ; } } <STR_LITERAL_ST_S>{ "\n" { gLine++; } "\'" { BEGIN(INITIAL);} . { ; } } %%ここでは変数(識別子)とコメントと文字列を追加しています。
識別子
変数(識別子)は{ident} { yylval.identString = yytext; return IDENTIFIER; }で表現されます。{ident}は上方で記述される正規表現
ident [A-Za-z_][A-Za-z_0-9]*です。 { } で囲むことで、上方で定義した正規表現を指定できます。
コメント
Flexは、最初は<INITIAL>という状態を持ちます。状態は別に定義することができ%s COMMENT %s LINE_COMMENT %s STR_LITERAL_ST_D %s STR_LITERAL_ST_Sのように定義します。状態の変更は
"/*" { BEGIN(COMMENT);}のように記述します。この言語は/*ではじまり*/で終わる複数行コメントと//以降その行は無視する1行コメントがあります。
これを状態の変更で行います。
"/*"があれば複数行コメントの始まりです。{ BEGIN(COMMENT);}と状態変更します。COMMENT状態では
<COMMENT>{ "\n" { gLine++;} "*/" { BEGIN(INITIAL); } . ; }のように"*/"が出てきたらBEGIN(INITIAL);とINITIAL状態に戻します。
1行コメントはINITIAL状態の時に
[/][/]+ { BEGIN(LINE_COMMENT);}があればLINE_COMMENT状態に推移します。その中では
<LINE_COMMENT>{ "\n" { gLine++; BEGIN(INITIAL); } . ; }のように改行があればINITIAL状態に戻します。
文字列
また、まだ実装はしてませんが文字列についてもFlexファイルでは定義されています。この言語はPHPのように複数行文字列を許可したいとおもいますので状態を使って、文字列を管理します。<STR_LITERAL_ST_D>{ "\n" { gLine++; } "\"" { BEGIN(INITIAL);} . { ; } }はダブルクオーテーションの文字列状態です。
同様に
<STR_LITERAL_ST_S>{ "\n" { gLine++; } "\'" { BEGIN(INITIAL);} . { ; } }はシングルクオーテーションの文字列状態です。まだFlex上でしか記述はないので、現時点では文字列が出てきても何もしません。
main.cpp
以下、main.cppです。前項と変わりません#include <stdio.h> #include <stdlib.h> #include <iostream> #include <fstream> #include <vector> #include <string> #include "driver.h" using namespace std; int main (int argc, char *argv[]) { Driver* drv = Driver::get(); for (int i = 1; i < argc; ++i){ return drv->parse(argv[i]); } return 0; }
Makefile
Makefileです。これも前項と変わりません。calcCpp : scanner.cpp parser.hpp parser.cpp main.cpp driver.cpp driver.h g++ -o calcCpp scanner.cpp parser.cpp driver.cpp main.cpp scanner.cpp : scanner.l flex -o scanner.cpp scanner.l parser.hpp parser.cpp : parser.y bison -d -o parser.cpp parser.y
test.txt
以下、スクリプトファイルですtest.txtです。少し実験的な内容になっています。10.5 + 3.5; 40.25+12.3; i = 15.55; 2+5; //1行コメント 4+50; /* ここは複数行コメント */以下、実行コマンドです。
./calcCpp test.txt以下、実行結果です。
>>14 >>52.55 >>15.55 >>7 >>54