コンパイル原理の実験 1 - 字句解析器の Java 実装

1. 実験の目的

PL/0 言語 (または C 言語のサブセットなどの他の言語のサブセット) の字句解析プログラムを設計および実装して、字句解析の原則の理解を深めます。

2. 実験原理

字句解析は、ソース プログラムの各行の記号を左から右にスキャンし、それらを単語に綴り、統一されたマシン内表現 (TOKEN 単語) に置き換えます。これが構文解析プログラムに送信されます。

TOKEN 単語はバイナリ形式です: (単語タイプ コード、独自の値)。PL/0 言語の単語のカテゴリ コードは整数で表され教科書を参照するか自分で設定できます。単語自体の値は、次の規則に従って与えられます。

1 識別子の自己値は、シンボル テーブル内のエントリのアドレスです。

  1. 定数の自己値は定数そのものです。
  2. キーワードと区切り文字の自己値はそれ自体です。

3. 実験手順と要件

1. 状態図に従って字句解析器を設計および実装する必要があります。

  2. 次の機能を持つプログラムをコンパイルします。

  1. 入力: 文字列 (字句解析するソース プログラム)。ファイルから読み取るか、キーボードから直接入力できます。

出力: (カテゴリ コード、独自の値)で構成されるバイナリ シーケンス。ファイルに保存するか、画面に直接表示できます。

単語のカテゴリ コードは、構文解析に必要な情報であり、整数コーディングで表すことができます。たとえば、識別子のカテゴリ コードは 1、定数は 2、予約語は 3、演算子は 4、デリミタは 5 です。

単語の自己値は、コンパイルの他の段階で必要な情報です。識別子の自己値は、記号テーブル内の識別子のエントリであり、他のタイプの単語の自己値はそれ自体です。  

次の例を参照できます。

入力文字列 if i>=15 then x := y;

出力: 

(3,‘if’)// i的符号表入口为0
(4,‘>=’)
(2,‘15’)
(3,‘then’)
(1,1) // x的符号表的入口为1
(4,‘:=’)
(1,2)   // y的符号表的入口为2
(5,‘;’)
  1. 関数:
  1. フィルタ スペース
  2. 予約語の認識: if then else while do など
  3. 識別 識別子: <文字>(<文字>|<数字>)*
  4. 整数を認識する: 0 | (1|2|3|4|5|6|7|8|9)(0|1|2|3|4|5|6|7|8|9)*
  5. + - * / > >= <= ( ) などの一般的な演算子と区切り記号を認識します
  1. 特定のエラー処理関数があります。たとえば、プログラミング言語の文字セット以外の不正な文字を検出できます。

3. 開発ツールを使用して、自分でインターフェースを設計し、いくつかの追加機能を自分で決定できます。

4. プログラムと運用結果を確認し、結果を評価するように教師に指示してください。

等級は、優、良、有資格、無資格に分けられます。初回の受入れで問題があれば、先生が指摘の上改善を認め、改善後に再受入れを行います。成績は最後の合格に基づいています。

5. 実験報告書を書いて提出する。

   実験レポートを提出する必要があり、このリンクを通じて実験の要約と分析の能力をトレーニングできます。最後に、実験レポートを参照して、実験結果を示します。

4. マシンレポートの内容

デザインのアイデア:

この実験は Java 言語で記述されており、語彙アナライザーはモジュール設計の考え方を採用しており、複数の正規表現パターンを通じて語彙要素を照合します。プログラムは最初に、一致する数字、文字と数字、演算子、区切り文字などを含む一連の正規表現パターンを定義します。さらに、C 言語のキーワードを識別するためのキーワード セットが定義されています。

字句解析器の主な機能は、入力されたソース コード文字列を lex メソッドで解析することです。このメソッドは、反復アプローチを使用して、入力文字列から字句要素を継続的に照合および抽出します。無限ループを回避するために、ループ回数を制限する条件を設けており、一致回数が100回を超えて一致が見つからなかった場合、不正な文字として認識されます。分析プロセス中に、一致した字句要素のタイプに従って Token オブジェクトとしてカプセル化され、結果リストに追加されます。

最後に、このプログラムには、分析結果を出力するための displayTokens メソッドも含まれています。このメソッドは、字句要素のリストを走査し、さまざまなタイプの Token オブジェクトに従って出力をフォーマットします。

実験のスクリーンショット:

テストケース:

if i>=15 then  x := y;

コード:

参考までに〜

