नेस्टेड कोष्ठकयुक्त पाठ को ऐरे ट्री में बदलने के लिए, उसे बाएँ से दाएँ स्कैन करें। हर शुरुआती कोष्ठक पर एक ऐरे बनाएँ और हर बंद होते कोष्ठक पर उसके पैरेंट ऐरे में लौटें। इस गाइड में JavaScript का एक पूरा पार्सर दिया गया है, जिसे आप Mac पर Safari में स्थानीय रूप से चला सकते हैं।

यहाँ काम लेबलों की छोटी, स्वयं लिखी गई रूपरेखा को नेस्टेड ऐरे में बदलना है, साथ ही उनका क्रम, उद्धृत पाठ और खाली समूह सुरक्षित रखना है। सभी उदाहरण कृत्रिम स्ट्रिंग का उपयोग करते हैं। पार्सर में स्पष्ट व्याकरण नियम और जाँच शामिल हैं, ताकि आप पता लगा सकें कि परिणामी संरचना का अर्थ वही है जो आपका इरादा था।

तय करें कि कोष्ठकों और उद्धृत लेबलों का क्या अर्थ है

कुछ भी बदलने से पहले, संरचनात्मक कोष्ठकों और किसी लेबल के भीतर आने वाले कोष्ठकों के बीच अंतर करें। इस उदाहरण में, बाहरी कोष्ठक एक समूह बनाते हैं, जबकि दोहरे उद्धरणों में दिए लेबल के भीतर के कोष्ठक पाठ ही रहते हैं:

(desk ("blue (round)" "say \"hello\"") () cards)

इच्छित ऐरे संरचना यह है:

[
  [
    "desk",
    ["blue (round)", "say \"hello\""],
    [],
    "cards"
  ]
]

सबसे बाहरी अतिरिक्त ऐरे जानबूझकर रखा गया है: यह पूरे दस्तावेज़ को दर्शाता है। हर कोष्ठकयुक्त समूह इसके भीतर एक ऐरे बन जाता है। इससे लौटाए गए मान का प्रकार एकसमान रहता है, चाहे इनपुट में एक समूह हो, कई समूह हों या बिना बाहरी कोष्ठकों वाले लेबल हों।

निम्नलिखित नियम इस लेख के इनपुट प्रारूप को परिभाषित करते हैं। ये इस पार्सर के लिए चुने गए नियम हैं, JavaScript का कोई अंतर्निहित कोष्ठकयुक्त-पाठ प्रारूप नहीं।

इनपुट की विशेषता परिणामी ट्री में अर्थ
दोहरे उद्धरणों के बाहर ( और ) नेस्टेड ऐरे शुरू और समाप्त करें
दोहरे उद्धरणों के बाहर की खाली जगह लेबलों को अलग करें; विभाजक को न रखें
बिना उद्धरण वाला टोकन, जैसे cards एक स्ट्रिंग बनाएँ
दोहरे उद्धरणों वाली स्ट्रिंग, जैसे "blue tile" आंतरिक खाली जगहों सहित एक स्ट्रिंग बनाएँ
() एक खाली ऐरे बनाएँ
"" एक खाली स्ट्रिंग बनाएँ
दोहरे उद्धरणों के बाहर 12, true या null वर्तनी को स्ट्रिंग के रूप में रखें
अल्पविराम और एकल उद्धरण बिना उद्धरण वाले टोकन में सामान्य वर्ण मानें

कोष्ठक टोकन को भी अलग करते हैं, इसलिए a(b)c से ["a", ["b"], "c"] बनता है। पास-पास के दो लेबलों के बीच खाली जगह होनी चाहिए: "a"b अस्वीकार किया जाता है। यदि किसी लेबल में खाली जगह, दोहरे उद्धरण या अक्षरशः कोष्ठक हों, तो दोहरे उद्धरण वाला रूप इस्तेमाल करें।

दोहरे उद्धरणों वाले टोकन JSON स्ट्रिंग सिंटैक्स का पालन करते हैं। पार्सर हर पूरा उद्धृत टोकन JSON.parse() को देता है, जो JSON पाठ स्वीकार करता है और अमान्य सिंटैक्स पर SyntaxError देता है। इससे एस्केप की जाँच दस्तावेज़ित पार्सर को सौंप दी जाती है। ECMAScript specification: JSON.parse

Safari में स्थानीय JavaScript कार्यक्षेत्र खोलें

