JavaRush /مدونة جافا /Random-AR /التعبيرات العادية في جافا، الجزء 5

التعبيرات العادية في جافا، الجزء 5

نشرت في المجموعة
نقدم انتباهكم إلى ترجمة دليل قصير للتعبيرات العادية في Java، كتبه Jeff Friesen لموقع javaworld. ولتسهيل القراءة قمنا بتقسيم المقال إلى عدة أجزاء. هذا الجزء هو الجزء الأخير. التعبيرات العادية في جافا، الجزء 5 - 1التعبيرات العادية في جافا، الجزء 1 التعبيرات العادية في جافا، الجزء 2 التعبيرات العادية في جافا، الجزء 3 التعبيرات العادية في جافا، الجزء 4

استخدام التعابير العادية للتحليل المعجمي

التطبيق الأكثر فائدة للتعبيرات العادية هو مكتبة التعليمات البرمجية القابلة لإعادة الاستخدام لإجراء التحليل المعجمي، وهو مكون رئيسي لأي مترجم أو مجمع. في هذه الحالة، يتم تجميع دفق الإدخال من الأحرف في دفق إخراج من الرموز المميزة - أسماء تسلسلات من الأحرف التي لها معنى مشترك. على سبيل المثال، بعد مواجهة تسلسل الأحرف c، oو u، nو، tو e، و r، في دفق الإدخال، يمكن للمحلل المعجمي إخراج رمز مميز ID(معرف). يُطلق على تسلسل الأحرف المقابلة للرمز المميز اسم المعجم.
المزيد عن العلامات والمعاجم
يمكن أن تتطابق الرموز المميزة مثل المعرف مع العديد من تسلسلات الأحرف. في حالة مثل هذه الرموز المميزة، فإن الرمز المميز الفعلي المطابق للرمز المميز مطلوب أيضًا بواسطة المترجم أو المجمّع أو أي أداة مساعدة أخرى تتطلب تحليلًا معجميًا. بالنسبة للرموز المميزة التي تمثل تسلسلًا محددًا من الأحرف، مثل الرمز المميز PLUSالمطابق للحرف فقط +، فإن الرمز المميز الفعلي غير مطلوب لأنه يمكن التعرف عليه من الرمز المميز.
تعد التعبيرات العادية أكثر كفاءة بكثير من أدوات التحليل المعجمي القائمة على الحالة، والتي يجب كتابتها يدويًا ولا يمكن إعادة استخدامها بشكل عام. مثال على المحلل المعجمي القائم على التعبير العادي هو JLex ، وهو منشئ معجمي للغة Java يستخدم التعبيرات العادية لتحديد قواعد لتقسيم دفق بيانات الإدخال إلى رموز مميزة. مثال آخر هو ليكسان.

التعرف على ليكسان

Lexan هي مكتبة جافا قابلة لإعادة الاستخدام للتحليل المعجمي. يعتمد هذا على تعليمات برمجية من سلسلة منشورات المدونة كتابة محلل بلغة Java على موقع Cogito Learning الإلكتروني . تتكون المكتبة من الفئات التالية، الموجودة في الحزمة ca.javajeff.lexanالمضمنة في الكود القابل للتنزيل لهذه المقالة:
  • Lexan: محلل معجمي.
  • LexanException: تم طرح الاستثناء في منشئ الفصلLexan;
  • LexException: تم طرح الاستثناء إذا تم اكتشاف بناء جملة غير صحيح أثناء التحليل المعجمي؛
  • Token: اسم ذو سمة تعبير عادي؛
  • TokLex: زوج الرمز المميز/الرمز المميز.
