13-06.構文解析(Bisonファイルの記述)
CLangProject.y
Flexの定義ファイルは
CLangProject.lですが、
Bisonの定義ファイルは
CLangProject.yです。
まず全体像を紹介します。
%{
#include "../CLangProject/proc.h"
#define YYDEBUG 1
extern int yylex(void);
extern char *yytext;
int yyerror(char const *str){
extern int gLine;
fprintf(stderr,"%s, line: %d, near %s\n",str, gLine, yytext);
return 0;
}
%}
%code requires {
#include "../CLangProject/proc.h"
}
%union {
clg::Expression* pExpression;
clg::Statement* pStatement;
clg::StatementList* pStatementList;
clg::Declaration* pDeclaration;
clg::DeclarationList* pDeclarationList;
clg::ParameterList* pParameterList;
clg::Root* pRoot;
}
%token IDENTIFIER INT_LITERAL DMP INT_TYPE SEMICOLON
%token LC RC LP RP
%type <pExpression> expression intliteral_expression identifier_expression postfix_expression
%type <pStatementList> statement_list
%type <pStatement> dump_statement expression_statement compound_statement statement
%type <pDeclaration> declaration
%type <pDeclarationList> declaration_list
%type <pRoot> root
%type <pParameterList> parameter_list
%%
root
: declaration_list
{
$$ = clg::StackMachine::get()->addRootDeclarationList($1);
}
;
declaration_list
: declaration
{
$$ = clg::StackMachine::get()->createDeclarationList($1);
}
| declaration_list declaration
{
$$ = clg::StackMachine::get()->createDeclarationList($1,$2);
}
;
declaration
: INT_TYPE identifier_expression LP parameter_list RP compound_statement
{
$$ = clg::StackMachine::get()->createIntFunctionDeclaration($2,$6);
}
;
parameter_list
: /*empty*/
{
$$ = clg::StackMachine::get()->createParameterList();
}
;
statement_list
: statement
{
$$ = clg::StackMachine::get()->createStatementList($1);
}
| statement_list statement
{
$$ = clg::StackMachine::get()->createStatementList($1,$2);
}
;
statement
: dump_statement
| compound_statement
| expression_statement
;
expression_statement
: expression SEMICOLON
{
$$ = clg::StackMachine::get()->createExpressionStm($1);
}
;
dump_statement
:DMP expression SEMICOLON
{
$$ = clg::StackMachine::get()->createDumpStm($2);
}
;
compound_statement
: LC RC
{
$$ = clg::StackMachine::get()->createCompoundStatement();
}
| LC statement_list RC
{
$$ = clg::StackMachine::get()->createCompoundStatement($2);
}
;
expression
:intliteral_expression
|identifier_expression
|postfix_expression
;
postfix_expression
: identifier_expression LP RP
{
$$ = clg::StackMachine::get()->createFunctionCallExp($1);
}
;
identifier_expression
: IDENTIFIER
{
$$ = clg::StackMachine::get()->createIdentifierExp(yytext);
}
;
intliteral_expression
: INT_LITERAL
{
$$ = clg::StackMachine::get()->createIntLiteralExp(yytext);
}
;
%%
CLangProjectとのかかわり
13-01.構造の修正で実装した内容は、
四則演算を
乗除算優先で実行できる簡易的なものでしたが、実際にプログラム言語を作成するには、
順次処理のほかに
分岐、ループ、関数呼び出しなどの機能が必要になります。
それらを実装するには、単純にスクリプトを順次トレースして実行するのではなく、いったん、何らかの形でコード全体を保持しておき、文法に従って実行する、という手順を踏む必要があります。
とくに
C言語では、あらかじめ
いくつもの関数が定義され、実行は
main関数を探して、そこから実行という文法になります。
コード全体を保持する方法として、いくつか手法がありますが、このドキュメントでは
スタックマシーンにするというのが最終目標です。
スタックマシーンは、プログラムのソースコードを、
バイナリコードに変換して保存します。ここで
バイナリコードというのは、CPUを直接操作できる、いわゆる
マシン語ではありません。
擬似マシン語といわれる、
マシン語を模した、単純なニーモニック言語です。その形にしておくのが一番なのですが、その一歩手前の手順として、せっかく
VisualStudioを使っているので
C++のオブジェクトとして、保持しておき、実行はそのオブジェクトに任せる、という手法で実装したいと思います。
こうすることで、細かなオブジェクトを作成することができ、オブジェクト指向で作成することができます。
そしてある程度形になった段階で、
スタックマシーンとして、再実装していきたいと思います。
ということで、
Bisonファイルでは、各文法ごとに
CLangProjectに含まれるオブジェクトを作成するという処理を行います。
ここでは、まずその構造を説明します。
StackMachineクラス
CLangProjectプロジェクトには
stackMachine.h/cppファイルに
StackMachineクラスが定義されています。名前こそ
スタックマシンですが、まだスタックマシンではありません。今のところ
CLangProject.yとのインターフェイスが実装されています。
StackMachineクラスには
Createなんとかというメンバ関数が多数存在します。それぞれ、
CLangProject.yから呼び出されることを想定した関数群です。
これらの関数では、オブジェクト化された
式や
文や
文リストなどを構築します。
関数も構築します。
それらは、例えば
整数リテラル式であれば
IntLiteralExpクラスのインスタンスを作成し、オブジェクトプールに保持する、という役割を担います。
ですので、
CLangProject.yは、解析した構文によって、
式クラスや文クラスなどを構築するメンバ関数を呼び出します。
そこまで説明したところで、
CLangProject.yを詳しく見ていきましょう。
まず
%{
#include "../CLangProject/proc.h"
#define YYDEBUG 1
extern int yylex(void);
extern char *yytext;
int yyerror(char const *str){
extern int gLine;
fprintf(stderr,"%s, line: %d, near %s\n",str, gLine, yytext);
return 0;
}
%}
の部分は、ほぼ定番の内容です。
#include "../CLangProject/proc.h"
は
CLangProjectプロジェクトの
proc.hをインクルードしていて、これは
#pragma once
#undef INT8_MIN
#undef INT16_MIN
#undef INT32_MIN
#undef INT8_MAX
#undef INT16_MAX
#undef INT32_MAX
#undef UINT8_MAX
#undef UINT16_MAX
#undef UINT32_MAX
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <fstream>
#include <string>
#include <set>
#include <map>
#include <vector>
#include <list>
#include <memory>
using namespace std;
#include "common.h"
#include "value.h"
#include "expression.h"
#include "statement.h"
#include "declaration.h"
#include "stackMachine.h"
といった内容になっています。すなわち
CLangProjectプロジェクトに含まれる多くのヘッダファイルをインクルードしています。これにより、
CLangProjectプロジェクトで宣言される
クラスや構造体にアクセスできます。
続く
%code requires {
#include "../CLangProject/proc.h"
}
でもどうようのインクルードをしています。
Bisonの定義ではこのように2か所必要なようです。
続く
%union {
clg::Expression* pExpression;
clg::Statement* pStatement;
clg::StatementList* pStatementList;
clg::Declaration* pDeclaration;
clg::DeclarationList* pDeclarationList;
clg::ParameterList* pParameterList;
clg::Root* pRoot;
}
は
Bisonの定義で使用する、
型と
その型の変数名について登録します。例えば
clg::Expression* pExpression;
というのは
clg::Expression*型の
pExpression変数、という意味です。これらの設定は、後ほど
%typeの設定で重要になります。
続く
%token IDENTIFIER INT_LITERAL DMP INT_TYPE SEMICOLON
%token LC RC LP RP
%type <pExpression> expression intliteral_expression identifier_expression postfix_expression
%type <pStatementList> statement_list
%type <pStatement> dump_statement expression_statement compound_statement statement
%type <pDeclaration> declaration
%type <pDeclarationList> declaration_list
%type <pRoot> root
%type <pParameterList> parameter_list
で
%tokenおよび
%typeの設定をします。
%token構文は
%token IDENTIFIER INT_LITERAL DMP INT_TYPE SEMICOLON
%token LC RC LP RP
のように
Flexと共有するトークンの名前を登録します。
すなわち前項で述べた
IDENTIFIERとか
INT_LITERALとかです。
Flexでも使用するトークンの名前をここで登録します。複数行にわたってもかまいません。
%type構文は
という使い方をします。例えば
%type <pExpression> expression intliteral_expression identifier_expression postfix_expression
というのは
pExpression は expression intliteral_expression identifier_expression postfix_expression
で使用する変数名である、
ということです。
BNF法による表記
Bisonは
バッカス・ナウア記法(英: Backus-Naur form)という方法を使って、演算の優先順位などを設定します。
具体的には、以下の部分です。
%%
root
: declaration_list
{
$$ = clg::StackMachine::get()->addRootDeclarationList($1);
}
;
declaration_list
: declaration
{
$$ = clg::StackMachine::get()->createDeclarationList($1);
}
| declaration_list declaration
{
$$ = clg::StackMachine::get()->createDeclarationList($1,$2);
}
;
declaration
: INT_TYPE identifier_expression LP parameter_list RP compound_statement
{
$$ = clg::StackMachine::get()->createIntFunctionDeclaration($2,$6);
}
;
parameter_list
: /*empty*/
{
$$ = clg::StackMachine::get()->createParameterList();
}
;
statement_list
: statement
{
$$ = clg::StackMachine::get()->createStatementList($1);
}
| statement_list statement
{
$$ = clg::StackMachine::get()->createStatementList($1,$2);
}
;
statement
: dump_statement
| compound_statement
| expression_statement
;
expression_statement
: expression SEMICOLON
{
$$ = clg::StackMachine::get()->createExpressionStm($1);
}
;
dump_statement
:DMP expression SEMICOLON
{
$$ = clg::StackMachine::get()->createDumpStm($2);
}
;
compound_statement
: LC RC
{
$$ = clg::StackMachine::get()->createCompoundStatement();
}
| LC statement_list RC
{
$$ = clg::StackMachine::get()->createCompoundStatement($2);
}
;
expression
:intliteral_expression
|identifier_expression
|postfix_expression
;
postfix_expression
: identifier_expression LP RP
{
$$ = clg::StackMachine::get()->createFunctionCallExp($1);
}
;
identifier_expression
: IDENTIFIER
{
$$ = clg::StackMachine::get()->createIdentifierExp(yytext);
}
;
intliteral_expression
: INT_LITERAL
{
$$ = clg::StackMachine::get()->createIntLiteralExp(yytext);
}
;
%%
Bisonは、下方ほど優先順位が高くなります。上記の記述では
intliteral_expression
: INT_LITERAL
{
$$ = clg::StackMachine::get()->createIntLiteralExp(yytext);
}
;
が最上位です。
intliteral_expression
は INT_LITERAL が1つだけあるトークンです。
clg::StackMachine::get()->createIntLiteralExp()関数に
yytext(トークンの文字列)を渡して実行しなさい。
という意味になります。
では
clg::StackMachine::get()->createIntLiteralExp()関数とは、どのような関数でしょうか?
Expression* StackMachine::createIntLiteralExp(const char* ptr) {
string str = clampToken(ptr);
auto pObj = new IntLiteralExp(std::stoi(str));
m_objPool.push_back(pObj);
return pObj;
}
といった関数です。
yytextは
const char* ptrで受けられます。ここでは
IntLiteralExpクラスを構築し、m_objPoolという領域に保存しておきます。
そして、そのポインタを返します。
IntLiteralExpクラスは
Expressionクラスの派生クラスですので、
Expression*型の戻り値として使用できるのです。
Bison側に戻ります
intliteral_expression
: INT_LITERAL
{
$$ = clg::StackMachine::get()->createIntLiteralExp(yytext);
}
;
では
$$に戻り値を代入してます。この
$$こそが
%type <pExpression> expression intliteral_expression identifier_expression postfix_expression
で指定した
pExpression変数なわけです。ここで赤くなっている部分のように
intliteral_expressionを指定しています。ですので、
intliteral_expressionと、
ここでの$$は等価であり、それは
pExpression変数といいことになります。
このように、
clg::StackMachine::get()->createIntLiteralExp(yytext);の実行により、
intliteral_expressionは
pExpression変数になりました。
そうすると
expression
:intliteral_expression
|identifier_expression
|postfix_expression
;
のように
expressionとまとめられるようになり、それは
dump_statement
:DMP expression SEMICOLON
{
$$ = clg::StackMachine::get()->createDumpStm($2);
}
;
と
dump_statementの定義に再利用できるようになります。
ここで
$$ = clg::StackMachine::get()->createDumpStm($2);
の
$2は
2番目のトークンという意味で、
DMP 1番目
expression 2番目
SEMICOLON 3番目
と数えますので、
clg::StackMachine::get()->createDumpStm関数には
pExpression変数が渡されます。ですので
CLangProject側の
createDumpStm関数は以下のような宣言になります。
Statement* createDumpStm(Expression* exp);
このような形で
CLangProject.yでは、優先順位の高いブロックで取得した
文字列を、優先順位の低いブロックで再利用しながら、
CLangProject側の関数に渡すことで、オブジェクトとして構築していきます。
さて、この項では
Bisonファイルによる
CLangProject側の関数呼び出しの構造を説明しました。
必ずしもこの構造が、よいとも限りません。あくまで
ぼくの書き方がこの構造なので、例えばオブジェクト指向を使用しない設計方法もあると思います。
このドキュメントを参考にしながら
自分のC言語を考えてみるのもよいと思います。