Safari का Web Inspector इस डेस्कटॉप रूपांतरण के लिए आवश्यक JavaScript प्रॉम्प्ट देता है। आपको पैकेज इंस्टॉल करने या अलग प्रोजेक्ट बनाने की ज़रूरत नहीं है।

  1. Safari में Safari → Settings… → Advanced चुनें।
  2. Show features for web developers सक्षम करें।
  3. एक टैब खोलें और पता फ़ील्ड में about:blank दर्ज करें।
  4. Develop → Show Web Inspector चुनें, या Option–Command–I दबाएँ।
  5. Console टैब चुनें और उसके इनपुट प्रॉम्प्ट का उपयोग करें।

WebKit डेवलपर सेटिंग और इंस्पेक्टर शॉर्टकट को Enabling Web Inspector में दस्तावेज़ित करता है। उसका Web Inspector Interface संदर्भ बताता है कि कंसोल प्रॉम्प्ट JavaScript का मूल्यांकन करके परिणाम दिखाता है।

नीचे दिया पूरा प्रोग्राम उस प्रॉम्प्ट में पेस्ट करके चलाएँ। इसमें पार्सर, स्वयं लिखा गया एक नमूना, वैध इनपुट की छह जाँचें और अमान्य इनपुट की सात जाँचें शामिल हैं। किसी इंपोर्ट की ज़रूरत नहीं है। प्रोग्राम Node.js 20.19.2 में सफलतापूर्वक चलाया गया; Safari इंटरफ़ेस के चरण दस्तावेज़ित हैं, लेकिन इस लेख के लिए उनका स्वतंत्र रूप से परीक्षण नहीं किया गया।

पूरा कोष्ठकयुक्त-पाठ पार्सर चलाएँ

पार्सर खुले हुए ऐरे का एक स्टैक बनाए रखता है। शुरुआती कोष्ठक एक चाइल्ड ऐरे बनाता है और उसे स्टैक पर रखता है। बंद होता कोष्ठक मौजूदा ऐरे को स्टैक से हटाता है। लेबल उस समय खुले ऐरे में जोड़े जाते हैं।

function parenthesizedToTree(text) {
  if (typeof text !== "string") {
    throw new TypeError("Expected a string");
  }
  const root = [];
  const stack = [{ items: root, start: -1 }];
  const separator = c => /\s|[()]/u.test(c);
  let i = 0;

  function fail(message, at = i) {
    throw new SyntaxError(message + " at UTF-16 offset " + at);
  }

  while (i < text.length) {
    const c = text[i];
    const current = stack[stack.length - 1].items;

    if (/\s/u.test(c)) {
      i++;
    } else if (c === "(") {
      const child = [];
      current.push(child);
      stack.push({ items: child, start: i });
      i++;
    } else if (c === ")") {
      if (stack.length === 1) fail("Unexpected closing parenthesis");
      stack.pop();
      i++;
    } else if (c === '"') {
      const start = i++;
      let closed = false;
      while (i < text.length) {
        if (text[i] === "\\") {
          i += 2;
        } else if (text[i] === '"') {
          i++;
          closed = true;
          break;
        } else {
          i++;
        }
      }
      if (!closed) fail("Unclosed quoted string", start);
      let value;
      try {
        value = JSON.parse(text.slice(start, i));
      } catch {
        fail("Invalid JSON string", start);
      }
      if (i < text.length && !separator(text[i])) {
        fail("Expected whitespace or a parenthesis");
      }
      current.push(value);
    } else {
      const start = i;
      while (i < text.length && !separator(text[i])) {
        if (text[i] === '"') fail("Quote inside an unquoted token");
        i++;
      }
      current.push(text.slice(start, i));
    }
  }

  if (stack.length !== 1) {
    fail("Unclosed opening parenthesis", stack[stack.length - 1].start);
  }
  return root;
}

const input = String.raw`(desk ("blue (round)" "say \"hello\"") () cards)`;
console.log(JSON.stringify(parenthesizedToTree(input), null, 2));

const validCases = [
  ["", []],
  ["()", [[]]],
  ["a(b)c", ["a", ["b"], "c"]],
  ['("a b" "")', [["a b", ""]]],
  ["(12 true)", [["12", "true"]]],
  [String.raw`("a\\b" "\u0061" "$&")`, [["a\\b", "a", "$&"]]]
];
for (const [source, expected] of validCases) {
  const actual = parenthesizedToTree(source);
  if (JSON.stringify(actual) !== JSON.stringify(expected)) {
    throw new Error("Unexpected tree for " + source);
  }
}

