私は現在、研究プロジェクトとして独自のプログラミング言語を設計しています。私は文法の大部分を実行して、文脈自由文法として書き留めてあり、そのまま動作するはずです。 -現在、言語を_x86 binary Assembly code
_に変換する実際のコンパイラに取り組んでいます。具体的には、parser
(フロントエンド)に取り組んでいます。
言語の構文は、ほとんどの場合、Java/C/C++によく似ています。ソースコードから中間表現を構築するパーサーは、次のように機能します。
文法は、実際のソースコードが葉のみを決定する大きなツリーとして構築されます。各構文変数(または非終端)には独自のクラスがあり、これらの各クラスには新しいリーフ(ノード)を返すstatic get(byte[] source, int offset)
メソッドがあり、ソースコードの構文にない場合はnull
があります。この非終端構造に適合します。
ちなみに、私は_predictive parsing
_のバリエーションを使用しています。
非終端記号DataType
については、次の文法構造を選択しました。
_DataType:
PrimitiveDataType
| ArrayDataType
| ComplexDataType
ArrayDataType:
DataType []
_
この言語がオブジェクト指向であることを私は言及しましたか?
したがって、ここでの問題は、DataType
のget
メソッドが呼び出されると、最初に以下がプリミティブデータ型かどうかがチェックされ、PrimitiveDataType
のget
メソッドが呼び出されるということです。 。配列があると仮定すると、これはnull
を返すため、ArrayDataType
メソッドを呼び出すことにより、それがget
かどうかを引き続きチェックします。
配列は、配列自体(_Type[][]
_のように見える)を含め、任意のデータ型で作成できます。したがって、ArrayDataType
のget
メソッドが行うことは、DataType
のget
メソッドを再度呼び出して、配列の型を判別することです。残念ながら、この動作ではループが発生するため、ここでパーサーの実装が失敗します。
good/betterこれに代わる設計はありますか?
選択した予測パーサー(LL(k))は、左再帰問題を解決する必要があることを意味します。直接および間接の再帰を解決するためのアルゴリズムは、Wikipediaに明確に記載されています。
http://en.wikipedia.org/wiki/Left_recursion
一部の情報は、StackOverflowのこちらの投稿にあります。
https://stackoverflow.com/questions/2652060/removing-left-recursionhttps://stackoverflow.com/questions/2999755/removing-left-recursion-in-antlrhttps://stackoverflow.com/questions/4994036/left-recursion-elimination
人間の言語(非科学的)では、「左再帰問題」とは、非終端(A-> Ab)で何度も何度も再帰を繰り返すことができないことを意味します。時には、ループを壊すためにパーサーアルゴリズムをターミナルにフィードする必要があります。
BNFでは、次のようになります。
再帰問題:
NT: NT T
NT: T
1つの解決策:
NT: T NT2
NT2: T NT2
NT2:
文法の場合、これは次のようになります。
DataType:
PrimitiveDataType ArrayDimensions
| ComplexDataType ArrayDimensions
ArrayDimensions:
[] ArrayDimensions
|
パーサージェネレーターで空のプロダクションが許可されていない場合や、配列型を個別に処理したい場合は、次のようにします。
DataType:
DataTypeName
| ArrayDataType
ArrayDataType:
DataTypeName ArrayDimensions
DataTypeName:
PrimitiveDataType
| ComplexDataType
ArrayDimensions:
[]
| [] ArrayDimensions
問題は、 LL(1)パーサーを使用すると、左再帰が発生する可能性がある であるということです。これを回避するには、LRパーサーに切り替えるか、この文法を使用して再帰を回避します。
DataType:
PrimitiveDataType ArrayDataType
| ComplexDataType ArrayDataType
ArrayDataType:
[] ArrayDataType
| (empty)
解析がプロジェクトの一部でない場合は、解析ライブラリ/ジェネレータを検索するだけで、おそらくそこにたくさんあります。
あなたが直面しているこの問題は、私がすでに直面しているものと見た目が同じです。その場合、それはSyntatic Analyzerクラスに実装する必要があります。そうです、ツリーを生成します。確かに再帰的です。
例:私の場合、関数が引数のリストを受け取る方法を実装する必要があったので、引数リストの文法規則は次のようになります。
_<ArgLi> ::= <Arg> <ArgLi> |
_
どこ:
<Arg> ::= '(' <Type> Identifier ')'
_<ArgLi>
_はrecursivellyと呼ばれます
_<Type> ::= 'int' | 'real' | 'str'
_
_Identifier = {ID Head}{ID Tail}*
_
したがって、その機能を実装するために、コマンドには区切り文字が含まれているため、ループに入った場合でも、ルールをチェックし、現在のトークンに基づいてアクションを決定します。
たとえば、私がこれを書きたいとしましょう:
_( def main [ ( int a1 ) ( str a2 ) ]
...
)
_
これを理解するために私の言語が従うコードは次のとおりです。
_/**
* <Def> ::= '(' 'def' Identifier '[' <ArgLi> ']' <CommandLi> ')'
*/
public Node Def() {
Node node = new Node(NodeIdentifier.DEF);
if (token.getImage().equals("(")) {
node.addSon(new Node(NodeIdentifier.TOKEN, token));
token = readToken();
if (token.getImage().equals("def")) {
node.addSon(new Node(NodeIdentifier.TOKEN, token));
token = readToken();
if (token._getClass().equals("ID")
|| token.getImage().equals("main")) {
node.addSon(new Node(NodeIdentifier.TOKEN, token));
token = readToken();
if (token.getImage().equals("[")) {
node.addSon(new Node(NodeIdentifier.TOKEN, token));
token = readToken();
node.addSon(argLi());
if (token.getImage().equals("]")) {
node.addSon(new Node(NodeIdentifier.TOKEN, token));
token = readToken();
node.addSon(commandLi());
if (token.getImage().equals(")")) {
node.addSon(new Node(NodeIdentifier.TOKEN,
token));
token = readToken();
} else {
errors.add("Error: token ')' not recognized. line: "
+ token.getLine());
}
} else {
errors.add("Error: token ']' not recognized. line: "
+ token.getLine());
}
} else {
errors.add("Error: token '[' not recognized. line: "
+ token.getLine());
}
} else {
errors.add("Error: token 'ID' not recognized. line: "
+ token.getLine());
}
} else {
errors.add("Error: token 'def' not recognized. line: "
+ token.getLine());
}
} else {
errors.add("Error: token '(' not recognized. line: "
+ token.getLine());
}
return node;
}
/**
* <ArgLi> ::= <Arg> <ArgLi> |
*/
public Node argLi() {
Node node = new Node(NodeIdentifier.ARGLI);
if (token.getImage().equals("(")) {
node.addSon(arg());
node.addSon(argLi());
}
return node;
}
/**
* <Arg> ::= '(' <Type> Identifier ')'
*/
public Node arg() {
Node node = new Node(NodeIdentifier.ARG);
if (token.getImage().equals("(")) {
node.addSon(new Node(NodeIdentifier.TOKEN, token));
token = readToken();
node.addSon(type());
if (token._getClass().equals("ID")) {
node.addSon(new Node(NodeIdentifier.TOKEN, token));
token = readToken();
if (token.getImage().equals(")")) {
node.addSon(new Node(NodeIdentifier.TOKEN, token));
token = readToken();
} else {
errors.add("Error: token ')' not recognized. line: "
+ token.getLine());
}
} else {
errors.add("Error: token 'ID' not recognized. line: "
+ token.getLine());
}
} else {
errors.add("Error: token '(' not recognized. line: "
+ token.getLine());
}
return node;
}
_
ご覧のとおり、メソッドarg()
およびargLi()
はrecursivellyと呼ばれ、効率的な方法で無限ループを回避するために実装しました。
このテクニックが問題の解決に役立つと確信しています。
ここで重要なのは、LLではなくLRパーサーを使用することです。実際にLLが実際にLLを使用している理由は、説明されているとおりです。これは、多くの特に有用な文法を実際に認識できないためです。