1. مقدمة
Expr هو حلاً لتكوين ديناميكي مصمم للغة Go، معروفة بتركيبتها البسيطة وميزات أداءها القوية. يتمحور نواة محرك التعبير Expr حول السلامة والسرعة والبديهية، مما يجعله مناسبًا للسيناريوهات مثل التحكم في الوصول وتصفية البيانات وإدارة الموارد. عند تطبيقه على Go، يعزز Expr بشكل كبير قدرة التطبيقات على التعامل مع القواعد الديناميكية. وعلى عكس المترجمات أو محركات النصوص في لغات أخرى، يتبنى Expr فحص النوع الثابت وإنشاء بايت كود للتنفيذ، مما يضمن الأداء والأمان معًا.
2. تثبيت Expr
يمكنك تثبيت محرك التعبير Expr باستخدام أداة إدارة حزم لغة Go وهي go get
:
go get github.com/expr-lang/expr
سيقوم هذا الأمر بتحميل ملفات مكتبة Expr وتثبيتها في مشروعك بلغة Go، مما يتيح لك استيراد Expr واستخدامه في كود Go الخاص بك.
3. البدء السريع
3.1 تجميع وتشغيل التعابير الأساسية
لنبدأ بمثال بسيط: كتابة تعبير بسيط، تجميعه، ثم تشغيله للحصول على النتيجة.
package main
import (
"fmt"
"github.com/expr-lang/expr"
)
func main() {
// جمع تعبير الإضافة الأساسي
program, err := expr.Compile(`2 + 2`)
if err != nil {
panic(err)
}
// تشغيل التعبير المجمع بدون تمرير بيئة، حيث لا تحتاج إلى متغيرات هنا
output, err := expr.Run(program, nil)
if err != nil {
panic(err)
}
// طباعة النتيجة
fmt.Println(output) // يخرج 4
}
في هذا المثال، يتم تجميع التعبير 2 + 2
إلى بايت كود قابل للتنفيذ، الذي يتم بعد ذلك تشغيله لإنتاج الناتج.
3.2 استخدام تعابير المتغيرات
بعد ذلك، سنقوم بإنشاء بيئة تحتوي على متغيرات، كتابة تعبير يستخدم هذه المتغيرات، تجميع وتشغيل هذا التعبير.
package main
import (
"fmt"
"github.com/expr-lang/expr"
)
func main() {
// إنشاء بيئة تحتوي على متغيرات
env := map[string]interface{}{
"foo": 100,
"bar": 200,
}
// جمع تعبير يستخدم المتغيرات من البيئة
program, err := expr.Compile(`foo + bar`, expr.Env(env))
if err != nil {
panic(err)
}
// تشغيل التعبير
output, err := expr.Run(program, env)
if err != nil {
panic(err)
}
// طباعة النتيجة
fmt.Println(output) // يخرج 300
}
في هذا المثال، تحتوي البيئة env
على المتغيرات foo
و bar
. التعبير foo + bar
يستنتج أنواع foo
و bar
من البيئة أثناء التجميع، ويستخدم قيم هذه المتغيرات أثناء التشغيل لتقييم نتيجة التعبير.
4. جملة Expr بالتفصيل
4.1 المتغيرات والنصوص الثابتة
يمكن لمحرك تعبير Expr التعامل مع النصوص الثابتة من أنواع البيانات الشائعة، بما في ذلك الأرقام والسلاسل وقيم البوليان. النصوص الثابتة هي قيم البيانات المكتوبة مباشرة في الكود، مثل 42
، "hello"
، و true
.
الأرقام
في Expr، يمكنك كتابة أعداد صحيحة مباشرة وأعداد عشرية:
42 // يمثل العدد الصحيح 42
3.14 // يمثل العدد عشري 3.14
السلاسل
تحتوي السلاسل الثابتة على علامات اقتباس مزدوجة "
أو علامات قفل قيود ``. على سبيل المثال:
"مرحبًا، العالم" // سلسلة محاطة بعلامات اقتباس مزدوجة، تدعم الحروف المهربة
`مرحبًا، العالم` // سلسلة محاطة بعلامات قفل قيود، تحتفظ بصيغة السلسلة دون دعم الحروف المهربة
قيم البوليان
هناك قيمتان بوليانيتان فقط، true
و false
، تمثلان الصحيح المنطقي صحيح وخاطئ:
true // قيمة بوليانية صحيحة
false // قيمة بوليانية خاطئة
المتغيرات
يسمح Expr أيضًا بتعريف المتغيرات في البيئة، ثم الإشارة إلى هذه المتغيرات في التعبير. على سبيل المثال:
env := map[string]interface{}{
"age": 25,
"name": "Alice",
}
ثم يمكنك الإشارة في التعبير إلى age
و name
:
age > 18 // التحقق مما إذا كان العمر أكبر من 18
name == "Alice" // تحديد ما إذا كان الاسم مساوٍ لـ "Alice"
4.2 العمليات
يدعم محرك تعبير Expr مجموعة متنوعة من العمليات، بما في ذلك العمليات الحسابية والعمليات المنطقية وعمليات القياس وعمليات المجموعات، وغيرها.
العوامل الحسابية والمنطقية
تتضمن العوامل الحسابية عمليات الجمع (+
), الطرح (-
), الضرب (*
), القسمة (/
), والباقي (%
). تتضمن العوامل اللوجيكية عوامل الواقع و (&&
), الأو (||
), والنفي (!
), مثلاً:
2 + 2 // النتيجة تساوي 4
7 % 3 // النتيجة تساوي 1
!true // النتيجة تساوي false
age >= 18 && name == "Alice" // تحقق ما إذا كان العمر ليس أقل من 18 وإذا كان الاسم مساوٍ لـ "Alice"
العوامل المقارنة
تتضمن العوامل المقارنة الساوي (==
), الغير مساوي (!=
), أقل من (<
), أقل من أو يساوي (<=
), أكبر من (>
), وأكبر من أو يساوي (>=
), تُستخدم للمقارنة بين قيمتين:
age == 25 // تحقق ما إذا كان العمر مساوٍ لـ 25
age != 18 // تحقق ما إذا كان العمر غير مساوٍ لـ 18
age > 20 // تحقق ما إذا كان العمر أكبر من 20
عوامل التعيين
توفر Expr أيضًا بعض العوامل للعمل مع المجموعات، مثل in
للتحقق مما إذا كان العنصر موجود في المجموعة. يمكن أن تكون المجموعات هي مصفوفات، أو قوائم، أو خرائط:
"user" in ["user", "admin"] // صحيح، لأن "user" موجود في المصفوفة
3 in {1: true, 2: false} // خطأ، لأن 3 ليس مفتاحًا في الخريطة
هناك أيضًا بعض وظائف العمليات المتقدمة للمجموعات، مثل all
, any
, one
, و none
, التي تتطلب استخدام الدوال المجهولة (لامبدا):
all(tweets, {.Len <= 240}) // تحقق ما إذا كانت حقل Len في جميع التغريدات لا يتجاوز 240
any(tweets, {.Len > 200}) // تحقق ما إذا كان هناك حقل Len في التغريدات يتجاوز 200
عامل العضو
في لغة التعبير Expr، يسمح عامل العضو لنا بالوصول إلى خصائص struct
في لغة Go. تمكننا هذه الميزة من التعامل المباشر مع هياكل البيانات المعقدة، مما يجعلها مرنة وعملية للغاية.
استخدام عامل العضو بسيط جدًا، فقط استخدم العامل .
متبوعًا باسم الخاصية. على سبيل المثال، إذا كان لدينا الهيكل التالي:
type User struct {
Name string
Age int
}
يمكنك كتابة تعبير للوصول إلى خاصية Name
في هيكل الـ User
كالتالي:
env := map[string]interface{}{
"user": User{Name: "Alice", Age: 25},
}
code := `user.Name`
program, err := expr.Compile(code, expr.Env(env))
if err != nil {
panic(err)
}
output, err := expr.Run(program, env)
if err != nil {
panic(err)
}
fmt.Println(output) // النتيجة: Alice
التعامل مع قيم الـ nil
عند الوصول إلى الخصائص، قد تواجه حالات حيث يكون الكائن nil
. يوفر Expr وصولًا آمنًا للخصائص، بحيث حتى إذا كان الهيكل أو الخاصية المتداخلة nil
، فإنه لن يُلقي خطأ تشغيليًا.
استخدم العامل ?.
للإشارة إلى الخصائص. إذا كان الكائن nil
، فسيعيد nil بدلاً من رمي خطأ.
author.User?.Name
التعبير المكافئ
author.User != nil ? author.User.Name : nil
استخدام العامل ??
هو في المقام الأول لإرجاع قيم افتراضية:
author.User?.Name ?? "مجهول"
التعبير المكافئ
author.User != nil ? author.User.Name : "مجهول"
عامل الأنبوب
يُستخدم عامل الأنبوب (|
) في Expr لتمرير نتيجة تعبير إلى تعبير آخر كمعلمة. هذا يشبه عملية الأنبوب في طرف Unix، حيث يُسمح بربط عدة وحدات وظيفية معًا لتشكيل خط أنابيب معالجة. في Expr، استخدامه يمكن أن يخلق تعابير أكثر وضوحًا وإيجازًا.
على سبيل المثال، إذا كان لدينا وظيفة للحصول على اسم مستخدم وقالب لرسالة ترحيب:
env := map[string]interface{}{
"user": User{Name: "Bob", Age: 30},
"get_name": func(u User) string { return u.Name },
"greet_msg": "Hello, %s!",
}
code := `get_name(user) | sprintf(greet_msg)`
program, err := expr.Compile(code, expr.Env(env))
if err != nil {
panic(err)
}
output, err := expr.Run(program, env)
if err != nil {
panic(err)
}
fmt.Println(output) // الإخراج: Hello, Bob!
في هذا المثال، نحصل أولاً على اسم المستخدم من خلال get_name(user)
، ثم نمرر الاسم إلى الدالة sprintf
باستخدام عامل الأنبوب |
لإنشاء رسالة الترحيب النهائية.
يمكن لعامل الأنبوب تجزئة الكود الخاص بنا، وتحسين إعادة استخدام الكود، وجعل التعابير أكثر قراءةً.
4.3 الوظائف
يدعم Expr الوظائف الجاهزة والوظائف المخصصة، مما يجعل التعابير أكثر قوة ومرونة.
استخدام الوظائف الجاهزة
يمكن استخدام الوظائف الجاهزة مثل len
، all
، none
، any
، إلخ مباشرة في التعبير.
// مثال على استخدام وظيفة جاهزة
program, err := expr.Compile(`all(users, {.Age >= 18})`, expr.Env(env))
if err != nil {
panic(err)
}
// ملاحظة: هنا يجب أن يحتوي البيئة على متغير users، ويجب أن يحتوي كل مستخدم على خاصية العمر
output, err := expr.Run(program, env)
fmt.Print(output) // إذا كان جميع المستخدمين في البيئة يبلغون من العمر 18 عامًا أو أكثر، ستُرجع قيمة صحيحة
كيفية تعريف واستخدام الوظائف المخصصة
في Expr، يمكنك إنشاء وظائف مخصصة عن طريق تمرير تعريفات الدالة في تعيين البيئة.
// مثال على وظيفة مخصصة
env := map[string]interface{}{
"greet": func(name string) string {
return fmt.Sprintf("Hello, %s!", name)
},
}
program, err := expr.Compile(`greet("World")`, expr.Env(env))
if err != nil {
panic(err)
}
output, err := expr.Run(program, env)
fmt.Print(output) // ترجع: Hello, World!
عند استخدام الوظائف في Expr، يمكنك تقسيم الكود الخاص بك ودمج المنطق المعقد في التعابير. من خلال دمج المتغيرات والعمليات والوظائف، يصبح Expr أداة قوية وسهلة الاستخدام. تذكر دائمًا ضمان سلامة النوع عند بناء بيئة Expr وتشغيل التعابير.
5. وثائق وظائف المدمجة
يوفر محرك التعبير Expr مجموعة غنية من الوظائف المدمجة للمطورين للتعامل مع سيناريوهات معقدة مختلفة. فيما يلي، سنوضح هذه الوظائف المدمجة واستخداماتها.
all
يمكن استخدام وظيفة all
للتحقق مما إذا كانت جميع العناصر في مجموعة تفي بشرط معين. تأخذ معها معاملين: المجموعة وتعبير الشرط.
// التحقق مما إذا كانت جميع التغريدات تحتوي على محتوى أقل من 240 حرفًا
code := `all(tweets, len(.Content) < 240)`
any
شبيهة بـ all
، تستخدم وظيفة any
للتحقق مما إذا كانت أي عنصر في مجموعة يفي بشرط ما.
// التحقق مما إذا كانت أي تغريدة تحتوي على محتوى أكثر من 240 حرفًا
code := `any(tweets, len(.Content) > 240)`
none
تُستخدم وظيفة none
للتحقق مما إذا كان أي عنصر في مجموعة لا يفي بشرط معين.
// التأكد من عدم تكرار أي تغريدة
code := `none(tweets, .IsRepeated)`
one
يتم استخدام وظيفة one
للتأكد من أن عنصرًا واحدًا فقط في مجموعة يفي بشرط ما.
// التحقق مما إذا كانت تغريدة واحدة فقط تحتوي على كلمة محددة
code := `one(tweets, contains(.Content, "keyword"))`
filter
تستخدم وظيفة filter
لفلترة عناصر المجموعة التي تفي بشرط معين.
// تصفية جميع التغريدات المراقبة بوصف الأولوية
code := `filter(tweets, .IsPriority)`
map
تستخدم وظيفة map
لتحويل العناصر في مجموعة وفقًا لطريقة محددة.
// تهيئة وقت نشر كل التغريدات
code := `map(tweets, {.PublishTime: Format(.Date)})`
len
تستخدم وظيفة len
لإرجاع طول مجموعة أو سلسلة.
// الحصول على طول اسم المستخدم
code := `len(username)`
contains
تُستخدم وظيفة contains
للتحقق مما إذا كانت سلسلة تحتوي على متغير نصي فرعي أو إذا كانت المجموعة تحتوي عنصر محدد.
// التحقق مما إذا كان اسم المستخدم يحتوي على أحرف غير قانونية
code := `contains(username, "illegal characters")`
ما تم ذكره أعلاه هو فقط جزء من الوظائف الأساسية التي يوفرها مُحرك التعبيرات Expr المدمجة. باستخدام هذه الوظائف القوية، يمكنك التعامل مع البيانات والمنطق بمرونة وكفاءة أكبر. للحصول على قائمة أكثر تفصيلاً للوظائف وتعليمات الاستخدام، يُرجى الرجوع إلى مستندات Expr الرسمية.