اليوم ، ننشر الجزء الثاني من ترجمة ملحق بناء جملة 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 {
يرجى ملاحظة أن هنا نسخة مبسطة للغاية لما هو موجود بالفعل في بابل. ولكني آمل أن تسمح لنا هذه الشفرة بتوضيح جوهر ما يحدث.
كما ترون ، المحلل هو ، بطبيعته ، عودي. ينتقل من التصميمات ذات الأولوية الأدنى إلى التصميمات ذات الأولوية العليا. على سبيل المثال ، يستدعي
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() {
أعلم أنني لم أعمق في شرح ملامح المحللون. لذلك ،
هنا وهناك - بضع موارد مفيدة حول هذا الموضوع. لقد تعلمت الكثير منهم وأستطيع أن أوصي بهم لك.
قد تكون مهتمًا بمعرفة كيف تمكنت من تصور بناء الجملة الذي قمت بإنشائه في 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 - ألق نظرة على
هذه المادة.
نتيجة لذلك ، يمكننا تحويل وظيفة تدعم الكاري ، القيام بذلك:
في الوقت الحالي ، لن ننتبه إلى آلية
رفع الوظائف في جافا سكريبت ، والتي تتيح لك الاتصال بالوظيفة قبل تعريفها.
إليك ما يشبه رمز التحويل:
babel-plugin-transformation-curry-function.js export default function ourBabelPlugin() { return {
function.js babel-plugin-transformation-curry-function.js export default function ourBabelPlugin() { return {
سيكون من الأسهل بالنسبة لك معرفة ذلك إذا قرأت
هذه المادة حول التحولات في بابل.
الآن نحن نواجه مسألة كيفية توفير هذه الآلية مع الوصول إلى وظيفة
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()
:
this.addHelper
الأمر
this.addHelper
، إذا لزم الأمر ، بتضمين وظيفة المساعد في الجزء العلوي من الملف وإرجاع
Identifier
يشير إلى الوظيفة المنفذة.
الملاحظات
لقد شاركت في العمل على بابل لبعض الوقت ، لكنني لم اضطر إلى إضافة ميزات لدعم بناء جملة جافا سكريبت الجديد للمحلل. عملت بشكل أساسي على إصلاح الخلل وتحسين ما يتعلق بميزات اللغة الرسمية.
ومع ذلك ، لبعض الوقت الآن كنت مشغولة بفكرة إضافة بنيات بناء جملة جديدة إلى اللغة. كنتيجة لذلك ، قررت أن أكتب مقالة عن ذلك وأجرّبها. إنه لأمر لطيف للغاية أن نرى أن كل شيء يعمل تمامًا كما هو متوقع.
تعد القدرة على التحكم في بناء جملة اللغة التي تستخدمها مصدرًا قويًا للإلهام. هذا يجعل من الممكن ، من خلال تطبيق بعض المنشآت المعقدة ، كتابة رمز أقل ، أو كتابة رمز أبسط من ذي قبل. يتم آليًا نقل آليات تحويل الشفرة البسيطة إلى إنشاءات معقدة ونقلها إلى مرحلة التجميع. هذا يذكرنا بحل
async/await
يحل مشاكل الجحيم الاستدعاء والسلاسل الطويلة من الوعود.
النتائج
تحدثنا هنا عن كيفية تعديل قدرات محلل Babel ، وكتبنا ملحق تحويل الشفرة الخاص بنا ، وتحدثنا باختصار عن
@babel/generator
وحول إنشاء وظائف المساعد باستخدام
@babel/helpers
. يتم تقديم المعلومات المتعلقة بتحويل الشفرة بشكل تخطيطي فقط. اقرأ المزيد عنها
هنا .
في هذه العملية ، تطرقنا إلى بعض ميزات المحلل اللغوي. إذا كنت مهتمًا بهذا الموضوع - إذن ،
هنا وهناك - الموارد المفيدة لك.
يشبه تسلسل الإجراءات التي قمنا بها جزءًا كبيرًا من العملية التي يتم تنفيذها عند إرسال ميزة JavaScript جديدة إلى TC39.
فيما يلي صفحة مستودع TC39 حيث يمكنك العثور على معلومات حول العروض الحالية.
هنا يمكنك العثور على مزيد من المعلومات التفصيلية حول كيفية العمل مع العروض المماثلة. عند اقتراح ميزة جافا سكريبت جديدة ، يقوم الشخص الذي يقدمها عادةً بكتابة polyfills أو ، من خلال تخطي Babel ، يعد مظاهرة تثبت أن الجملة تعمل. كما ترون ، فإن إنشاء شوكة للمحلل أو كتابة polyfill ليس هو الجزء الأكثر صعوبة في عملية اقتراح ميزات JS جديدة. من الصعب تحديد مجال الابتكار ، والتخطيط والتفكير في خيارات استخدامه وحالات الحدود ؛ من الصعب جمع آراء واقتراحات أعضاء مجتمع مبرمج JavaScript لذلك ، أود أن أعرب عن امتناني لجميع أولئك الذين يجدون القوة في تقديم ميزات JavaScript TC39 الجديدة ، وبالتالي تطوير هذه اللغة.
إليكم صفحة على جيثب تتيح لك رؤية الصورة الكبيرة لما فعلناه هنا.
أعزائي القراء! هل سبق لك أن أردت تمديد بناء جملة جافا سكريبت؟