يقوم المنشئ Lexan(java.lang.Class tokensClass)بإنشاء محلل معجمي جديد. يتطلب وسيطة واحدة في شكل كائن فئة java.lang.Classيتوافق مع فئة ثابتة من النوع static Token. باستخدام واجهة برمجة تطبيقات Reflection، يقرأ المنشئ جميع الثوابت Tokenفي مصفوفة من القيم Token[]. إذا Tokenلم تكن هناك ثوابت، فسيتم طرح استثناء LexanException. التعبيرات العادية في جافا، الجزء 5 - 2يوفر الفصل Lexanأيضًا الطريقتين التاليتين:
  • تقوم الطريقة بإرجاع قائمة بهذا المعجم؛List getTokLexes() Token
  • Метод void lex(String str)يقوم بإجراء تحليل معجمي لسلسلة الإدخال [مع وضع النتيجة] في قائمة قيم النوع TokLex. إذا تمت مصادفة حرف لا يتطابق مع أي من أنماط الصفيف Token[]، فسيتم طرح استثناء LexException.
لا تحتوي الفئة LexanExceptionعلى أساليب؛ فهي تستخدم طريقة موروثة لإرجاع رسالة استثناء getMessage(). في المقابل، يوفر الفصل LexExceptionالطرق التالية:
  • تقوم الطريقة int getBadCharIndex()بإرجاع موضع الحرف الذي لا يتطابق مع أي من أنماط العلامة.
  • تقوم الطريقة String getText()بإرجاع النص الذي تم تحليله عند إنشاء الاستثناء.
تتجاوز الفئة Tokenالطريقة toString()لإرجاع اسم العلامة. كما يوفر أيضًا طريقة String getPattern()تُرجع سمة التعبير العادي للرمز المميز. توفر الفئة TokLexطريقة Token getToken()تقوم بإرجاع الرمز المميز الخاص بها. كما يوفر أيضًا طريقة String getLexeme()تقوم بإرجاع الرمز المميز الخاص به.

مظاهرة لمكتبة ليكسان

لتوضيح كيفية عمل المكتبة، Lexanقمت بكتابة تطبيق LexanDemo. يتكون من LexanDemoفصول BinTokensو MathTokensو NoTokens. LexanDemoيظهر الكود المصدري للتطبيق في القائمة 2. القائمة 2. عرض توضيحي لمكتبة Lexan أثناء العمل
import ca.javajeff.lexan.Lexan;
import ca.javajeff.lexan.LexanException;
import ca.javajeff.lexan.LexException;
import ca.javajeff.lexan.TokLex;

public final class LexanDemo
{
   public static void main(String[] args)
   {
      lex(MathTokens.class, " sin(x) * (1 + var_12) ");
      lex(BinTokens.class, " 1 0 1 0 1");
      lex(BinTokens.class, "110");
      lex(BinTokens.class, "1 20");
      lex(NoTokens.class, "");
   }

   private static void lex(Class tokensClass, String text)
   {
      try
      {
         Lexan lexan = new Lexan(tokensClass);
         lexan.lex(text);
         for (TokLex tokLex: lexan.getTokLexes())
            System.out.printf("%s: %s%n", tokLex.getToken(),
                              tokLex.getLexeme());
      }
      catch (LexanException le)
      {
         System.err.println(le.getMessage());
      }
      catch (LexException le)
      {
         System.err.println(le.getText());
         for (int i = 0; i < le.getBadCharIndex(); i++)
            System.err.print("-");
         System.err.println("^");
         System.err.println(le.getMessage());
      }
      System.out.println();
   }
}
تستدعي الطريقة main()الموجودة في القائمة 2 أداة مساعدة lex()لتوضيح التحليل المعجمي باستخدام الليكسان. يتم تمرير كل استدعاء لهذه الطريقة فئة الرموز المميزة في الكائن Classوالسلسلة المراد تحليلها. تقوم الطريقة lex()أولاً بإنشاء كائن من الفئة Lexanعن طريق تمرير الكائن Classإلى مُنشئ الفئة Lexan. ثم يقوم باستدعاء أسلوب lex()الفئة Lexanعلى تلك السلسلة. إذا نجح التحليل المعجمي، TokLexفسيتم استدعاء أسلوب getTokLexes()الفصل لإرجاع قائمة الكائنات Lexan. لكل من هذه الكائنات، يتم استدعاء أسلوب فئتها getToken()لإرجاع TokLexالرمز المميز وأسلوب فئتها getLexeme()لإرجاع الرمز المميز. تتم طباعة كلتا القيمتين على الإخراج القياسي. LexanExceptionإذا فشل التحليل المعجمي، فسيتم طرح أحد الاستثناءات أو التعامل معه وفقًا لذلك LexException. للإيجاز، دعونا ننظر فقط في الفئة التي تشكل هذا التطبيق MathTokens. تعرض القائمة 3 كود المصدر الخاص بها. القائمة 3. وصف مجموعة الرموز المميزة للغة رياضية صغيرة
import ca.javajeff.lexan.Token;

