مرة أخرى حول تحليل التعبيرات باستخدام طريقة النسب العودية

كنت لا أزال تلميذًا عندما وقعت في أيدي كتاب "Beginner Course C و C ++" من دار النشر Dialog MEPhI. لقد تعلمت من هذا الكتاب عن أساسيات البرمجة الكينونية ، كما شكل مشكلة بالنسبة لي لم أستطع حلها لفترة طويلة. في نهاية الأمر كان هناك تطبيق لقائمة التطبيقات ، أحدها كان يسمى الآلة الحاسبة العودية. كان هذا أول لقاء لي مع تحليل التعبيرات الحسابية باستخدام طريقة النسب العودية. ما زلت أتذكر تلك الساعات الطويلة التي قضيتها في محاولة فهم شيء على الأقل في هذا الرمز.

المقالة مخصصة في المقام الأول لأولئك الذين يواجهون مهمة تحليل التعبيرات لأول مرة.

وهكذا ، قبل كتابة المحلل اللغوي للتعبيرات الحسابية ، نحتاج إلى فهم بنية هذه التعبيرات. من أجل البساطة ، لن نأخذ في الاعتبار العملية أحادية الطرح وإمكانية استخدام الوظائف في التعبير في الوقت الحالي.

لوصف الهيكل ، نستخدم أشكال Beckus-Naur

E -> T + E | T - E | ت
T -> F * T | F / t | F
F -> N | (هـ)

هذا هو الشريط الرأسي "|" تعني عملية منطقية "أو" ، و E و T و F و N - أنواع مختلفة من التعبيرات الفرعية التي سيتم تقسيم التعبير الأصلي إليها عند استخدام هذه الجمل. N في هذه الحالة تشير إلى رقم.

الجملة الأولى تعني أن التعبير من النوع E يمكن أن يتكون من تعبير من النوع T بالإضافة إلى تعبير من النوع E ، أو تعبير من النوع T ناقص تعبير من النوع E أو مجرد تعبير من النوع T.

بالنسبة للجملة الثانية ، كل شيء مشابه ، ولكن بدلاً من الجمع والطرح ، سيكون هناك الضرب و قطاع.

الجملة الثالثة تعني أن التعبير من النوع F هو إما رقم أو تعبير من النوع E داخل أقواس.

دعونا نحاول تطبيق هذه الجمل الثلاثة على مثال لتعبير حسابي

2 + 3 * (4 - 5) + 6-7

نطبق الجملة الأولى عدة مرات

E (2 + 3 * (4-5) + 6-7) -> 
T (2) + E (3 * (4-5) + 6-7) -> 
T (2) + T (3 * (4-5)) + E (6-7) -> 
T (2) + T (3 * (4-5)) + T (6) - T (7)

يتكون تعبيرنا الآن فقط من التعبيرات الفرعية من النوع T ، لذلك نطبق الجملة الثانية على كل منها.

T (2) -> F (2)
T (3 * (4-5)) -> F (3) * T ((4-5)) -> F (3) * F ((4-5))
T (6) -> F (6)
T (7) -> F (7)

حان الوقت للجملة الثالثة

F (2) -> N (2) -> 2
ف (3) -> ن (3) -> 3
F ((4-5)) -> E (4-5)
ف (6) -> ن (6) -> 6
ف (7) -> ن (7) -> 7

للتعبير بين قوسين (4-5) ، تحولنا بشكل متكرر إلى الجملة الأولى وسيتعين علينا تكرار جميع الإجراءات عن طريق القياس مع ما كان عليه لبقية التعبير. إذا احتوى التعبير على أقواس متداخلة ، فسيكون هناك المزيد من التحولات العودية. أوصي بأن تحاول بشكل مستقل تطبيق الجمل الثلاث على تعبير يحتوي على مستويات متداخلة من الأقواس ، على سبيل المثال 2 + 2 * (3 + 4 * (5-6)) ، للتأكد من أنها تعمل من أجلها أيضًا.

