0

私は現在、8つのキーワード(大文字と小文字を区別しない)と4つの算術演算子を使用して基本的なインタープリターを作成する割り当てに取り組んでいます。この言語のプログラムは次のようになります(実際にはBASICの構文に似ています)。

# (signals start of a comment line)
LET
INTEGER
STRING
PRINT
END

とにかく、私は現在、解析するテキストの行をトークン化しようとしています。私はすでにテキストのすべての行をArrayListに解析し、文字列をトークン化しました。私の現在の問題は、StringTokenizerがすべての文字列を事前にトークン化することです(区切り文字として空白を使用しています)。必要なのは、コード行の最初の単語であるキーワードを見つけることです。 、および特定の問題により、それは望ましくありません。String.split()を使用してもあまり役に立たないと思います。

私が計画していた方法は、インタプリタに最初のトークンを見つけてもらい、そこからHashMapを介して適切なクラスに移動することでした(ここで、インタプリタのswitchステートメントの使用に関する以前の質問を参照してください:Switchまたはifステートメントjavaでインタプリタを書く際に;他のメンバーから、キーワードトークンを削除して実行するためにマップを使用することが提案されました。特に変数を保持するために2番目の一時的なArrayListまたは配列を設定するのは良い考えですか?過度に複雑にする必要はありません。

提案を事前に感謝します。

public static void main (String[]args)
    {
        try
        {
            ArrayList<String> demo= new ArrayList <String>();
            FileReader fr= new FileReader("hi.tpl");
            BufferedReader reader= new BufferedReader(fr);
            String line;
            while ((line=reader.readLine()) !=null)//read file line by line
                {
                    //Add to ArrayList
                    demo.add(line);
                }

            reader.close();

            boolean checkEnd= demo.contains("END");//check if arraylist contains END statement
                    if(line=null && checkEnd== false)
                        {
                            System.out.println(" Unexpected end of file: no END statement");
                            System.exit(0);
                        }

            ListIterator<String>arrayListIt=demo.listIterator();
            while (arrayListIt.hasNext())
            for (String file: demo)// begin interpreting the program file here
                {               
                    StringTokenizer st=new StringTokenizer(file);
                    while(st.hasMoreTokens())
                        {

                            int firstWord=file.indexOf();
                            String command = file;
                            if (firstSpace > 0)
                            {
                                command= file.substring(0, firstSpace);
                            }
                            TokenHandler tokens= tokens.get(command.toUpperCase());
                            if(tokens != null)
                            {
                                tokens.execute(file);
                            }

                        }
4

3 に答える 3

1

したがって、それを実行している場合は、よりOOのアプローチを使用します。

すべて同じインターフェイスを実装するコマンドごとに「クラス」を作成した場合はどうなりますか?インターフェイス-それをCommandObjectと呼びましょう。execute()メソッドがあります。

次に、「Let」などのコマンドをLetクラスのインスタンスにマップするプリロードされたマップを使用できます。

これで、メインループは次のようになります(疑似):

for(line:lineList)
    CommandObject commandObject=map.get(line.split()[0]) // do this more clearly
    commandObject.execute(variableHash, line) // Parse and execute the line

これらのコマンドオブジェクトは、変数のセットを共有する必要があります。シングルトンを作成することは機能しますが、多少アンチパターンです。代わりに、ハッシュマップ(上記のvariableHash)として渡すことをお勧めします。

このアプローチの良いところは、新しい「コマンド」の追加が非常に簡単で、ほとんど独立していることです。

編集(コメント再):

最初に行うことは、ハッシュマップを作成し、各コマンドを「インストール」することです。例:(まだ疑似コードですが、自分で割り当てを行う方がよいと思います)

map = new HashMap<String, CommandObject>

次に、各クラスのインスタンスをマップに追加します。

map.put("LET", new LetCommand());
map.put("INTEGER", new Integercommand());

ここで、右側のクラスは「CommandObject」インターフェースを実装します。

各CommandObjectは、そのキーワードが見つかるたびに再利用されるインスタンスであるため、おそらくどの状態も保存しないでください(インスタンス変数はありません)。これは、CommandObjectが必要とするメソッドが1つだけであることを意味します。 :

execute(String commandLine, HashMap variables);

これはおそらく最も簡単な方法です(これを反映するために、元の提案から上記のテキストを編集しました)。

このパーサーがより複雑になった場合、「CommandObject」に機能を追加することは完全に有効です。reset()メソッドがある限り、状態変数を保持できます(私の最初の提案ですが、実行していることには非常に複雑に見えます)。

コマンドオブジェクトへのキーワードのマップはリフレクションに置き換えることができますが、学校の課題ではそうしようとしないでください。リフレクションの複雑さは時間の価値がなく、教師がダウングレードする可能性があります。理解できません。私はこのようなシステムを実装しました。すべてのキーワードがテストにリンクされています(テストのチェーン、テストのループ、さらにはそれらのテストに渡されて操作される変数の定義と実行が可能です。この場合、リフレクションは価値があります。新しいテストでは、キャッシュを更新する必要はありませんでした)

于 2012-01-10T19:26:22.927 に答える
0

良い方法の1つは、を使用することenumです。splitまた、2点限定での使用は控えさせていただきます。

enum Command {
    LET {
        @Override
        public void execute(Context context, String args) {
        }
    },
    INTEGER { ... },
    STRING { ... },
    PRINT { ... },
    END { ... };

    public abstract void execute(Context context, String args);
}

private void executeLine(String line) {
    String[] commandAndArgs = line.split("\\s+", 2);
    String command = "";
    String args = "";
    if (commandAndArgs.length > 0)
        command = commandArgs[0].toUpperCase();
    if (commandAndArgs.length > 1)
        args = commandArgs[1];
    Command cmd = Command.valueOf(command);
    Context context = ...;
    cmd.execute(context, args);
}
于 2012-01-10T19:12:50.770 に答える
0

正しい設計は、これらの引数の解析を後回しにすることであるように思われます。「STRING」や「PRINT」などの特定のコマンドは空白に依存しない場合があります。ここで、STRING S ="HELLOWORLD"は実際にはSTRINGS="HELLOWORLD"と機能的に違いはありません。このようなことを前もって「オーバーエンジニアリング」したくはありません。現在機能している最も単純なことを実行し、1つまたは2つのコマンドクラスを記述してから、それらのコマンドクラスに共通するものを理解することをお勧めします。

後で、すべて(またはほとんど)のコマンドでこれらの引数を特定の方法でリストに解析する必要があることがわかった場合は、その「リスト解析コード」を静的ユーティリティメソッド(または、場合によっては非静的ユーティリティメソッド)にリファクタリングできます。継承を使用している場合は、親Commandクラス自体。)これは、一連の自動テストを作成するのが賢明な場合はリスクがはるかに低くなりますが、そのようなタスクは割り当ての範囲外になる可能性があります。

于 2012-01-10T18:41:43.270 に答える