public final class MathTokens
{
   public final static Token FUNC = new Token("FUNC", "sin|cos|exp|ln|sqrt");
   public final static Token LPAREN = new Token("LPAREN", "\\(");
   public final static Token RPAREN = new Token("RPAREN", "\\)");
   public final static Token PLUSMIN = new Token("PLUSMIN", "[+-]");
   public final static Token TIMESDIV = new Token("TIMESDIV", "[*/]");
   public final static Token CARET = new Token("CARET", "\\^");
   public final static Token INTEGER = new Token("INTEGER", "[0-9]+");
   public final static Token ID = new Token("ID", "[a-zA-Z][a-zA-Z0-9_]*");
}
توضح القائمة 3 أن الفصل MathTokensيصف سلسلة من الثوابت من النوع Token. يتم تعيين قيمة كائن لكل منهم Token. يتلقى مُنشئ هذا الكائن سلسلة تمثل اسم العلامة، بالإضافة إلى تعبير عادي يصف كافة سلاسل الأحرف المرتبطة بتلك العلامة. من أجل الوضوح، من المرغوب فيه أن يكون اسم سلسلة العلامة هو نفس اسم الثابت، ولكن هذا غير مطلوب. التعبيرات العادية في جافا، الجزء 5 - 3يعد موضع الثابت Tokenفي قائمة العلامات أمرًا مهمًا. الثوابت الموجودة في أعلى القائمة Tokenلها الأسبقية على تلك الموجودة أدناه. على سبيل المثال، عند مواجهة sin، يختار Lexan الرمز المميز FUNCبدلاً من ID. إذا كان الرمز المميز IDقد سبق الرمز المميز FUNC، فسيتم تحديده.

تجميع وتشغيل تطبيق LexanDemo

يشتمل الكود القابل للتنزيل لهذه المقالة على أرشيف lexan.zipيحتوي على كافة ملفات توزيعة Lexan. قم بفك ضغط هذا الأرشيف وانتقل إلى دليل فرعي demosللدليل الجذر lexan. إذا كنت تستخدم نظام التشغيل Windows، فقم بتشغيل الأمر التالي لتجميع ملفات التعليمات البرمجية المصدر للتطبيق التجريبي:
javac -cp ..\library\lexan.jar *.java
إذا نجح التجميع، قم بتشغيل الأمر التالي لتشغيل التطبيق التجريبي:
java -cp ..\library\lexan.jar;. LexanDemo
يجب أن ترى النتائج التالية:
FUNC: sin
LPAREN: (
ID: x
RPAREN: )
TIMESDIV: *
LPAREN: (
INTEGER: 1
PLUSMIN: +
ID: var_12
RPAREN: )
ONE: 1
ZERO: 0
ONE: 1
ZERO: 0
ONE: 1
ONE: 1
ONE: 1
ZERO: 0
1 20
--^
Неожиданный символ во входном тексте: 20
تظهر الرسالة Неожиданный символ во входном тексте: 20نتيجة طرح استثناء LexanExceptionنظرًا لأن الفصل BinTokensلا يعلن عن ثابت Tokenبقيمة 2كتعبير عادي. لاحظ أن معالج الاستثناء يقوم بإخراج موضع الحرف غير المناسب الذي تم الحصول عليه من التحليل المعجمي للنص. إن رسالة الرموز المميزة المفقودة هي نتيجة طرح استثناء LexExceptionلأنه NoTokensلم يتم الإعلان عن أي ثوابت في الفصل Token.

