axjack's blog

axjack is said to be an abbreviation for An eXistent JApanese Cool Klutz.

ERROR: syntax error, unexpected VAR, expecting $end を追う

きっかけ

次のコードを選択して実行するとエラーになる。

コード1

var aa;
aa = 1;
var bb;
bb = 1 + aa;

エラー

ERROR: syntax error, unexpected VAR, expecting $end
  in file 'selected text'
  line 3 char 3:

  var bb;
  ^^^
  bb = 1 + aa; 
-----------------------------------
ERROR: Command line parse failed
-> nil

"シンタックスエラーなり。予期せぬVARがある。そこは終端記号があるべき。場所は4行目云々。"と読める。

一方、こちらの場合は問題なく通る。

コード2

var aa;
var bb;
aa = 1;
bb = 1 + aa;

出力結果

-> 2

どうやら、var宣言した後に「式」を書いて、その後にまたvar宣言するとエラーになるらしい。斯くして、var宣言はまとめて書いた後に「式」を書けば良いという教訓を得た。

のは良いが、どうせならということでSuperColliderのソースを追ってどの辺りでエラーになったのかを確かめてみよう。

コードを追う

まずは検索

githubsupercolliderの中をsyntax error, unexpectedで検索する。するとlang/LangSource/Bison/lang11d_tab.cppあたりで引っかかっているのを発見。

このファイルはどのフォルダに入っているのかを見ると、

supercollider/lang/LangSource/Bison/
    lang11d
    lang11d_tab.cpp
    lang11d_tab.h
    make_parser.sh

となっている。make_parser.shというシェルスクリプトで何やら生成しているのだろう。また「make parser」、ということはパーサー(構文解析器)をメイク(作る)に違いない。このファイルを見ると、

#!/bin/sh
bison -o lang11d_tab.cpp lang11d

と記述してある。意味的には「bisonを実行、出力ファイルはlang11d_tab.cppで入力ファイルはlang11d」であろう。ということなので、肝となるのは入力ファイルのlang11dに違いない。

bisonとは?

bisonとはWikipediaによると、

Bison(バイソン)とは構文解析器を生成するパーサジェネレータの一種であり、CコンパイラとしてのGCCのサポートのために開発されたフリーソフトウェアである。

とのこと。このことから、lang11dは構文解析機を生成するための入力ファイルであることが分かる。つまりSuperColliderのsyntaxはここで定義されているのだろう、と認識してみることにする。

該当箇所を追う

以下、lang11dから該当しそうな箇所を引用しながらコードを追う。

VARを探す

class宣言の方にもVARが見つかったけれど、そちらではなく以下の定義が怪しい。怪しいというのは、普段使っているVARの定義っぽいということである。

funcvardecl  : VAR vardeflist ';'
                { $$ = (intptr_t)newPyrVarListNode((PyrVarDefNode*)$2, varLocal); }
;

これは、「funcvardeclとは、VARというtokenに、vardeflistと';'が続くものである」と読める。「VARに続く次の文字列はvardeflistである」とも読める。では、vardeflistは?というと、

vardeflist      : vardef
                        | vardeflist ',' vardef
                                { $$ = (intptr_t)linkNextNode((PyrParseNode*)$1, (PyrParseNode*)$3); }
;

「vardeflistとは、vardefもしくはvardeflistに','とvardefと表されるものである」と読める。再帰的な定義である。vardefにカンマを打ちvardefを更に書いてもvardeflistと認める、ということだろうか?

では、vardefとはなんぞや?と探してみると、

vardef          : name
                                { $$ = (intptr_t)newPyrVarDefNode((PyrSlotNode*)$1, NULL, 0); }
                        | name '=' expr
                                { $$ = (intptr_t)newPyrVarDefNode((PyrSlotNode*)$1, (PyrParseNode*)$3, 0); }
                        | name '(' exprseq ')'
                                {
                                    PyrParseNode* node = (PyrParseNode*)$3;
                                    node->mParens = 1;
                                    $$ = (intptr_t)newPyrVarDefNode((PyrSlotNode*)$1, node, 0);
                                }
;

「vardefとは、nameまたはname = exprまたはname(expreseq)である」とのこと。ここで、nameは

%token NAME INTEGER SC_FLOAT ACCIDENTAL SYMBOL STRING ASCII PRIMITIVENAME CLASSNAME CURRYARG

にあるようにtoken(終端記号)である。exprとexprseqはいわゆる式である。長いので引用は割愛する。

ひとまずまとめると、lang11dから普段使うVARのようなものを調べた結果、VAR v1(,v2,v3,...,vn);のうち、v1(,v2,v3,...,vn)の部分をfuncvardeclと呼ぶことが分かった。次にfuncvardeclを見てみる

funcvardeclを探す

funcvardeclのすぐ上に幾つか見つかる。

funcvardecls : { $$ = 0; }
                | funcvardecls funcvardecl
                    { $$ = (intptr_t)linkNextNode((PyrParseNode*)$1, (PyrParseNode*)$2); }
                ;

funcvardecls1   : funcvardecl
                | funcvardecls1 funcvardecl
                    { $$ = (intptr_t)linkNextNode((PyrParseNode*)$1, (PyrParseNode*)$2); }
                ;

funcvardecl : VAR vardeflist ';'
{ $$ = (intptr_t)newPyrVarListNode((PyrVarDefNode*)$2, varLocal); }

funcvardeclを並べたものをfuncvardecls・funcvardecls1と呼ぶ。では、funcvardeclsは・・・と更に辿ると面白い箇所を発見した。

cmdlinecode  : '(' funcvardecls1 funcbody ')'
                { $$ = (intptr_t)newPyrBlockNode(NULL, (PyrVarListNode*)$2, (PyrParseNode*)$3, false); }
            | funcvardecls1 funcbody
                { $$ = (intptr_t)newPyrBlockNode(NULL, (PyrVarListNode*)$1, (PyrParseNode*)$2, false); }
            | funcbody
                { $$ = (intptr_t)newPyrBlockNode(NULL, NULL, (PyrParseNode*)$1, false); }
;

cmdlinecodeの定義である。中括弧内を省略して書くと、

cmdlinecode  : '(' funcvardecls1 funcbody ')'
            | funcvardecls1 funcbody
            | funcbody
;

となる。cmdlinecodeは察するにコマンドラインコードであろう。IDEにコードを書いて選択して実行することを指すものだと思われる。そして、その定義というか受理される文法は

  • '(' funcvardecls1 funcbody ')'
  • funcvardecls1 funcbody
  • funcbody

の3タイプしかない。念のためfuncbody(おそらく関数本体を意味する)を見ると、

funcbody : funretval
            | exprseq funretval
                { $$ = (intptr_t)newPyrDropNode((PyrParseNode*)$1, (PyrParseNode*)$2); }
;

funretval(おそらく関数戻り値を意味する)を見ると、

funretval    :
            { $$ = (intptr_t)newPyrBlockReturnNode(); }
        | '^' expr optsemi
            { $$ = (intptr_t)newPyrReturnNode((PyrParseNode*)$2); }
;

となっている。つまり、funcbodyにVARは含まれていないことが分かる。

まとめ

VAR宣言はfuncbodyより前に書かれていないとcmdlinecodeとしては受理されず、よってsyntax error扱いされると思われる。斯くして、var宣言はまとめて書いた後に「式」を書けば良いという教訓は妥当であると言えよう。