web-dev-qa-db-ja.com

プログラミング言語パーサー(Java)-特別な場合のためのより良い設計代替案は何ですか?

バックグラウンド

私は現在、研究プロジェクトとして独自のプログラミング言語を設計しています。私は文法の大部分を実行して、文脈自由文法として書き留めてあり、そのまま動作するはずです。 -現在、言語を_x86 binary Assembly code_に変換する実際のコンパイラに取り組んでいます。具体的には、parser(フロントエンド)に取り組んでいます。

言語の構文は、ほとんどの場合、Java/C/C++によく似ています。ソースコードから中間表現を構築するパーサーは、次のように機能します。

文法は、実際のソースコードが葉のみを決定する大きなツリーとして構築されます。各構文変数(または非終端)には独自のクラスがあり、これらの各クラスには新しいリーフ(ノード)を返すstatic get(byte[] source, int offset)メソッドがあり、ソースコードの構文にない場合はnullがあります。この非終端構造に適合します。

ちなみに、私は_predictive parsing_のバリエーションを使用しています。

非終端記号DataTypeについては、次の文法構造を選択しました。

_DataType:
    PrimitiveDataType
 |  ArrayDataType
 |  ComplexDataType

ArrayDataType:
    DataType []
_

この言語がオブジェクト指向であることを私は言及しましたか?

したがって、ここでの問題は、DataTypegetメソッドが呼び出されると、最初に以下がプリミティブデータ型かどうかがチェックされ、PrimitiveDataTypegetメソッドが呼び出されるということです。 。配列があると仮定すると、これはnullを返すため、ArrayDataTypeメソッドを呼び出すことにより、それがgetかどうかを引き続きチェックします。

問題

配列は、配列自体(_Type[][]_のように見える)を含め、任意のデータ型で作成できます。したがって、ArrayDataTypegetメソッドが行うことは、DataTypegetメソッドを再度呼び出して、配列の型を判別することです。残念ながら、この動作ではループが発生するため、ここでパーサーの実装が失敗します。

質問

good/betterこれに代わる設計はありますか?

5
Kierrow

選択した予測パーサー(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
2
Andrey

問題は、 LL(1)パーサーを使用すると、左再帰が発生する可能性がある であるということです。これを回避するには、LRパーサーに切り替えるか、この文法を使用して再帰を回避します。

DataType:
    PrimitiveDataType ArrayDataType
 |  ComplexDataType ArrayDataType

ArrayDataType:
    [] ArrayDataType
 |  (empty)

解析がプロジェクトの一部でない場合は、解析ライブラリ/ジェネレータを検索するだけで、おそらくそこにたくさんあります。

2
Garrett Hall

あなたが直面しているこの問題は、私がすでに直面しているものと見た目が同じです。その場合、それは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と呼ばれ、効率的な方法で無限ループを回避するために実装しました。

このテクニックが問題の解決に役立つと確信しています。

0
rogcg

ここで重要なのは、LLではなくLRパーサーを使用することです。実際にLLが実際にLLを使用している理由は、説明されているとおりです。これは、多くの特に有用な文法を実際に認識できないためです。

0
DeadMG