خلف الكواليس

Lexanيستخدم فئة Lexan كمحرك له. ألقِ نظرة على تنفيذ هذه الفئة في القائمة 4 ولاحظ مساهمة التعبيرات النمطية في جعل المحرك قابلاً لإعادة الاستخدام. القائمة 4. إنشاء بنية محلل معجمي يعتمد على التعبيرات العادية
package ca.javajeff.lexan;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;

/**
 *  Лексический анализатор. Этот класс можно использовать для
 *  преобразования входного потока символов в выходной поток маркеров.
 *
 *  @Author Джефф Фризен
 */

public final class Lexan
{
   private List tokLexes;

   private Token[] values;

   /**
    *  Инициализируем лексический анализатор набором an objectов Token.
    *
    *  @параметры tokensClass – an object Class класса, содержащего
    *       набор an objectов Token
    *
    *  @генерирует исключение LexanException в случае невозможности
    *       формирования an object Lexan, возможно, из-за отсутствия an objectов
    *       Token в классе
    */

   public Lexan(Class tokensClass) throws LexanException
   {
      try
      {
         tokLexes = new ArrayList<>();
         List _values = new ArrayList<>();
         Field[] fields = tokensClass.getDeclaredFields();
         for (Field field: fields)
            if (field.getType().getName().equals("ca.javajeff.lexan.Token"))
               _values.add((Token) field.get(null));
         values = _values.toArray(new Token[0]);
         if (values.length == 0)
            throw new LexanException("маркеры отсутствуют");
      }
      catch (IllegalAccessException iae)
      {
         throw new LexanException(iae.getMessage());
      }

   /**
    * Получаем список TokLex'ов этого лексического анализатора.
    *
    *  @возвращает список TokLex'ов
    */

   public List getTokLexes()
   {
      return tokLexes;
   }

   /** * Выполняет лексический анализ входной строки [с помещением * результата] в список TokLex'ов. * * @параметры str – строка, подвергаемая лексическому анализу * * @генерирует исключение LexException: во входных данных обнаружен * неожиданный символ */

   public void lex(String str) throws LexException
   {
      String s = new String(str).trim(); // удалить ведущие пробелы
      int index = (str.length() - s.length());
      tokLexes.clear();
      while (!s.equals(""))
      {
         boolean match = false;
         for (int i = 0; i < values.length; i++)
         {
            Token token = values[i];
            Matcher m = token.getPattern().matcher(s);
            if (m.find())
            {
               match = true;
               tokLexes.add(new TokLex(token, m.group().trim()));
               String t = s;
               s = m.replaceFirst("").trim(); // удалить ведущие пробелы
               index += (t.length() - s.length());
               break;
            }
         }
         if (!match)
            throw new LexException("Неожиданный символ во входном тексте: "
                                    + s, str, index);
      }
   }
}
يعتمد رمز الطريقة lex()على الكود الموجود في منشور المدونة "كتابة محلل في Java: مولد الرمز المميز" على موقع Cogito Learning الإلكتروني. اقرأ هذا المنشور لمعرفة المزيد حول كيفية استخدام Lexan لواجهة برمجة تطبيقات Regex لتجميع التعليمات البرمجية. التعبيرات العادية في جافا، الجزء 5 - 4

خاتمة

تعد التعبيرات العادية أداة مفيدة يمكن أن تكون مفيدة لأي مطور. تعمل واجهة Regex API الخاصة بلغة برمجة Java على تسهيل استخدامها في التطبيقات والمكتبات. الآن بعد أن أصبح لديك فهم أساسي للتعبيرات العادية وواجهة برمجة التطبيقات (API) هذه، يمكنك إلقاء نظرة على وثائق SDK java.util.regexلمعرفة المزيد حول التعبيرات العادية وطرق Regex API الإضافية.
تعليقات
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION