إنشاء تصميمات بناء جملة جافا سكريبت مخصصة باستخدام بابل. الجزء 2

اليوم ، ننشر الجزء الثاني من ترجمة ملحق بناء جملة JavaScript باستخدام Babel.



الجزء الأول المذهل

كيف يعمل تحليل


يتلقى المحلل اللغوي قائمة الرموز المميزة من نظام رمز الرمز المميز ، وبالنظر إلى الرموز المميزة واحدة تلو الأخرى ، يقوم بإنشاء AST. من أجل اتخاذ قرار بشأن كيفية استخدام الرموز ، وفهم الرمز المميز الذي يمكن توقعه بعد ذلك ، يشير المحلل اللغوي إلى تحديد قواعد اللغة.

تبدو مواصفات القواعد النحو التالي:

... ExponentiationExpression -> UnaryExpression                             UpdateExpression ** ExponentiationExpression MultiplicativeExpression -> ExponentiationExpression                             MultiplicativeExpression ("*" or "/" or "%") ExponentiationExpression AdditiveExpression    -> MultiplicativeExpression                             AdditiveExpression + MultiplicativeExpression                             AdditiveExpression - MultiplicativeExpression ... 

وهو يصف أولوية تنفيذ التعبيرات أو العبارات. على سبيل المثال ، يمكن أن يمثل تعبير AdditiveExpression أحد AdditiveExpression التالية:

  • التعبير MultiplicativeExpression التعبير.
  • تعبير AdditiveExpression ، متبوعًا بعامل + token ، متبوعًا بتعبير MultiplicativeExpression .
  • تعبير AdditiveExpression ، متبوعًا برمز " - " ، متبوعًا بتعبير MultiplicativeExpression .

نتيجةً لذلك ، إذا كان لدينا التعبير 1 + 2 * 3 ، فسيبدو كما يلي:

 (AdditiveExpression "+" 1 (MultiplicativeExpression "*" 2 3)) 

لكنها لن تكون كذلك:

 (MultiplicativeExpression "*" (AdditiveExpression "+" 1 2) 3) 

