बाबेल का उपयोग करके कस्टम जावास्क्रिप्ट सिंटैक्स का निर्माण करें। भाग 1

आज हम सामग्री के अनुवाद के पहले भाग को प्रकाशित करते हैं, जो बेबल का उपयोग करके जावास्क्रिप्ट के लिए अपने स्वयं के वाक्यविन्यास निर्माण के लिए समर्पित है।



सिंहावलोकन


शुरू करने के लिए, आइए एक नज़र डालते हैं कि इस सामग्री के अंत तक हम क्या हासिल करेंगे:

//  '@@'   `foo`   function @@ foo(a, b, c) {   return a + b + c; } console.log(foo(1, 2)(3)); // 6 

हम @@ सिंटैक्स को लागू करने जा रहे हैं जो करी क्रियाओं की अनुमति देता है। यह सिंटैक्स जेनरेटर फ़ंक्शंस बनाने के लिए उपयोग किए जाने वाले समान है, लेकिन हमारे मामले में, * साइन के बजाय, function कीवर्ड और function नाम के बीच @@ वर्णों का एक क्रम रखा गया है। परिणामस्वरूप, जब फ़ंक्शन की घोषणा करते हैं, तो आप फॉर्म function @@ name(arg1, arg2) निर्माण का उपयोग कर सकते हैं।

उपरोक्त उदाहरण में, जब foo फ़ंक्शन के साथ काम कर रहे हैं, तो आप इसके आंशिक अनुप्रयोग का उपयोग कर सकते हैं। फ़ंक्शन foo को इसे पास करने के साथ इतने सारे पैरामीटर जो इसे आवश्यक तर्कों की संख्या से कम है, एक नया फ़ंक्शन लौटाएगा जो शेष तर्क ले सकता है:

 foo(1, 2, 3); // 6 const bar = foo(1, 2); // (n) => 1 + 2 + n bar(3); // 6 

मैंने @@ वर्णों का अनुक्रम चुना क्योंकि @ प्रतीक का उपयोग चर नामों में नहीं किया जा सकता है। इसका मतलब यह है कि फॉर्म function@@foo(){} का एक निर्माण भी वाक्यात्मक रूप से सही होगा। इसके अलावा, डेकोरेटर कार्यों के लिए "ऑपरेटर" @ का उपयोग किया जाता है, और मैं कुछ पूरी तरह से नया उपयोग करना चाहता था। परिणामस्वरूप, मैंने @@ निर्माण को चुना।

अपने लक्ष्य को प्राप्त करने के लिए, हमें निम्नलिखित क्रियाएं करने की आवश्यकता है:

  • बाबेल पार्सर का कांटा बनाएं।
  • कोड परिवर्तन के लिए अपना स्वयं का बैबेल प्लगइन बनाएं।

कुछ असंभव सा लगता है?
वास्तव में, यहां कुछ भी भयानक नहीं है, हम एक साथ सब कुछ का विस्तार से विश्लेषण करेंगे। मुझे उम्मीद है कि जब आप इसे पढ़ेंगे, तो आप कुशलता से बेबेल के मास्टर की उपाधि प्राप्त करेंगे।

एक कांटा बाबेल बनाना


गिटहब पर बैबेल रिपॉजिटरी पर जाएं और Fork बटन पर क्लिक करें, जो पृष्ठ के ऊपरी बाएं भाग में स्थित है।


बाबेल का एक कांटा बनाना ( पूर्ण आकार की छवि )

और वैसे, अगर आपने पहली बार लोकप्रिय ओपन सोर्स प्रोजेक्ट का कांटा बनाया है - बधाई!

अब अपने कंप्यूटर पर बैबल फोर्क को क्लोन करें और इसे काम के लिए तैयार करें

 $ git clone https://github.com/tanhauhau/babel.git # set up $ cd babel $ make bootstrap $ make build 

अब मैं संक्षेप में बेबल भंडार के संगठन के बारे में बात करता हूं।

बैबेल एक मोनोरेपोजिटरी का उपयोग करता है। सभी पैकेज (उदा। @babel/core , @babel/parser , @babel/plugin-transform-react-jsx इत्यादि) packages/ फ़ोल्डर में स्थित हैं। यह इस तरह दिखता है:

 - doc - packages  - babel-core  - babel-parser  - babel-plugin-transform-react-jsx  - ... - Gulpfile.js - Makefile - ... 