public class Lexer {
    // 存储分析结果的列表
    private static List<Token> tokens = new ArrayList<>();
    private static int count = 0; // 标识符记录器,每次自增 1
    // C 语言关键字集合
    private static Set<String> keywords = Set.of("auto", "break", "case", "char", "const", "continue", "default", "do", "double", "else", "enum", "extern", "float", "for", "goto", "if", "int", "long", "register", "return", "short", "signed", "sizeof", "static", "struct", "switch", "typedef", "union", "unsigned", "void", "volatile", "while", "then");
    private static Map<String, Integer> typeCodes = Map.of("标识符", 1, "常数", 2, "保留字", 3, "运算符", 4, "界符", 5, "非法字符", 6);
    // regex 字符串表达式,识别数字
    private static Pattern patternNumber = Pattern.compile("\\d+");
    // regex 字符串表达式,识别字母和数字
    private static Pattern patternLetterNumber = Pattern.compile("^[a-zA-Z_][a-zA-Z0-9_]*");
    // regex 字符串表达式,识别C语言中的所有操作符
    private static Pattern patternSymbol = Pattern.compile("\\{\\}|\\{|\\}|~|==|>=|<=|'|=|\"|&&|\\^|\\(\\)|\\(|\\)|\\|\\||:=|[+\\-*/><]");
    // regex 字符串表达式,识别界符;
    private static Pattern patternDelimiter = Pattern.compile(";");

    public static void main(String[] args) {
        String  input   = "if i>=15 then  x := y;";
        Scanner scanner = new Scanner(input).useDelimiter("\\s+");
        // 拆分空格,对每一项进行词法分析
        while (scanner.hasNext()) {
            String next = scanner.next();
            lex(next);
        }
        displayTokens(); // 输出结果
    }

    /**
     * 词法分析,结果存入 tokens
     *
     * @param msg 输入字符串
     */
    public static void lex(String msg) {
        StringBuilder sb    = new StringBuilder(msg);
        int           times = 0;
        // 当输入字符串非空时,持续运行
        while (sb.length() > 0) {
            matchAndAddToken(sb, patternLetterNumber, typeCodes.get("标识符"));
            matchAndAddToken(sb, patternSymbol, typeCodes.get("运算符"));
            matchAndAddToken(sb, patternNumber, typeCodes.get("常数"));
            matchAndAddToken(sb, patternDelimiter, typeCodes.get("界符"));
            // 100 次循环还没匹配到,说明有非法字符
            if (times++ > 100) {
                String match = sb.substring(0, 1);
                sb.delete(0, 1);
                tokens.add(new Token(typeCodes.get("非法字符"), match));
            }
        }
    }

    /**
     * 尝试匹配给定的正则表达式,如果匹配成功,添加到 tokens 列表并返回 true,否则返回 false
     *
     * @param sb       输入字符串
     * @param pattern  正则表达式
     * @param typeCode 类型码
     * @return 是否匹配成功
     */
    private static boolean matchAndAddToken(StringBuilder sb, Pattern pattern, int typeCode) {
        Matcher matcher = pattern.matcher(sb.toString());
        if (matcher.find()) {
            String match = matcher.group();
            sb.delete(0, match.length());
            // 判断是不是关键字
            if (typeCode == typeCodes.get("标识符") && keywords.contains(match)) {
                tokens.add(new Token(typeCodes.get("保留字"), match));
            } else if (typeCode == typeCodes.get("标识符")) {
                tokens.add(new Token(typeCodes.get("标识符"), count, "//" + match + "符号表的入口为" + count++));
            }else {
                    tokens.add(new Token(typeCode, match));
                }
            return true;
        }
        return false;
    }

    // 输出结果
    public static void displayTokens() {
        for (Token token : tokens) {
            if (token.msg != null) {
                System.out.println("(" + token.typeCode + ", " + token.value + ")  " + token.msg);
            } else {
                System.out.println("(" + token.typeCode + ", '" + token.value + "')");
            }
        }
    }

    /**
     * Token 类
     * typeCode: 类型码
     * value: 值
     */
    static class Token {
        int typeCode;
        Object value;
        String msg;

        public Token(int typeCode, Object value) {
            this.typeCode = typeCode;
            this.value = value;
        }
        public Token(int typeCode, Object value, String msg) {
            this.typeCode = typeCode;
            this.value = value;
            this.msg = msg;
        }
    }
}

5.実験のまとめと収穫

この実験の主な目的は、字句解析の原理の理解を深めるために、PL/0 言語 (または他の言語のサブセット) の字句解析プログラムを設計および実装することです。実験はJava言語で書かれており、字句要素は複数の正規表現パターンを通じて照合され、予約語、識別子、定数、演算子、区切り記号などを識別する機能を実現し、特定のエラー処理機能を備えています。

この実験を通じて、字句解析の原理を深く理解しただけでなく、Java 言語の正規表現とモジュール設計の考え方についても学びました。ソース プログラムをスキャンして分析することにより、語彙アナライザーは、後続の構文分析とコード生成の基本的なサポートを提供し、コンパイラーのプロセス全体の強固な基盤を築くことができます。

実験では、異なる言語の違いを考慮し、さまざまな異常な状況を合理的に処理する必要があるなど、注意を払う必要があるいくつかの問題が見つかりました。これらの問題を克服することで、字句解析の実装プロセスに慣れるだけでなく、プログラミング能力とコード品質も向上しました。

一般に、この実験は字句解析の原理についての理解を深めただけでなく、プログラミング能力とコードの品質も向上させました。今後のソフトウェア開発作業のためのより強固な基盤を築くために、コンパイルの原則に関する関連知識を学ぶために引き続き努力していきます。

おすすめ

転載: blog.csdn.net/qq_35760825/article/details/130349798