JavaRush /בלוג Java /Random-HE /ביטויים רגולריים ב-Java, חלק 5

ביטויים רגולריים ב-Java, חלק 5

פורסם בקבוצה
אנו מציגים לתשומת לבכם תרגום של מדריך קצר לביטויים רגולריים בג'אווה, שנכתב על ידי ג'ף פריזן עבור אתר javaworld. כדי להקל על הקריאה, חילקנו את המאמר למספר חלקים. החלק הזה הוא האחרון. ביטויים רגולריים ב-Java, חלק 5 - 1ביטויים רגילים ב-Java, חלק 1 ביטויים רגילים ב-Java, חלק 2 ביטויים רגילים ב-Java, חלק 3 ביטויים רגילים ב-Java, חלק 4

שימוש בביטויים רגולריים לניתוח מילוני

יישום שימושי עוד יותר של ביטויים רגולריים הוא ספרייה של קוד לשימוש חוזר לביצוע ניתוח מילוני, מרכיב מפתח בכל מהדר או אסמבלר. במקרה זה, זרם הקלט של התווים מקובץ לזרם פלט של אסימונים - שמות לרצפים של תווים בעלי משמעות משותפת. לדוגמה, לאחר שנתקל ברצף התווים c, o, u, n, t, e, r, בזרם הקלט, המנתח המילוני יכול להוציא אסימון ID(מזהה). רצף התווים התואם לאסימון נקרא לקסמה.
עוד על סמנים ולקסמות
אסימונים כגון ID יכולים להתאים לרצפי תווים רבים. במקרה של אסימונים כאלה, האסימון האמיתי המתאים לאסימון נחוץ גם על ידי המהדר, האסמבלר או כלי עזר אחר הדורשים ניתוח מילוני. עבור אסימונים המייצגים רצף ספציפי אחד של תווים, כגון האסימון PLUSהמתאים רק לתו +, האסימון בפועל אינו נדרש מכיוון שניתן לזהות אותו מהאסימון.
ביטויים רגולריים הם הרבה יותר יעילים מאשר מנתחים מילוניים המבוססים על מצב, אשר חייבים להיכתב ביד ובדרך כלל לא ניתן לעשות בהם שימוש חוזר. דוגמה לניתוח מילוני מבוסס ביטויים רגולריים הוא JLex , מחולל מילוני לשפת Java שמשתמש בביטויים רגולריים כדי להגדיר כללים לפירוק זרם נתוני קלט לאסימונים. דוגמה נוספת היא לקסאן.

היכרות עם לקסאן

Lexan היא ספריית Java הניתנת לשימוש חוזר לניתוח מילוני. הוא מבוסס על קוד מסדרת הפוסטים בבלוג Writing a Parser ב-Java באתר Cogito Learning . הספרייה מורכבת מהשיעורים הבאים, הנמצאים בחבילה ca.javajeff.lexanהכלולה בקוד הניתן להורדה עבור מאמר זה:
  • Lexan: מנתח מילוני;
  • LexanException: חריג נזרק בבנאי המחלקהLexan;
  • LexException: חריג נזרק אם התחביר שגוי מזוהה במהלך ניתוח מילוני;
  • Token: שם עם תכונת ביטוי רגולרי;
  • TokLex: זוג אסימון/אסימון.
הבנאי Lexan(java.lang.Class tokensClass)יוצר מנתח מילוני חדש. זה דורש ארגומנט אחד בצורה של אובייקט מחלקה java.lang.Classהמתאים למחלקה הקבועה מסוג static Token. באמצעות ה-Reflection API, הבנאי קורא את כל הקבועים Tokenלמערך של ערכים Token[]. אם Tokenאין קבועים, נזרק חריג LexanException. ביטויים רגולריים ב-Java, חלק 5 - 2הכיתה Lexanמספקת גם את שתי השיטות הבאות:
  • השיטה מחזירה רשימה של lexer זה;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()כדי להדגים ניתוח מילוני באמצעות Lexan. כל קריאה לשיטה זו עוברת את המחלקה של האסימונים באובייקט Classוהמחרוזת לניתוח. השיטה lex()יוצרת תחילה אובייקט של המחלקה Lexanעל ידי העברת האובייקט Classלבנאי המחלקה Lexan. ואז זה קורא לשיטת lex()המחלקה Lexanעל המחרוזת הזו. אם הניתוח המילוני מצליח, שיטת המחלקה TokLexנקראת כדי להחזיר רשימה של אובייקטים . עבור כל אחד מהאובייקטים הללו, שיטת המחלקה שלו נקראת להחזיר את האסימון ושיטת המחלקה שלו להחזיר את האסימון. שני הערכים מודפסים לפלט סטנדרטי. אם הניתוח המילוני נכשל, אחד החריגים או נזרק ומטופל בהתאם . לקיצור, ניקח בחשבון רק את המחלקה המרכיבה את היישום הזה . רישום 3 מציג את קוד המקור שלו. רישום 3. תיאור של סט אסימונים לשפה מתמטית קטנהgetTokLexes()LexangetToken()TokLexgetLexeme()LexanExceptionLexExceptionMathTokens
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. הבנאי של אובייקט זה מקבל מחרוזת שהיא שם הסמן, יחד עם ביטוי רגולרי שמתאר את כל מחרוזות התווים המשויכות לאותו סמן. למען הבהירות, רצוי ששם המחרוזת של הסמן יהיה זהה לשם הקבוע, אך אין צורך בכך. ביטויים רגולריים ב-Java, חלק 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: A Token Generator" באתר Cogito Learning. קרא את הפוסט הזה כדי ללמוד עוד על האופן שבו Lexan משתמשת ב-Regex API כדי להדר קוד. ביטויים רגולריים ב-Java, חלק 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