const invalidCases = [
  "(a", "a)", '("a)', String.raw`("\q")`,
  '"a"b', 'a"b"', '("line\nbreak")'
];
for (const source of invalidCases) {
  let rejected = false;
  try {
    parenthesizedToTree(source);
  } catch (error) {
    if (!(error instanceof SyntaxError)) throw error;
    rejected = true;
  }
  if (!rejected) throw new Error("Expected rejection for " + source);
}
console.log("All 13 checks passed.");

पहला प्रिंट हुआ परिणाम वही नेस्टेड संरचना है जो ऊपर दिखाई गई है, और कई पंक्तियों में फ़ॉर्मैट किया गया है। अंतिम संदेश All 13 checks passed. होना चाहिए। फ़ंक्शन एक वास्तविक JavaScript ऐरे लौटाता है; JSON.stringify() उस मान को प्रिंट किए जा सकने वाले पाठ में बदलता है, और इसका तीसरा आर्ग्युमेंट इंडेंटेशन देता है। ECMAScript specification: JSON.stringify

किसी दूसरी कृत्रिम रूपरेखा को बदलने के लिए, input को दिए गए नमूने को ही बदलें और प्रोग्राम फिर चलाएँ। जाँचों को जस का तस रखें, ताकि वे पार्सर के अपेक्षित व्यवहार की पुष्टि करती रहें। जब पाठ्य रूप चाहिए, तब प्रिंट हुए ऐरे-पाठ को अपने कार्य दस्तावेज़ में कॉपी करें।

उद्धरण और एस्केप के अर्थ सुरक्षित रखें

दो परतों पर ध्यान देना होगा: नमूने को रखने वाला JavaScript स्रोत और पार्स किया जा रहा कोष्ठकयुक्त पाठ। नमूने में String.raw टैग किया हुआ टेम्पलेट इस्तेमाल होता है, ताकि बैकस्लैश JavaScript स्रोत वाली परत पर सामान्य स्ट्रिंग एस्केप डिकोडिंग के बिना पार्सर तक पहुँचें। दस्तावेज़ित ऑपरेशन टेम्पलेट के कच्चे सेगमेंट पढ़ता है। ECMAScript specification: String.raw

