אנו מציגים לתשומת לבכם תרגום של מדריך קצר לביטויים רגולריים בג'אווה, שנכתב על ידי ג'ף פריזן עבור אתר javaworld. כדי להקל על הקריאה, חילקנו את המאמר למספר חלקים. החלק הזה הוא האחרון. ביטויים רגילים ב-Java, חלק 1 ביטויים רגילים ב-Java, חלק 2 ביטויים רגילים ב-Java, חלק 3 ביטויים רגילים ב-Java, חלק 4
ביטויים רגולריים הם הרבה יותר יעילים מאשר מנתחים מילוניים המבוססים על מצב, אשר חייבים להיכתב ביד ובדרך כלל לא ניתן לעשות בהם שימוש חוזר. דוגמה לניתוח מילוני מבוסס ביטויים רגולריים הוא JLex , מחולל מילוני לשפת Java שמשתמש בביטויים רגולריים כדי להגדיר כללים לפירוק זרם נתוני קלט לאסימונים. דוגמה נוספת היא לקסאן.
שימוש בביטויים רגולריים לניתוח מילוני
יישום שימושי עוד יותר של ביטויים רגולריים הוא ספרייה של קוד לשימוש חוזר לביצוע ניתוח מילוני, מרכיב מפתח בכל מהדר או אסמבלר. במקרה זה, זרם הקלט של התווים מקובץ לזרם פלט של אסימונים - שמות לרצפים של תווים בעלי משמעות משותפת. לדוגמה, לאחר שנתקל ברצף התוויםc
, o
, u
, n
, t
, e
, r
, בזרם הקלט, המנתח המילוני יכול להוציא אסימון ID
(מזהה). רצף התווים התואם לאסימון נקרא לקסמה.
עוד על סמנים ולקסמות |
---|
אסימונים כגון ID יכולים להתאים לרצפי תווים רבים. במקרה של אסימונים כאלה, האסימון האמיתי המתאים לאסימון נחוץ גם על ידי המהדר, האסמבלר או כלי עזר אחר הדורשים ניתוח מילוני. עבור אסימונים המייצגים רצף ספציפי אחד של תווים, כגון האסימון PLUS המתאים רק לתו + , האסימון בפועל אינו נדרש מכיוון שניתן לזהות אותו מהאסימון. |
היכרות עם לקסאן
Lexan היא ספריית Java הניתנת לשימוש חוזר לניתוח מילוני. הוא מבוסס על קוד מסדרת הפוסטים בבלוג Writing a Parser ב-Java באתר Cogito Learning . הספרייה מורכבת מהשיעורים הבאים, הנמצאים בחבילהca.javajeff.lexan
הכלולה בקוד הניתן להורדה עבור מאמר זה:
Lexan
: מנתח מילוני;LexException
: חריג נזרק אם התחביר שגוי מזוהה במהלך ניתוח מילוני;Token
: שם עם תכונת ביטוי רגולרי;TokLex
: זוג אסימון/אסימון.
LexanException
: חריג נזרק בבנאי המחלקהLexan;
Lexan(java.lang.Class tokensClass)
יוצר מנתח מילוני חדש. זה דורש ארגומנט אחד בצורה של אובייקט מחלקה java.lang.Class
המתאים למחלקה הקבועה מסוג static Token
. באמצעות ה-Reflection API, הבנאי קורא את כל הקבועים Token
למערך של ערכים Token[]
. אם Token
אין קבועים, נזרק חריג LexanException
. הכיתה 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()
Lexan
getToken()
TokLex
getLexeme()
LexanException
LexException
MathTokens
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
. הבנאי של אובייקט זה מקבל מחרוזת שהיא שם הסמן, יחד עם ביטוי רגולרי שמתאר את כל מחרוזות התווים המשויכות לאותו סמן. למען הבהירות, רצוי ששם המחרוזת של הסמן יהיה זהה לשם הקבוע, אך אין צורך בכך. מיקום הקבוע 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 כדי להדר קוד.
סיכום
ביטויים רגולריים הם כלי שימושי שיכול להיות שימושי לכל מפתח. ה-Regex API של שפת התכנות Java הופך אותם לקלים לשימוש ביישומים וספריות. כעת, לאחר שכבר יש לך הבנה בסיסית של ביטויים רגולריים ושל ממשק API זה, עיין בתיעוד ה-SDKjava.util.regex
כדי ללמוד עוד יותר על ביטויים רגולריים ושיטות נוספות של Regex API.
GO TO FULL VERSION