2

tl;dr:#define前処理ステップを実行せずに、jison を使用してC に相当するものをどのようにエミュレートしますか?

私は、簡潔にするために後で再利用できるコードのチャンクに識別子を割り当てる機能を備えた比較的単純な文法に取り組んでいます。例:

# Valid grammar with various elements of different types
foo x.3 y.4 z.5

# Assign an id to a chunk of code. Everything after -> is assigned to id
fill_1-> bar a.1 b.2 c.3

# Use chunk of code later
# Should be equivalent to parsing: "baz d.4 bar a.1 b.2 c.3 e.5"
baz d.4 ["fill_1"] e.5

これまでのところ、コードの割り当て行を正しく識別し、「->」の右側の部分を他のパーサー アクションで使用できる辞書に格納するようにパーサーをセットアップしました。以下に示す定義アクションに関連するコード:

// Lexer
HSPC                      [ \t]
ID                        [a-zA-Z_][a-zA-Z0-9_]*
%%
{ID}{HSPC}*"->"       {
                        this.begin("FINISHLINE"); 
                        yytext = yytext.replace("->", "").trim();
                        return "DEFINE";
                      }

('"'{ID}'"')          { 
                        yytext = yytext.slice(1,-1);
                        return "QUOTED_ID"; 
                      }

<FINISHLINE>.*        {
                        this.begin("INITIAL");
                        yytext = yytext.trim();
                        return "REST_OF_LINE";
                      }

%%

// Parser
statement
  : "[" QUOTED_ID "]"
    { $$ = (defines[$2] ? defines[$2] : ""); }
  | DEFINE  REST_OF_LINE
    { 
      defines[$1] = $2;
    }
  ;

%% 
var defines = {};

保存されたコードのスニペットを実際にトークン化して解析するように jison を取得するにはどうすればよいですか? AST アプローチを採用する必要がありますか? コードをパーサーに挿入する方法はありますか? これは字句解析段階または解析段階で発生する必要がありますか? 短い例のスニペットを使用して、複数の戦略を採用できることを楽しみにしています。

ありがとう!

4

3 に答える 3

2

「AST アプローチを採用する」とは、「元の置換されていないプログラムと置換用の AST を構築し、それらをつなぎ合わせる」ことを意味する場合、苦労しています。置換された文字列が文法の有効な非終端記号と一致するという保証はないため、ツリーを構築するのは簡単ではありません。置換前のメイン プログラムも、完全な文法では解析できない可能性が非常に高くなります。[これらの問題は、部分文字列パーサーを構築し、ツリー フラグメントを接着することで克服できますが、大変な作業になります [C プリプロセッサ アナライザーに対してこのようなことを行っています]。

これに対する通常のアプローチは、部分的に読み取られた入力ストリームのスタックをレクサーに保持させ、一番下のストリームがメイン プログラムであり、部分的に読み取られたマクロの呼び出しに対応するネストされたストリームを保持することです (1 つのマクロが別のマクロを呼び出すことができる場合は、複数のストリームが必要です)。 . 確かにあなたの言語は "fill2 -> x.1 [ fill1 ] y.3 " を許可していますか? これはレクサーが以下をしなければならないことを意味します:

  • マクロ定義を認識するため、名前とマクロ コンテンツ間のマッピングにアクセスできます。
  • マクロ呼び出しを認識します (字句解析または解析状態を乱すことなく。通常、これはマクロ呼び出しが字句解析器のアドホック機構によって認識される必要があることを意味します)
  • 呼び出されたマクロの入力ストリームをストリーム スタックに「プッシュ」する
  • ストリームの終わりに達したときにストリーム スタックを「ポップ」する

いつの日か、マクロにパラメーターが必要だと判断するかもしれません。通常、これらもストリームとして実装できます。

トークンを字句解析し、マクロ本体としてテキストではなくトークン ストリームを格納することを想像できます。マクロ呼び出しの検出と本体の挿入は、レクサーの後、パーサーの前に発生する可能性があります。両者の間にはおそらくインターフェースがあるので、これを管理するために間にコードを配置するのが実用的な方法のようです。プログラム内の異なる場所で同じ文字列を別の方法で解釈することが言語で許可されている場合、複雑な問題が発生する可能性があります。この場合、マクロ キャプチャはどのようにしてマクロ コンテンツを lex するかを知るのでしょうか?

これを達成する方法を詳しく説明するには、ANTLR3 について十分 (または多く) 知りません。

于 2013-12-30T07:38:41.337 に答える
0

したがって、さらに読んだ後、前処理を必要としない1つの潜在的なアプローチを次に示します。

現在、さまざまなタイプのノードのパーサーで抽象構文ツリーを生成しています。私の AST はさまざまなクラスで構成されており、基本単位を表すものもあれば、メタ単位を表すものもあります。私の「マクロ」は、解析された AST 要素のコレクションへの参照を保持する独自の AST 要素で表されます。そのため、コードを置換してから複数回解析する「コード置換」を行う代わりに、定義されたチャンクを 1 回解析し、作成された AST 要素への参照を格納しています。例えば:

# fundamental units
class TabElement
# subclasses of TabElement
class NoteElement
class SlideElement
class MuteElement

# meta element: holds a collection of TabElements's
class NoteGroupElement
# meta element: holds a collection of TabElements's and otherstatements, has an ID
#               the collection is accessible via a global dict to other elements
class PredefElement
# meta element: represents a usage of a PredefElement
class PredefInvokeElement

これを行うための簡略化されたレクサーとパーサーのルールは、次のようになります (多くの無関係なものは省略されています。うまくいけば、全体像がわかると思います)。

/* LEXER */
<INITIAL>[\s]             /* ignore whitespace */
{ID}{HSPC}*"->"       {
                        this.begin("DEFINE"); 
                        yytext = yytext.replace("->", "").trim();
                        return "DEFINE";
                      }
/* using a pre-def */
('"'{ID}'"')|("'"{ID}"'") { 
                        yytext = yytext.slice(1,-1);
                        return "QUOTED_ID"; 
                      }

/* When defining a code chunk.  Newlines delimit the end of the definition */
<DEFINE>{HSPC}            /* ignore horizontal whitespace */
<DEFINE>({NL}|<<EOF>>)    { this.begin("INITIAL"); return "NL"; }

/* PARSER */
statement
  : statement_group
    { $$ = $1; }
  | DEFINE statements NL
    { 
      defines[$1] = new ast.PredefinedElement($1, $2, @1);
      $$ = defines[$1]
    }
  | predefine_invoke
    { $$ = $1; }
  | chord
    { $1 = $1; }
  | bar_token
    { $$ = $1; }
  | REPEAT_MEASURE
    { $$ = new ast.RepeatMeasureElement(@1); }
  | REST
    { $$ = new ast.RestElement(@1); }
  | DURATION
    { $$ = new ast.DurationElement($1, @1); }
  | note_token
    { $$ = $1; }
  ;

predefine_invoke
  : "[" QUOTED_ID "]"
    %{ 
      if (defines[$2]) { $$ = new ast.PredefinedInvokeElement(defines[$2], @2); } 
      else { $$ = undefined; }
    %}
  | "[" QUOTED_ID "]" "*" INTEGER
    %{ 
      if (defines[$2]) { $$ = new ast.PredefinedInvokeElement(defines[$2], @2, { repeat: parseInt($5) }); } 
      else { $$ = undefined; }
    %}
  ;
于 2014-01-01T23:45:03.067 に答える