الآن علينا فقط تنفيذ الإجراءات التي تنفذها المقترحات في شكل رمز. سيكون هذا هو تحليل التعبير باستخدام طريقة الهبوط العودي ، ولكن قبل أن تبدأ في كتابة التعليمات البرمجية ، دعنا نبسط الجمل قليلاً عن طريق التخلص من العودية في الأولين.

إذا استبدلنا الجملة الأولى في نفسها إلى ما لا نهاية ، فإننا نحصل عليها

E -> T ± T ± T ± T ± ... ± T

يمكن تحويل الجملة الثانية بطريقة مماثلة.

T -> F * / F * / F * / * / ... * / F

في الواقع ، هذا يعني أنه بدلاً من العودية ، يمكننا استخدام حلقة هنا. وبالمثل ، لا يمكن استبعاد العودية من الجملة الثالثة.

سنكتب الرمز في جافا ، لأن هذه هي لغة البرمجة الأكثر شيوعًا بالنسبة لي في الوقت الحالي. الملاحظة الأخيرة قبل أن نبدأ في كتابة الرمز - أترك مهمة الترميز ، أي تقسيم التعبير إلى عناصر مهمة غير قابلة للتجزئة (الرموز). هذه الرموز هي أرقام وعلامات العمليات الحسابية والأقواس. افترض أن لدينا بالفعل مصفوفة رمزية كمدخلات من التعبير.

String[] tokens = {"2", "+", "3", "*", "(", "4", "-", "5", ")", "+", "6", "-", "7"};

قم بإنشاء فئة المحلل اللغوي التالية التي ستحتوي على الأساليب

  • يبدأ التحليل () في تحليل تعبير
  • التعبير () للتعامل مع تعبيرات النوع E
  • مصطلح () لمعالجة التعبيرات من النوع T
  • عامل () لمعالجة التعبيرات من النوع F و N

public class RecursiveDescentParser {

    private final String[] tokens;
    private int pos = 0; //   

    public RecursiveDescentParser(String[] tokens) {
        this.tokens = tokens;
    }

    public Double parse() {
        Double result = expression();
        if (pos != tokens.length) {
            throw new IllegalStateException("Error in expression at " + tokens[pos]));
        }
        return result;
    }

    // E -> T±T±T±T± ... ±T
    private Double expression() {
        //   
        Double first = term();

        while (pos < tokens.length) {
            String operator = tokens[pos];
            if (!operator.equals("+") && !operator.equals("-")) {
                break;
            } else {
                pos++;
            }

            //    ()
            Double second = term();
            if (operator.equals("+")) {
                first += second;
            } else {
                first -= second;
            }
        }
        return first;
    }

    // T -> F*/F*/F*/*/ ... */F
    private Double term() {
        //   
        Double first = factor();

        while (pos < tokens.length) {
            String operator = tokens[pos];
            if (!operator.equals("*") && !operator.equals("/")) {
                break;
            } else {
                pos++;
            }

            //    ()
            Double second = factor();
            if (operator.equals("*")) {
                first *= second;
            } else {
                first /= second;
            }
        }
        return first;
    }
    
    // F -> N | (E)
    private Double factor() {
        String next = tokens[pos];
        Double result;
        if (next.equals("(")) {
            pos++;
            //    ,        
            result = expression();  
            String closingBracket;
            if (pos < tokens.length) {
                closingBracket = tokens[pos];
            } else {
                throw new IllegalStateException("Unexpected end of expression");
            }
            if (pos < tokens.length && closingBracket.equals(")")) {
                pos++;
                return result;
            }
            throw new IllegalStateException("')' expected but " + closingBracket);
        }
        pos++;
        //       
        return Double.parseDouble(next);
    }
}

هذا كل شئ. إذا رغبت في ذلك ، يمكن تدريس هذا المحلل اللغوي للعمل مع ناقص أو وظائف أحادية في التعبير. أول شيء يجب فعله هو تغيير الجمل الأصلية بحيث تأخذ في الاعتبار عنصرًا جديدًا من بنية التعبير ، وقد يكون هناك المزيد من المقترحات من هذا. بمجرد تعديلها بشكل صحيح ، يصبح التنفيذ شبه رسمي.

All Articles