मैं ध्यान देता हूं कि बैबल कार्यों को स्वचालित करने के लिए मेकफाइल का उपयोग करता है। make build द्वारा एक परियोजना का make build , गुलप का उपयोग कार्य प्रबंधक के रूप में किया जाता है।

एएसटी शॉर्ट कोर्स में कोड रूपांतरण


यदि आप "पार्सर" और "सार सिंटेक्स ट्री" (एएसटी) जैसी अवधारणाओं से परिचित नहीं हैं, तो इससे पहले कि आप पढ़ना जारी रखें, मैं अत्यधिक अनुशंसा करता हूं कि आप इस सामग्री पर एक नज़र डालें।

यदि आप बहुत संक्षेप में बात करते हैं कि कोड को पार्स करने (पार्स करने) के दौरान क्या होता है, तो आपको निम्नलिखित मिलते हैं:

  • एक स्ट्रिंग (प्रकार string ) के रूप में प्रस्तुत कोड वर्णों की एक लंबी सूची की तरह दिखता है: f, u, n, c, t, i, o, n, , @, @, f, ...
  • शुरुआत में, बबेल कोड टोकेनाइजेशन करता है। इस चरण में, बैबल कोड को स्कैन करता है और टोकन बनाता है। उदाहरण के लिए, function, @@, foo, (, a, ...
  • तब टोकन को पार्सर के माध्यम से उनके पार्सिंग के लिए पारित किया जाता है। यहाँ पर बबेल, जावास्क्रिप्ट भाषा के विनिर्देशन के आधार पर, एक सार वाक्य रचना ट्री बनाता है।

यहां उन लोगों के लिए एक महान संसाधन है जो संकलक के बारे में अधिक जानना चाहते हैं।

यदि आपको लगता है कि "संकलक" कुछ बहुत ही जटिल और समझ से बाहर है, तो जान लें कि वास्तव में सब कुछ इतना रहस्यमय नहीं है। संकलन केवल कोड को पार्स कर रहा है और इसके आधार पर एक नया कोड बना रहा है, जिसे हम XXX कहेंगे। XXX कोड को मशीन कोड द्वारा दर्शाया जा सकता है (शायद, मशीन कोड वह है जो हम में से अधिकांश के दिमाग में सबसे पहले पॉप अप करता है जब हम संकलक के बारे में सोचते हैं)। यह लीगासी ब्राउज़र के साथ संगत जावास्क्रिप्ट कोड हो सकता है। असल में, बैबेल का एक मुख्य कार्य आधुनिक जेएस-कोड का संकलन है जो पुराने ब्राउज़रों के लिए समझ में आता है।

बाबेल के लिए अपना खुद का पार्सर विकसित करना


हम packages/babel-parser/ फ़ोल्डर में काम करने जा रहे हैं:

 - src/  - tokenizer/  - parser/  - plugins/    - jsx/    - typescript/    - flow/    - ... - test/ 

हम पहले से ही टोकन और पार्सिंग के बारे में बात कर चुके हैं। आप उन कोड को पा सकते हैं जो इन प्रक्रियाओं को संबंधित नाम वाले फ़ोल्डरों में लागू करते हैं। plugins/ फ़ोल्डर में प्लगइन्स (प्लग-इन) होते हैं जो बेस पार्सर की क्षमताओं का विस्तार करते हैं और सिस्टम में अतिरिक्त सिंटैक्स के लिए समर्थन जोड़ते हैं। यह वास्तव में कैसे है, उदाहरण के लिए, jsx और flow समर्थन लागू किया गया है।

आइए परीक्षण (टेस्ट-संचालित विकास, टीडीडी) के माध्यम से विकास तकनीक का उपयोग करके हमारी समस्या को हल करें। मेरी राय में, पहले एक परीक्षण लिखना सबसे आसान है, और फिर, धीरे-धीरे सिस्टम पर काम करना, त्रुटियों के बिना इस परीक्षण को चलाना। अपरिचित कोड बेस में काम करते समय यह दृष्टिकोण विशेष रूप से अच्छा है। TDD यह समझना आसान बनाता है कि आपको अपनी इच्छित कार्यक्षमता को लागू करने के लिए कोड में बदलाव करने की आवश्यकता कहां है।

 packages/babel-parser/test/curry-function.js import { parse } from '../lib'; function getParser(code) {  return () => parse(code, { sourceType: 'module' }); } describe('curry function syntax', function() {  it('should parse', function() {    expect(getParser(`function @@ foo() {}`)()).toMatchSnapshot();  }); }); 

आप इस तरह से babel-parser लिए परीक्षण चला सकते हैं: TEST_ONLY=babel-parser TEST_GREP="curry function" make test-only । यह आपको त्रुटियों को देखने की अनुमति देगा:

 SyntaxError: Unexpected token (1:9) at Parser.raise (packages/babel-parser/src/parser/location.js:39:63) at Parser.raise [as unexpected] (packages/babel-parser/src/parser/util.js:133:16) at Parser.unexpected [as parseIdentifierName] (packages/babel-parser/src/parser/expression.js:2090:18) at Parser.parseIdentifierName [as parseIdentifier] (packages/babel-parser/src/parser/expression.js:2052:23) at Parser.parseIdentifier (packages/babel-parser/src/parser/statement.js:1096:52) 

यदि आप पाते हैं कि सभी परीक्षणों को देखने में बहुत अधिक समय लगता है, तो आप वांछित परीक्षण को चलाने के लिए, सीधे jest को कॉल कर सकते हैं:

 BABEL_ENV=test node_modules/.bin/jest -u packages/babel-parser/test/curry-function.js 

हमारे पार्सर ने 2 @ टोकन की खोज की, प्रतीत होता है कि वे पूरी तरह से निर्दोष हैं, जहां उन्हें नहीं होना चाहिए।

मुझे यह कैसे पता चला? इस प्रश्न का उत्तर हमें make watch द्वारा शुरू किए गए कोड मॉनिटरिंग मोड का उपयोग करने में मदद करेगा।

कॉल स्टैक को देखने से हमें पैकेज / बेबल-पार्सर / src / parser / अभिव्यक्ति . this.unexpected() , जहां इस अपवाद को this.unexpected() दिया जाता है।

इस फ़ाइल में लॉगिंग कमांड के एक जोड़े को जोड़ें:

 packages/babel-parser/src/parser/expression.js parseIdentifierName(pos: number, liberal?: boolean): string {  if (this.match(tt.name)) {    // ...  } else {    console.log(this.state.type); //      console.log(this.lookahead().type); //      throw this.unexpected();  } } 

जैसा कि आप देख सकते हैं, दोनों टोकन @ :

 TokenType {  label: '@',  // ... } 

मुझे यह कैसे पता चला कि कंस्ट्रक्शन इस this.state.type और this.lookahead().type करते हैं और मुझे वर्तमान और अगला टोकन देंगे?
मैं इस सामग्री के अनुभाग में इस कार्य के लिए समर्पित के बारे में बात करूँगा। यह, this.match और this.nextthis.next

आगे बढ़ने से पहले, आइए संक्षेप करते हैं:

  • हमने babel-parser लिए एक परीक्षण लिखा।
  • हमने make test-only बनाकर टेस्ट चलाया।
  • हमने make watch का उपयोग करके कोड मॉनिटरिंग मोड का उपयोग किया।
  • हमने पार्सर की स्थिति के बारे में सीखा और कंसोल में वर्तमान टोकन के प्रकार ( this.state.type ) के बारे में जानकारी this.state.type

और अब हम यह सुनिश्चित करेंगे कि 2 @ वर्णों को अलग टोकन के रूप में नहीं माना जाता है, लेकिन एक नए @@ टोकन के रूप में, जिसे हमने करी कार्यों के लिए उपयोग करने का निर्णय लिया है।

नया टोकन: "@@"


सबसे पहले, आइए देखें कि टोकन के प्रकार कहाँ निर्धारित किए जाते हैं। यह फ़ाइल पैकेज / बैबल-पार्सर / src / tokenizer / type.js है

यहां आप टोकन की सूची पा सकते हैं। यहां नया atat टोकन की परिभाषा जोड़ें:

 packages/babel-parser/src/tokenizer/types.js export const types: { [name: string]: TokenType } = {  // ...  at: new TokenType('@'),  atat: new TokenType('@@'), }; 

अब कोड में उस जगह की तलाश करते हैं जहां, टोकन प्रक्रिया में, टोकन बनाए जाते हैं। tt.at babel-parser/src/tokenizer में tt.at वर्णों के अनुक्रम का पता tt.at हमें फ़ाइल की ओर ले जाता है: पैकेज / babel-parser / src / tokenizer / index.jsbabel-parser टोकन प्रकार को tt रूप में आयात किया जाता है।

अब, यदि वर्तमान @ प्रतीक दूसरे के बाद @ आता है, तो tt.at टोकन के बजाय एक नया टोकन tt.atat tt.at :

 packages/babel-parser/src/tokenizer/index.js getTokenFromCode(code: number): void {  switch (code) {    // ...    case charCodes.atSign:      //    -  `@`      if (this.input.charCodeAt(this.state.pos + 1) === charCodes.atSign) {        //  `tt.atat`  `tt.at`        this.finishOp(tt.atat, 2);      } else {        this.finishOp(tt.at, 1);      }      return;    // ...  } } 

यदि आप परीक्षण फिर से चलाते हैं, तो आप देखेंगे कि वर्तमान और अगले टोकन के बारे में जानकारी बदल गई है:

 //   TokenType {  label: '@@',  // ... } //   TokenType {  label: 'name',  // ... } 

यह पहले से ही काफी अच्छा लग रहा है। हम काम जारी रखेंगे।

नया पार्सर


आगे बढ़ने से पहले, एएसटी में जनरेटर कार्यों का प्रतिनिधित्व कैसे किया जाता है, इस पर एक नज़र डालें।


जनरेटर समारोह के लिए एएसटी ( पूर्ण आकार की छवि )

जैसा कि आप देख सकते हैं, generator: true FunctionDeclaration इकाई की generator: true विशेषता यह दर्शाती है कि यह एक जनरेटर FunctionDeclaration

हम एक फ़ंक्शन का वर्णन करने के लिए एक समान दृष्टिकोण ले सकते हैं जो करी का समर्थन करता है। अर्थात्, हम curry: true को जोड़ सकते हैं curry: true FunctionDeclaration को curry: true विशेषता।


एएसटी के लिए करी समारोह ( पूर्ण आकार छवि )

दरअसल, अब हमारे पास एक योजना है। चलो इसके कार्यान्वयन से निपटते हैं।

यदि आप parseFunction शब्द के कोड में देखते हैं, तो आप parseFunction फंक्शन फ़ंक्शन पर जा सकते हैं, जो पैकेज / बेबेल-पार्सर / src / parser / statement.js में घोषित किया गया है । यहां आप वह लाइन पा सकते हैं जहां generator विशेषता सेट है। कोड में एक और लाइन जोड़ें:

 packages/babel-parser/src/parser/statement.js export default class StatementParser extends ExpressionParser {  // ...  parseFunction<T: N.NormalFunction>(    node: T,    statement?: number = FUNC_NO_FLAGS,    isAsync?: boolean = false  ): T {    // ...    node.generator = this.eat(tt.star);    node.curry = this.eat(tt.atat);  } } 

अगर हम दोबारा परीक्षा देते हैं, तो सुखद आश्चर्य का इंतजार होगा। कोड का सफलतापूर्वक परीक्षण किया गया है!

 PASS packages/babel-parser/test/curry-function.js  curry function syntaxshould parse (12ms) 

क्या वह सब है? हमने परीक्षा को चमत्कारिक ढंग से पास करने के लिए क्या किया है?

यह पता लगाने के लिए, आइए चर्चा करें कि पार्सिंग कैसे काम करता है। इस वार्तालाप के दौरान, मुझे आशा है कि आप समझेंगे कि कैसे लाइन node.curry = this.eat(tt.atat);

जारी रखने के लिए ...

प्रिय पाठकों! क्या आप कोलाहल का उपयोग करते हैं?


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


All Articles