DSL على TypeScript: إنشاء لغة برمجة خاصة بك في أمسية

مدرب بايثون أونلاين للمبتدئين

تعلم Python بسهولة دون إرهاق نظري. حل مهام عملية مع التحقق التلقائي واكتب الكود مباشرة في المتصفح.

ابدأ الدورة

لماذا يستحق مشروعك لغته الخاصة؟

\n\n

تخيل: أنت تكتب كودًا لحساب أقساط التأمين، وبدلاً من عشرات if-else المتداخلة والأرقام السحرية، ترى بنية موجزة كهذه:

\n\n
const rule = new InsuranceRule()\n  .when(age > 25)\n  .and(experience > 2)\n  .then(premium = base * 0.8);
\n\n

هذا ليس سحرًا. هذا DSL (Domain Specific Language) — لغة مخصصة للمجال، مدمجة في TypeScript. في هذه المقالة، سنشرح كيفية إنشاء مثل هذه اللغات، بحيث يتحدث كودك لغة الأعمال، وليس لغة الآلات.

\n\n

ما هو DSL ولماذا هو مطلوب؟

\n\n

DSL هي لغة تم إنشاؤها لحل مشاكل مجال معين واحد. على عكس لغات الأغراض العامة (Java, C#, TypeScript)، لا تحاول DSL أن تكون عالمية. إنها تضحي بالمرونة من أجل التعبيرية.

\n\n

متى يجب استخدام DSL:

\n
    \n
  • يتغير منطق الأعمال بشكل متكرر ويحتاج إلى أن يتم تعديله بواسطة المحللين
  • \n
  • يوجد في المشروع العديد من أنماط التكوين المتكررة
  • \n
  • تحتاج إلى عزل الخوارزميات المعقدة عن بقية الكود
  • \n
  • مطلوب التحقق من صحة البيانات على مستوى القواعد النحوية
  • \n
\n\n

TypeScript لإنشاء DSL هو الخيار المثالي. الكتابة الثابتة، نظام الأنواع القوي، وإمكانية الترجمة إلى JavaScript تجعله أداة مرنة لبناء كل من اللغات الداخلية والخارجية.

\n\n

DSL الداخلي: fluent API وسلاسل الاستدعاءات

\n\n

أسهل طريقة لإنشاء DSL في TypeScript هي استخدام Fluent Interface (الواجهة الانسيابية). هذا عندما تقوم كل طريقة بإرجاع الكائن نفسه، مما يسمح ببناء سلاسل من الاستدعاءات.

\n\n

مثال: منشئ استعلامات SQL

\n\n
class QueryBuilder {\n  private table: string = '';\n  private conditions: string[] = [];\n  private selectedFields: string[] = ['*'];\n\n  select(...fields: string[]): this {\n    this.selectedFields = fields.length ? fields : ['*'];\n    return this;\n  }\n\n  from(table: string): this {\n    this.table = table;\n    return this;\n  }\n\n  where(condition: string): this {\n    this.conditions.push(condition);\n    return this;\n  }\n\n  and(condition: string): this {\n    return this.where(condition);\n  }\n\n  build(): string {\n    let sql = `SELECT ${this.selectedFields.join(', ')} FROM ${this.table}`;\n    if (this.conditions.length) {\n      sql += ` WHERE ${this.conditions.join(' AND ')}`;\n    }\n    return sql;\n  }\n}\n\n// الاستخدام\nconst query = new QueryBuilder()\n  .select('name', 'age')\n  .from('users')\n  .where('age > 18')\n  .and('status = "active"')\n  .build();\n\nconsole.log(query);\n// SELECT name, age FROM users WHERE age > 18 AND status = "active"
\n\n

هذا هو DSL الداخلي الكلاسيكي على TypeScript. لا يتطلب محللاً منفصلاً ويعمل داخل كود TypeScript. الميزة الرئيسية هي الدعم الكامل للإكمال التلقائي والكتابة.

\n\n

التعقيد: DSL مكتوب بقوة لقواعد الأعمال

\n\n

دعنا نضيف كتابة صارمة باستخدام الأدوية (generics) وأنواع الاتحاد (union types):

\n\n
type ComparisonOperator = '>' | '<' | '=' | '!=';\n\nclass RuleBuilder<T extends Record<string, any>> {\n  private conditions: ((data: T) => boolean)[] = [];\n\n  when<K extends keyof T>(field: K, op: ComparisonOperator, value: T[K]): this {\n    this.conditions.push((data: T) => {\n      switch (op) {\n        case '>': return data[field] > value;\n        case '<': return data[field] < value;\n        case '=': return data[field] === value;\n        case '!=': return data[field] !== value;\n      }\n    });\n    return this;\n  }\n\n  and<K extends keyof T>(field: K, op: ComparisonOperator, value: T[K]): this {\n    return this.when(field, op, value);\n  }\n\n  evaluate(data: T): boolean {\n    return this.conditions.every(c => c(data));\n  }\n}\n\n// نوع البيانات للتحقق\ninterface UserData                

المدونات

توصيات الكتب