本稿は、メトリクスのグラフ構造を表現するためのデータ記述言語「SynapseScript」に関するマニュアルである。
仕様や具体例の提示によって、SynapseScript利用時の補助を行うことが目的である。
図1 メトリクスグラフの例
|
対象を何かしらの指標に基づいて計測する「メトリクス」は、図1のようなグラフ構造で表現することができる。
グラフのノードは、多入力多出力の関数に相当する「メトリクスプラグイン」として実現し、グラフのアークは、ノード同士の繋ぎ役として「SynapseScript」を提案するのである。
ちなみに「SynapseScript」という名称は、メトリクスグラフが神経細胞の接続構造(ニューラルネットワーク)に似ていたことを由来としたものであり、
その構造の要となる「接続」の仕組み・構造を意味する「シナプス」という言葉を採用したのだ。
ここでは、本ガイドラインに記載されている用語の定義を行う。
-
SynapseScript
関数の役割を果たす「メトリクスプラグイン」を接続するための仕組み。データ記述言語。
-
入力(input)/ 出力(output)
変換系(メトリクスプラグイン)がデータAをデータBに変換する時、変換前のデータ(つまりA)を入力と呼び、変換後のデータ(つまりB)を出力と呼ぶ。
また、この変換のため変換系に対してデータAの提供を行うが、この行為自体も入力と呼び、変換系がデータBを作り出す行為も出力と呼ぶ。
-
プログラム
ソフトウェアプログラム。コンピュータを操作するための言語表現。ソースコードとも。
-
メトリクス
プログラムなどの評価対象を、何らかの尺度・指標(ものさし)で計測することを言う。
-
メトリクスプラグイン
具体的なメトリクスの処理(写像処理)を行う機能単位。何らかの入力値に対応する出力値を応答する変換系(翻訳者)である。
例えば、プログラムを入力して、そのプログラムの行数を計測して出力するなどを実現するもの。
概要 ─ 具体例を添えて
具体的なメトリクスグラフの例を挙げ、その構造を表すSynapseScriptの記述(スクリプト)を紹介しよう。
図2 サンプルのメトリクスグラフ
|
/*** Assignments ***/
sourceFile = File("/path/to/Programs/program.py", "read");
language = Value("Python");
codeMetrics = Metrics("/path/to/Plugins/CodeMetrics");
division = Metrics("/path/to/Plugins/Division");
resultFile = File("/path/to/Results/ResultFile.txt", "write");
transcript = Transcript();
/*** Synapses ***/
sourceFile
to: codeMetrics.code
send: value1 to: division.dividend
send: quotient to: resultFile;
language
to: codeMetrics.lang
send: value2 to: division.divisor
send: remainder to: transcript;
スクリプトは「代入文」と「シナプス文」の2種類で構成される。それぞれを以下に詳説する。
代入文
/*** Assignments ***/
sourceFile = File("/path/to/Programs/program.py", "read");
language = Value("Python");
codeMetrics = Metrics("/path/to/Plugins/CodeMetrics");
division = Metrics("/path/to/Plugins/Division");
resultFile = File("/path/to/Results/ResultFile.txt", "write");
transcript = Transcript();
代入文は、グラフのノードたちを定義するものである。等号「=」を挟み、右辺はノードのコンストラクタ、左辺は対応付ける変数を記述する。
ここで命名した変数名は、後述のシナプス文でグラフ構造を表現する際に用いることになる。
単一の代入文は、次のように記述される。また、利用できるコンストラクタ名の例も示そう。
変数名 = コンストラクタ名 ( 引数群 );
コンストラクタ名 | 役割 |
Value | データのノードを生成 |
File | ファイルのノードを生成 |
Metrics | メトリクスプラグインのノードを生成 |
Transcript | トランスクリプトのノードを生成 |
-
コンストラクタ「Value」
データ(値)のノードを生成する。具体的なデータを引数に持たせる。生成されたノードは出力のみ行う。
以下に示すのは、「あいうえお」という文字列をデータとするノードを生成し、変数「stringValue」に束縛する場合の例である。
stringValue = Value("あいうえお");
引数として渡すデータは、記述できるものであれば数値でも文字列でも何でも良い。ただし、二重引用符「"」で囲む必要がある。
もし、データそのものに二重引用符が含まれる場合(「あい"う"えお」など)は、二重引用符を連続して記述しなければならない。つまり、次のような記述になる。
stringValue = Value("あい""う""えお");
-
コンストラクタ「File」
ファイルのノードを生成する。取り扱うファイルのパスを引数に持たせる。また、第二引数として「read」「write」「append」を指定する。
「read」の指定とともに生成されたノードは出力(データの読み出し)のみを行い、「write」「append」の指定とともに生成されたノードは入力(データの書き込み・追記)のみを行う。
以下に示すのは、「/path/to/hoge.txt」というファイルからデータを読み出すノードを生成する例である。
hogeFile = File("/path/to/hoge.txt", "read");
-
コンストラクタ「Metrics」
メトリクスプラグインのノードを生成する。対象となるメトリクスプラグインのパスを引数に持たせる。生成されたノードは入力及び出力を行う。
以下に示すのは、「/path/to/Plugins/Foobar」というプラグインに対応するノードを生成する例である。
foobar = Metrics("/path/to/Plugins/Foobar");
-
コンストラクタ「Transcript」
トランスクリプト(文字列を表示する端末)のノードを生成する。生成されたノードは出力のみ行う。生成例を以下に示す。
transcript = Transcript();
以上、4種類のコンストラクタを示したが、処理系によっては「Transcript」を用意できない場合もあろう。つまり、これらは処理系に依存することになる。
また、この4種類以外のコンストラクタを用意することもあり得る。
シナプス文
/*** Synapses ***/
sourceFile
to: codeMetrics.code
send: value1 to: division.dividend
send: quotient to: resultFile;
language
to: codeMetrics.lang
send: value2 to: division.divisor
send: remainder to: transcript;
シナプス文は、代入文で定義したノードたちの繋ぎ方を明記するものである。
「あるノードの出力」と「あるノードの入力」を繋げるために、to:文(またはsend:to:文)を記述することになる。
-
ノード同士の接続
あるノード「nodeA」の出力を、あるノード「nodeB」の入力に繋げる場合、to:文を用いて次のように記述する。
nodeA to: nodeB;
ちなみに、これはラベル名の記述を(特別に)省略しているに過ぎない。デフォルトの出力ラベル名「output」入力ラベル名「input」を用いて、次のように記述することと相等である。
nodeA.output to: nodeB.input;
では、ラベル名が別に定められている場合について。あるノード「nodeA」の出力(ラベル「output1」)を、あるノード「nodeB」の入力(ラベル「input1」)に繋げる場合、次のように記述する。
nodeA.output1 to: nodeB.input1;
これは、send:to:文を用いて次のように記述することもできる。意味は全く同じである。
nodeA send: output1 to: nodeB.input1;
-
連続した接続
あるノード「nodeA」の出力を、あるノード「nodeB」の入力とし、また「nodeB」の出力を、あるノード「nodeC」の入力とする場合、次のように記述する。
nodeA to: nodeB;
nodeB to: nodeC;
この連続した接続を、以下のように簡便な表現に直すこともできる。
nodeA to: nodeB to: nodeC;
ラベルが指定されている場合も同様に、連続した接続を表現することができる。ちなみに、インデントの形式は自由である。
nodeA
send: output1 to: nodeB.inputA
send: outputA to: nodeC.input1;
字句解析向けの正規表現
字句解析器生成系の一「flex」向けに拵えた正規表現を掲載する。PDF形式の正規表現(rexp.lex.pdf)も用意している。
%{
#include "defs.h"
%}
%option nounput
%%
"send:" { return(SEND); }
"to:" { return(TO); }
[_a-zA-Z][_a-zA-Z0-9]* { return(ID); }
"=" { return(GETS); }
";" { return(SEMICOLON); }
"\." { return(PERIOD); }
"(" { return(LPAR); }
")" { return(RPAR); }
"," { return(COMMA); }
\"([^\"]|\"\")*\" { return(STRING); }
"\r\n"|"\r"|"\n"|" "|"\t" { }
"/*"([^*]|"*"+[^*/])*"*"+"/" { return(COMMENT); }
. { return(UNKNOWN); }
%%
int yywrap(void) {
return(1);
}
構文解析向けのBNF
構文解析器生成系の一「Yacc」向けに拵えたBNF(バッカス・ナウア記法)を掲載する。
PDF形式のBNF(BNF.pdf)と抽象構文木の生成を行う意味解析付きのBNF(syns.yac.pdf)も用意している。
%{
#include "defs.h"
#define YYSTYPE char *
char s[1024];
%}
%token SEND TO GETS SEMICOLON PERIOD LPAR RPAR COMMA ID STRING COMMENT UNKNOWN
%%
Program
: Statements { }
Statements
: { }
| Statement { }
| Statements Statement { }
Statement
: Assignment SEMICOLON { }
| Synapse SEMICOLON { }
| Comment { }
Assignment
: VariableName GETS Constructor { }
Synapse
: Variable Mappings { }
Mappings
: Mapping { }
| Mappings Mapping { }
Mapping
: TO Variable { }
| SEND MemberName TO Variable { }
Variable
: VariableName { }
| VariableName PERIOD MemberName { }
VariableName
: ID { }
MemberName
: ID { }
Constructor
: ConstructorName LPAR RPAR { }
| ConstructorName LPAR Arguments RPAR { }
ConstructorName
: ID { }
Arguments
: Argument { }
| Arguments COMMA Argument { }
Argument
: STRING { }
Comment
: COMMENT { }
%%
#include "lex.yy.c"
void yyerror(char *s) {
fprintf(stderr, "\n%s at %d: nearby \"%s\"\n\n", s, linecounter, yytext);
exit(EXIT_FAILURE);
}