दिए गए नमूने के चारों ओर बैकटिक डिलिमिटर रखें। इस सरल संपादन प्रक्रिया में, नमूने का पाठ बैकटिक और टेम्पलेट इंटरपोलेशन ओपनर ${ के बिना लिखें, क्योंकि JavaScript टेम्पलेट सिंटैक्स में उनका अर्थ होता है।

दोहरे उद्धरणों वाले इनपुट टोकन के भीतर, \" एक अक्षरशः दोहरे उद्धरण को दर्शाता है, \\ एक बैकस्लैश को दर्शाता है, और \u0061 का अर्थ a है। ये डिकोड किए गए स्ट्रिंग मान हैं: ट्री लेबल का अर्थ सुरक्षित रखता है, जबकि मूल एस्केप वर्तनी और उसके आसपास के उद्धरण चिह्न हटा दिए जाते हैं। दोहरे उद्धरणों के बाहर, कस्टम पार्सर बैकस्लैश को सामान्य टोकन वर्ण मानता है।

उदाहरण के लिए, वैध इनपुट की आखिरी जाँच पुष्टि करती है कि इनपुट टोकन "a\\b", "\u0061" और "$&" से एक बैकस्लैश वाला लेबल, a लेबल और अक्षरशः $& लेबल बनते हैं। यदि आपको मूल खाली जगह और एस्केप वर्तनी को ठीक वैसे ही दोहराना है, तो डिकोड की गई स्ट्रिंग का ऐरे पर्याप्त नहीं है; उस रूप में स्रोत पाठ या टोकन की स्थिति भी सुरक्षित रखनी होगी।

हर जगह प्रतिस्थापन करने से परिणाम क्यों बदल जाता है

हर शुरुआती कोष्ठक को चौकोर कोष्ठक और हर बंद होते कोष्ठक को किसी दूसरे चौकोर कोष्ठक से बदलना इस व्याकरण को लागू नहीं करता। इससे "blue (round)" के भीतर के कोष्ठक भी बदल जाएँगे, जबकि बिना उद्धरण वाले लेबल और आइटमों के बीच छूटे हुए विभाजक हल नहीं होंगे। यह निष्कर्ष सीधे स्वयं लिखे नमूने से निकलता है।

प्रतिस्थापन में एक और प्रासंगिक बात है: स्ट्रिंग वाला प्रतिस्थापन आर्ग्युमेंट $& जैसी विशेष श्रंखलाओं का अर्थ निकालता है, जो मिलान किए गए पाठ को डालती है। प्रतिस्थापन फ़ंक्शन अपना लौटाया हुआ पाठ इस प्रतिस्थापन चरण के बिना देता है। ECMAScript specification: replaceAll and replacement substitution

यह स्वतंत्र उदाहरण अंतर दिखाता है:

const label = "tile";
console.log(label.replaceAll("tile", "$&"));       // tile
console.log(label.replaceAll("tile", () => "$&")); // $&

तैयारी के दौरान दोनों परिणामों की पुष्टि की गई थी। ट्री पार्सर प्रतिस्थापन के नियमों का उपयोग बिल्कुल नहीं करता: यह डिकोड की गई स्ट्रिंग सीधे ऐरे में जोड़ता है। यदि आप बाद में लेबल का नाम बदलने का चरण जोड़ते हैं, तो पार्स किए गए लेबलों पर प्रतिस्थापन करें और स्पष्ट रूप से तय करें कि प्रतिस्थापन पाठ का अर्थ निकाला जाना चाहिए या उसे अक्षरशः जोड़ना चाहिए।

ट्री का फिर से उपयोग करने से पहले अप्रत्याशित आउटपुट की जाँच करें

शामिल जाँचें एक संक्षिप्त निर्णय-सहायक की तरह काम करती हैं। वे खाली इनपुट, खाली समूह, पास-पास के समूह और लेबल, उद्धरणों वाली खाली जगह, केवल स्ट्रिंग वाले मान, एस्केप और अमान्य सिंटैक्स को कवर करती हैं। अपने इच्छित रूपरेखा और परिभाषित व्याकरण के बीच असंगति का पता लगाने के लिए ये लक्षण देखें।

लक्षण व्याख्या और अगला कदम
परिणाम में एक अतिरिक्त सबसे बाहरी ऐरे है रूट दस्तावेज़ को दर्शाता है। इसे तब तक रखें, जब तक अगले चरण में स्पष्ट रूप से ठीक एक शीर्ष-स्तरीय समूह की आवश्यकता न हो।
कोई वाक्यांश कई स्ट्रिंग बन जाता है खाली जगह बिना उद्धरण वाले लेबलों को अलग करती है। उस वाक्यांश को दोहरे उद्धरणों के भीतर रखें।
अल्पविराम लेबल में बना रहता है यहाँ अल्पविराम सामान्य वर्ण हैं। अलग-अलग लेबलों के बीच खाली जगह का उपयोग करें।
कोष्ठक अनपेक्षित रूप से नेस्टेड ऐरे बन जाते हैं अक्षरशः कोष्ठकों को दोहरे उद्धरणों वाले लेबल के भीतर रखें।
Unexpected closing parenthesis किसी बंद होते कोष्ठक से मेल खाता खुला समूह नहीं है। बताई गई स्थिति जाँचें।
Unclosed opening parenthesis अंत में एक समूह खुला रह गया है। स्थिति सबसे भीतर वाले ऐसे शुरुआती कोष्ठक की पहचान करती है जिसका समूह बंद नहीं हुआ।
Invalid JSON string उद्धृत टोकन में अमान्य एस्केप या बिना एस्केप वाला नियंत्रण वर्ण है या नहीं, जाँचें।
उद्धरण से जुड़ी विभाजक त्रुटि पास-पास के लेबलों के बीच खाली जगह रखें, या इच्छित पूरे लेबल को एक उद्धृत स्ट्रिंग में रखें।

इस कार्यान्वयन में ऑफ़सेट शून्य से शुरू होते हैं और UTF-16 कोड यूनिट गिनते हैं, जो इसकी JavaScript स्ट्रिंग इंडेक्सिंग से मेल खाता है। ये पंक्ति संख्याएँ या दिखाई देने वाले वर्णों की गिनती नहीं हैं। उद्धृत टोकन की जाँच में बताई गई स्थिति टोकन की शुरुआत होती है, इसलिए पूरे उद्धृत लेबल को देखें।

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

डेस्कटॉप पर छोटी रूपरेखा के लिए व्यावहारिक क्रम यह है: व्याकरण पर सहमति बनाएँ, बिना बदलाव के नमूना और जाँचें चलाएँ, फिर अपना लिखा हुआ पाठ रखें। परिणाम का फिर से उपयोग करने से पहले उद्धृत लेबलों, खाली समूहों और दस्तावेज़ रूट की पुष्टि करें; यही तीन बातें तय करती हैं कि ऐरे ट्री में आपकी इच्छित संरचना सुरक्षित है या नहीं।

स्रोत