يتم تحويل البرنامج ، باستخدام هذه القواعد ، إلى رمز صادر عن المحلل اللغوي:

 class Parser {  // ...  parseAdditiveExpression() {    const left = this.parseMultiplicativeExpression();    //    -  `+`  `-`    if (this.match(tt.plus) || this.match(tt.minus)) {      const operator = this.state.type;      //          this.nextToken();      const right = this.parseMultiplicativeExpression();      //        this.finishNode(        {          operator,          left,          right,        },        'BinaryExpression'      );    } else {      //  MultiplicativeExpression      return left;    }  } } 

يرجى ملاحظة أن هنا نسخة مبسطة للغاية لما هو موجود بالفعل في بابل. ولكني آمل أن تسمح لنا هذه الشفرة بتوضيح جوهر ما يحدث.

كما ترون ، المحلل هو ، بطبيعته ، عودي. ينتقل من التصميمات ذات الأولوية الأدنى إلى التصميمات ذات الأولوية العليا. على سبيل المثال ، يستدعي parseMultiplicativeExpression ، parseMultiplicativeExpression هذا البناء parseExponentiationExpression وهكذا. تسمى هذه العملية العودية "تحليل النسب العودية" .

وظائف this.eat ، this.match ، this.next


ربما لاحظت أنه في الأمثلة السابقة ، تم استخدام بعض الوظائف المساعدة ، مثل this.eat و this.match و this.next وغيرها. هذه هي الوظائف الداخلية لمحلل بابل. هذه الوظائف ، مع ذلك ، ليست فريدة من نوعها بابل ، فهي عادة ما تكون موجودة في المحللون الآخرين.

  • ترجع الدالة this.match قيمة منطقية تشير إلى ما إذا كان الرمز المميز الحالي يفي بالشرط المحدد.
  • this.next الدالة this.next للأمام في قائمة الرموز المميزة إلى الرمز المميز التالي.
  • تُرجع الدالة this.eat نفس ما this.match الدالة this.match ، وإذا كانت this.match تُرجع this.match ، this.eat بإجراء مكالمة على this.next قبل العودة this.next .
  • تتيح لك وظيفة this.lookahead الحصول على الرمز المميز التالي دون المضي قدمًا ، مما يساعد على اتخاذ قرار بشأن العقدة الحالية.

إذا نظرت مرة أخرى إلى رمز المحلل اللغوي الذي قمنا بتغييره ، ستجد أن القراءة أصبحت أسهل بكثير:

 packages/babel-parser/src/parser/statement.js export default class StatementParser extends ExpressionParser {  parseStatementContent(/* ...*/) {    // ...    // NOTE:   match        if (this.match(tt._function)) {      this.next();      // NOTE:     ,          this.parseFunction();    }  }  // ...  parseFunction(/* ... */) {    // NOTE:   eat         node.generator = this.eat(tt.star);    node.curry = this.eat(tt.atat);    node.id = this.parseFunctionId();  } } 

أعلم أنني لم أعمق في شرح ملامح المحللون. لذلك ، هنا وهناك - بضع موارد مفيدة حول هذا الموضوع. لقد تعلمت الكثير منهم وأستطيع أن أوصي بهم لك.

قد تكون مهتمًا بمعرفة كيف تمكنت من تصور بناء الجملة الذي قمت بإنشائه في Babel AST Explorer عندما عرضت سمة " curry " الجديدة التي ظهرت في AST.

أصبح هذا ممكنًا بسبب حقيقة أنني أضفت ميزة جديدة في Babel AST Explorer والتي تتيح لك تحميل المحلل اللغوي الخاص بك في أداة بحث AST هذه.

إذا ذهبت على طول packages/babel-parser/lib المسار packages/babel-parser/lib ، يمكنك العثور على نسخة مجمعة من المحلل اللغوي وخريطة رمز. في لوحة Babel AST Explorer ، يمكنك رؤية زر تحميل المحلل اللغوي الخاص بك. عن طريق تنزيل packages/babel-parser/lib/index.js يمكنك تصور AST الذي تم إنشاؤه باستخدام المحلل اللغوي الخاص بك.


AST التصور

البرنامج المساعد لدينا لبابل


الآن بعد اكتمال المحلل اللغوي ، دعنا نكتب ملحقًا لبابل.

لكن ، ربما لديك الآن بعض الشكوك حول كيفية استخدامنا بالضبط لمحلل Babel الخاص بنا ، وخاصة بالنظر إلى مجموعة التكنولوجيا التي نستخدمها لبناء المشروع.

صحيح ، لا يوجد شيء للخوف. يمكن أن يوفر بابل المساعد قدرات المحلل اللغوي. يمكن الاطلاع على الوثائق ذات الصلة على موقع بابل.

 babel-plugin-transformation-curry-function.js import customParser from './custom-parser'; export default function ourBabelPlugin() {  return {    parserOverride(code, opts) {      return customParser.parse(code, opts);    },  }; } function.js babel-plugin-transformation-curry-function.js import customParser from './custom-parser'; export default function ourBabelPlugin() {  return {    parserOverride(code, opts) {      return customParser.parse(code, opts);    },  }; } 

نظرًا لأننا أنشأنا مفترقًا لمحلل Babel ، فهذا يعني أن جميع ميزات المحلل اللغوي الموجودة ، بالإضافة إلى الإضافات المدمجة ، سوف تستمر في العمل بشكل جيد.

بعد أن نتخلص من هذه الشكوك ، دعونا نلقي نظرة على كيفية جعل وظيفة من هذا القبيل يدعم الكاري.

إذا لم تتمكن من تحمل التوقعات وحاولت بالفعل إضافة المكون الإضافي الخاص بنا إلى نظام إنشاء المشروع الخاص بك ، فقد تلاحظ أن الوظائف التي تدعم الكاري يتم تجميعها في وظائف عادية.

يحدث هذا لأنه بعد تحليل الشفرة وتحويلها ، يستخدم Babel @babel/generator لإنشاء الشفرة من AST المحول. نظرًا لأن @babel/generator لا يعرف شيئًا عن سمة curry الجديدة ، فإنه يتجاهلها ببساطة.

إذا كانت الوظائف التي تدعم الكاري في يوم من الأيام تدخل في معيار JavaScript ، فقد ترغب في القيام بعلاقات عامة لإضافة رمز جديد هنا .

من أجل جعل وظيفة دعم currying ، يمكنك لفه في currying وظيفة أعلى ترتيب:

 function currying(fn) {  const numParamsRequired = fn.length;  function curryFactory(params) {    return function (...args) {      const newParams = params.concat(args);      if (newParams.length >= numParamsRequired) {        return fn(...newParams);      }      return curryFactory(newParams);    }  }  return curryFactory([]); } 

إذا كنت مهتمًا بميزات تنفيذ آلية وظائف الكاري في JS - ألق نظرة على هذه المادة.

نتيجة لذلك ، يمكننا تحويل وظيفة تدعم الكاري ، القيام بذلك:

 //   function @@ foo(a, b, c) {  return a + b + c; } //    const foo = currying(function foo(a, b, c) {  return a + b + c; }) 

في الوقت الحالي ، لن ننتبه إلى آلية رفع الوظائف في جافا سكريبت ، والتي تتيح لك الاتصال بالوظيفة قبل تعريفها.

إليك ما يشبه رمز التحويل:

 babel-plugin-transformation-curry-function.js export default function ourBabelPlugin() {  return {    // ... <i>    visitor: {      FunctionDeclaration(path) {        if (path.get('curry').node) {          // const foo = curry(function () { ... });          path.node.curry = false;          path.replaceWith(            t.variableDeclaration('const', [              t.variableDeclarator(                t.identifier(path.get('id.name').node),                t.callExpression(t.identifier('currying'), [                  t.toExpression(path.node),                ])              ),            ])          );        }      },    },</i>  }; } function.js babel-plugin-transformation-curry-function.js export default function ourBabelPlugin() {  return {    // ... <i>    visitor: {      FunctionDeclaration(path) {        if (path.get('curry').node) {          // const foo = curry(function () { ... });          path.node.curry = false;          path.replaceWith(            t.variableDeclaration('const', [              t.variableDeclarator(                t.identifier(path.get('id.name').node),                t.callExpression(t.identifier('currying'), [                  t.toExpression(path.node),                ])              ),            ])          );        }      },    },</i>  }; } 

سيكون من الأسهل بالنسبة لك معرفة ذلك إذا قرأت هذه المادة حول التحولات في بابل.

الآن نحن نواجه مسألة كيفية توفير هذه الآلية مع الوصول إلى وظيفة currying . هنا يمكنك استخدام واحد من طريقتين.

▍Approach رقم 1: يمكن افتراض أن وظيفة الكاري معلنة في النطاق العالمي


إذا كان الأمر كذلك ، فإن المهمة قد أنجزت بالفعل.

إذا ، أثناء تنفيذ التعليمات البرمجية المترجمة ، اتضح أن وظيفة currying غير محددة ، فسنواجه رسالة خطأ تشبه " currying is not defined ". إنه مشابه جدًا للرسالة " لم يتم تعريف regeneratorRuntime ".

لذلك ، إذا استخدم شخص ما babel-plugin-transformation-curry-function لبرنامج babel-plugin-transformation-curry-function ، فقد تحتاج إلى إعلامه بأنه يحتاج إلى تثبيت currying currying لضمان عمل هذا البرنامج المساعد بشكل صحيح.

# النهج رقم 2: يمكنك استخدام بابل / المساعدين


يمكنك إضافة وظيفة مساعد جديدة إلى @babel/helpers . من غير المرجح دمج هذا التطور مع @babel/helpers الرسمي. نتيجةً لذلك ، سيتعين عليك إيجاد طريقة لإظهار @babel/core موقع @babel/helpers :

 package.json {  "resolutions": {    "@babel/helpers": "7.6.0--your-custom-forked-version",  } 

لم أحاول هذا بنفسي ، لكنني أعتقد أن هذه الآلية ستنجح. إذا جربتها وواجهت مشاكل ، فسأناقشها بكل سرور.

@babel/helpers وظيفة مساعدة جديدة إلى @babel/helpers بسيطة للغاية.

أولاً ، انتقل إلى ملف الحزم / babel-helpers / src / helpers.js وأضف إدخالًا جديدًا:

 helpers.currying = helper("7.6.0")`  export default function currying(fn) {    const numParamsRequired = fn.length;    function curryFactory(params) {      return function (...args) {        const newParams = params.concat(args);        if (newParams.length >= numParamsRequired) {          return fn(...newParams);        }        return curryFactory(newParams);      }    }    return curryFactory([]);  } `; 

عند وصف وظيفة مساعدة ، يشار إلى الإصدار المطلوب @babel/core . بعض الصعوبات هنا قد يكون سببها export default وظيفة currying .

لاستخدام وظيفة المساعد ، ما this.addHelper() سوى استدعاء this.addHelper() :

 // ... path.replaceWith(  t.variableDeclaration('const', [    t.variableDeclarator(      t.identifier(path.get('id.name').node),      t.callExpression(this.addHelper("currying"), [        t.toExpression(path.node),      ])    ),  ]) ); 

this.addHelper الأمر this.addHelper ، إذا لزم الأمر ، بتضمين وظيفة المساعد في الجزء العلوي من الملف وإرجاع Identifier يشير إلى الوظيفة المنفذة.

الملاحظات


لقد شاركت في العمل على بابل لبعض الوقت ، لكنني لم اضطر إلى إضافة ميزات لدعم بناء جملة جافا سكريبت الجديد للمحلل. عملت بشكل أساسي على إصلاح الخلل وتحسين ما يتعلق بميزات اللغة الرسمية.

ومع ذلك ، لبعض الوقت الآن كنت مشغولة بفكرة إضافة بنيات بناء جملة جديدة إلى اللغة. كنتيجة لذلك ، قررت أن أكتب مقالة عن ذلك وأجرّبها. إنه لأمر لطيف للغاية أن نرى أن كل شيء يعمل تمامًا كما هو متوقع.

تعد القدرة على التحكم في بناء جملة اللغة التي تستخدمها مصدرًا قويًا للإلهام. هذا يجعل من الممكن ، من خلال تطبيق بعض المنشآت المعقدة ، كتابة رمز أقل ، أو كتابة رمز أبسط من ذي قبل. يتم آليًا نقل آليات تحويل الشفرة البسيطة إلى إنشاءات معقدة ونقلها إلى مرحلة التجميع. هذا يذكرنا بحل async/await يحل مشاكل الجحيم الاستدعاء والسلاسل الطويلة من الوعود.

النتائج


تحدثنا هنا عن كيفية تعديل قدرات محلل Babel ، وكتبنا ملحق تحويل الشفرة الخاص بنا ، وتحدثنا باختصار عن @babel/generator وحول إنشاء وظائف المساعد باستخدام @babel/helpers . يتم تقديم المعلومات المتعلقة بتحويل الشفرة بشكل تخطيطي فقط. اقرأ المزيد عنها هنا .

في هذه العملية ، تطرقنا إلى بعض ميزات المحلل اللغوي. إذا كنت مهتمًا بهذا الموضوع - إذن ، هنا وهناك - الموارد المفيدة لك.

يشبه تسلسل الإجراءات التي قمنا بها جزءًا كبيرًا من العملية التي يتم تنفيذها عند إرسال ميزة JavaScript جديدة إلى TC39. فيما يلي صفحة مستودع TC39 حيث يمكنك العثور على معلومات حول العروض الحالية. هنا يمكنك العثور على مزيد من المعلومات التفصيلية حول كيفية العمل مع العروض المماثلة. عند اقتراح ميزة جافا سكريبت جديدة ، يقوم الشخص الذي يقدمها عادةً بكتابة polyfills أو ، من خلال تخطي Babel ، يعد مظاهرة تثبت أن الجملة تعمل. كما ترون ، فإن إنشاء شوكة للمحلل أو كتابة polyfill ليس هو الجزء الأكثر صعوبة في عملية اقتراح ميزات JS جديدة. من الصعب تحديد مجال الابتكار ، والتخطيط والتفكير في خيارات استخدامه وحالات الحدود ؛ من الصعب جمع آراء واقتراحات أعضاء مجتمع مبرمج JavaScript لذلك ، أود أن أعرب عن امتناني لجميع أولئك الذين يجدون القوة في تقديم ميزات JavaScript TC39 الجديدة ، وبالتالي تطوير هذه اللغة.

إليكم صفحة على جيثب تتيح لك رؤية الصورة الكبيرة لما فعلناه هنا.

أعزائي القراء! هل سبق لك أن أردت تمديد بناء جملة جافا سكريبت؟


Source: https://habr.com/ru/post/ar470878/


All Articles