نقدم انتباهكم إلى ترجمة دليل قصير للتعبيرات العادية في Java، كتبه Jeff Friesen لموقع javaworld. ولتسهيل القراءة قمنا بتقسيم المقال إلى عدة أجزاء. هذا الجزء هو الجزء الأخير. التعبيرات العادية في جافا، الجزء 1 التعبيرات العادية في جافا، الجزء 2 التعبيرات العادية في جافا، الجزء 3 التعبيرات العادية في جافا، الجزء 4
تعد التعبيرات العادية أكثر كفاءة بكثير من أدوات التحليل المعجمي القائمة على الحالة، والتي يجب كتابتها يدويًا ولا يمكن إعادة استخدامها بشكل عام. مثال على المحلل المعجمي القائم على التعبير العادي هو JLex ، وهو منشئ معجمي للغة Java يستخدم التعبيرات العادية لتحديد قواعد لتقسيم دفق بيانات الإدخال إلى رموز مميزة. مثال آخر هو ليكسان.
استخدام التعابير العادية للتحليل المعجمي
التطبيق الأكثر فائدة للتعبيرات العادية هو مكتبة التعليمات البرمجية القابلة لإعادة الاستخدام لإجراء التحليل المعجمي، وهو مكون رئيسي لأي مترجم أو مجمع. في هذه الحالة، يتم تجميع دفق الإدخال من الأحرف في دفق إخراج من الرموز المميزة - أسماء تسلسلات من الأحرف التي لها معنى مشترك. على سبيل المثال، بعد مواجهة تسلسل الأحرفc
، o
و u
، n
و، t
و e
، و r
، في دفق الإدخال، يمكن للمحلل المعجمي إخراج رمز مميز ID
(معرف). يُطلق على تسلسل الأحرف المقابلة للرمز المميز اسم المعجم.
المزيد عن العلامات والمعاجم |
---|
يمكن أن تتطابق الرموز المميزة مثل المعرف مع العديد من تسلسلات الأحرف. في حالة مثل هذه الرموز المميزة، فإن الرمز المميز الفعلي المطابق للرمز المميز مطلوب أيضًا بواسطة المترجم أو المجمّع أو أي أداة مساعدة أخرى تتطلب تحليلًا معجميًا. بالنسبة للرموز المميزة التي تمثل تسلسلًا محددًا من الأحرف، مثل الرمز المميز PLUS المطابق للحرف فقط + ، فإن الرمز المميز الفعلي غير مطلوب لأنه يمكن التعرف عليه من الرمز المميز. |
التعرف على ليكسان
Lexan هي مكتبة جافا قابلة لإعادة الاستخدام للتحليل المعجمي. يعتمد هذا على تعليمات برمجية من سلسلة منشورات المدونة كتابة محلل بلغة Java على موقع Cogito Learning الإلكتروني . تتكون المكتبة من الفئات التالية، الموجودة في الحزمةca.javajeff.lexan
المضمنة في الكود القابل للتنزيل لهذه المقالة:
Lexan
: محلل معجمي.LexException
: تم طرح الاستثناء إذا تم اكتشاف بناء جملة غير صحيح أثناء التحليل المعجمي؛Token
: اسم ذو سمة تعبير عادي؛TokLex
: زوج الرمز المميز/الرمز المميز.
LexanException
: تم طرح الاستثناء في منشئ الفصلLexan;
Lexan(java.lang.Class tokensClass)
بإنشاء محلل معجمي جديد. يتطلب وسيطة واحدة في شكل كائن فئة java.lang.Class
يتوافق مع فئة ثابتة من النوع static Token
. باستخدام واجهة برمجة تطبيقات Reflection، يقرأ المنشئ جميع الثوابت Token
في مصفوفة من القيم Token[]
. إذا Token
لم تكن هناك ثوابت، فسيتم طرح استثناء LexanException
. يوفر الفصل 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
. يتلقى مُنشئ هذا الكائن سلسلة تمثل اسم العلامة، بالإضافة إلى تعبير عادي يصف كافة سلاسل الأحرف المرتبطة بتلك العلامة. من أجل الوضوح، من المرغوب فيه أن يكون اسم سلسلة العلامة هو نفس اسم الثابت، ولكن هذا غير مطلوب. يعد موضع الثابت 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 لتجميع التعليمات البرمجية.
خاتمة
تعد التعبيرات العادية أداة مفيدة يمكن أن تكون مفيدة لأي مطور. تعمل واجهة Regex API الخاصة بلغة برمجة Java على تسهيل استخدامها في التطبيقات والمكتبات. الآن بعد أن أصبح لديك فهم أساسي للتعبيرات العادية وواجهة برمجة التطبيقات (API) هذه، يمكنك إلقاء نظرة على وثائق SDKjava.util.regex
لمعرفة المزيد حول التعبيرات العادية وطرق Regex API الإضافية.
GO TO FULL VERSION