Computer >> कंप्यूटर >  >> प्रोग्रामिंग >> Ruby

रूबी टेम्प्लेटिंग में गहरी खुदाई:पार्सर

आज, हम रूबी टेम्पलेटिंग में अपनी यात्रा जारी रखते हैं। लेक्सर के स्थान पर, आइए अगले चरण पर चलते हैं:पार्सर।

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

ये रहे!

सार सिंटेक्स ट्री

आइए Welcome to {{name}} में आपका स्वागत है के लिए हमारे सरल उदाहरण टेम्पलेट पर एक नज़र डालते हैं . स्ट्रिंग को टोकन करने के लिए लेक्सर का उपयोग करने के बाद, हमें इस तरह के टोकन की एक सूची मिलती है।

Magicbars::Lexer.tokenize("Welcome to {{name}}")
# => [[:CONTENT, "Welcome to "], [:OPEN_EXPRESSION], [:IDENTIFIER, "name"], [:CLOSE]]

अंततः, हम टेम्पलेट का मूल्यांकन करना चाहते हैं और व्यंजक को वास्तविक मानों से बदलना चाहते हैं। चीजों को थोड़ा और चुनौतीपूर्ण बनाने के लिए, हम जटिल ब्लॉक अभिव्यक्तियों का मूल्यांकन भी करना चाहते हैं, जिससे दोहराव और सशर्त की अनुमति मिलती है।

ऐसा करने के लिए, हमें एक अमूर्त सिंटैक्स ट्री (एएसटी) उत्पन्न करना होगा जो टेम्पलेट की तार्किक संरचना का वर्णन करता है। ट्री में नोड्स होते हैं जो अन्य नोड्स को संदर्भित कर सकते हैं या टोकन से अतिरिक्त डेटा संग्रहीत कर सकते हैं।

हमारे सरल उदाहरण के लिए, वांछित सार सिंटैक्स ट्री इस तरह दिखता है:

व्याकरण को परिभाषित करना

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

एक संदर्भ-मुक्त व्याकरण नियमों का एक समूह है जो वर्णन करता है कि किसी भाषा के सभी संभावित तारों का निर्माण कैसे किया जाता है। आइए EBNF नोटेशन में हमारी टेम्प्लेटिंग भाषा के व्याकरण को देखें:

template = statements;
statements = { statement };
statement = CONTENT | expression | block_expression;
expression = OPEN_EXPRESSION, IDENTIFIER, arguments, CLOSE;
block_expression = OPEN_BLOCK, IDENTIFIER, arguments, CLOSE, statements, [ OPEN_INVERSE, CLOSE, statements ], OPEN_END_BLOCK, IDENTIFIER, CLOSE;
arguments = { IDENTIFIER };

प्रत्येक असाइनमेंट एक नियम को परिभाषित करता है। नियम का नाम बाईं ओर है और हमारे लेक्सर से अन्य नियमों (लोअर केस) या टोकन (अपर केस) का एक गुच्छा दाईं ओर है। नियमों और टोकन को अल्पविराम , . का उपयोग करके संयोजित किया जा सकता है या पाइप का उपयोग करके वैकल्पिक रूप से | चिन्ह, प्रतीक। घुंघराले ब्रेसिज़ के अंदर नियम और टोकन { ... } कई बार दोहराया जा सकता है। जब वे कोष्ठक के अंदर हों [ ... ] , उन्हें वैकल्पिक माना जाता है।

उपरोक्त व्याकरण यह वर्णन करने का एक संक्षिप्त तरीका है कि एक टेम्पलेट में कथन होते हैं। एक स्टेटमेंट या तो CONTENT होता है टोकन, एक अभिव्यक्ति, या एक ब्लॉक अभिव्यक्ति। एक व्यंजक एक OPEN_EXPRESSION है टोकन, उसके बाद IDENTIFIER टोकन, उसके बाद तर्क, उसके बाद CLOSE टोकन। और एक ब्लॉक एक्सप्रेशन इसका सटीक उदाहरण है कि प्राकृतिक भाषा के साथ इसका वर्णन करने की कोशिश करने के बजाय ऊपर वाले जैसे नोटेशन का उपयोग करना बेहतर क्यों है।

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

पार्सर बनाना

एक तरफ भाषा सिद्धांत के साथ, आइए वास्तव में पार्सर के निर्माण में कूदें। आइए एक और अधिक न्यूनतम, लेकिन फिर भी मान्य टेम्पलेट के साथ शुरू करें:Welcome to Ruby Magic . इस टेम्पलेट में कोई भाव नहीं है और टोकन की सूची में केवल एक तत्व है। यह इस तरह दिखता है:

[[:CONTENT, "Welcome to Ruby Magic"]]

सबसे पहले, हमने अपना पार्सर वर्ग स्थापित किया। यह इस तरह दिखता है:

module Magicbars
  class Parser
    def self.parse(tokens)
      new(tokens).parse
    end
 
    attr_reader :tokens
 
    def initialize(tokens)
      @tokens = tokens
    end
 
    def parse
      # Parsing starts here
    end
  end
end

वर्ग टोकन की एक सरणी लेता है और उसे संग्रहीत करता है। इसकी केवल एक सार्वजनिक विधि है जिसे parse . कहा जाता है जो टोकन को एएसटी में बदल देता है।

हमारे व्याकरण को देखते हुए, सबसे ऊपर का नियम है template . इसका मतलब है कि parse , पार्सिंग प्रक्रिया की शुरुआत में, एक template लौटाएगा नोड.

नोड्स सरल वर्ग हैं जिनका अपना कोई व्यवहार नहीं है। वे सिर्फ अन्य नोड्स को जोड़ते हैं या टोकन से कुछ मूल्यों को संग्रहीत करते हैं। यहाँ क्या है template नोड जैसा दिखता है:

module Magicbars
  module Nodes
    class Template
      attr_reader :statements
 
      def initialize(statements)
        @statements = statements
      end
    end
  end
end

हमारे उदाहरण को काम करने के लिए, हमें एक Content . की भी आवश्यकता है नोड. यह सिर्फ टेक्स्ट सामग्री को स्टोर करता है ("Welcome to Ruby Magic" ) टोकन से।

module Magicbars
  module Nodes
    class Content
      attr_reader :content
 
      def initialize(content)
        @content = content
      end
    end
  end
end

इसके बाद, template . का एक उदाहरण बनाने के लिए पार्स विधि को लागू करते हैं और Content . का एक उदाहरण और उन्हें सही तरीके से कनेक्ट करें।

def parse
  Magicbars::Nodes::Template.new(parse_content)
end
 
def parse_content
  return unless tokens[0][0] == :CONTENT
 
  Magicbars::Nodes::Content.new(tokens[0][1])
end

जब हम पार्सर चलाते हैं, तो हमें सही परिणाम मिलता है:

Magicbars::Parser.parse(tokens)
# => #<Magicbars::Nodes::Template:0x00007fe90e939410 @statements=#<Magicbars::Nodes::Content:0x00007fe90e939578 @content="Welcome to Ruby Magic">>

बेशक, यह केवल हमारे सरल उदाहरण के लिए काम करता है जिसमें केवल एक सामग्री नोड है। आइए एक अधिक जटिल उदाहरण पर स्विच करें जिसमें वास्तव में एक अभिव्यक्ति शामिल है:Welcome to {{name}}

Magicbars::Lexer.tokenize("Welcome to {{name}}")
# => [[:CONTENT, "Welcome to "], [:OPEN_EXPRESSION], [:IDENTIFIER, "name"], [:CLOSE]]

इसके लिए हमें एक Expression की आवश्यकता है नोड और एक IDENTIFIER नोड. Expression नोड पहचानकर्ता के साथ-साथ किसी भी तर्क को संग्रहीत करता है (जो, व्याकरण के अनुसार, शून्य या अधिक की एक सरणी है IDENTIFIER नोड्स)। अन्य नोड्स की तरह, यहां देखने के लिए बहुत कुछ नहीं है।

module Magicbars
  module Nodes
    class Expression
      attr_reader :identifier, :arguments
 
      def initialize(identifier, arguments)
        @identifier = identifier
        @arguments = arguments
      end
    end
  end
end
 
module Magicbars
  module Nodes
    class Identifier
      attr_reader :value
 
      def initialize(value)
        @value = value.to_sym
      end
    end
  end
end

नए नोड्स के साथ, आइए parse को संशोधित करें नियमित सामग्री के साथ-साथ अभिव्यक्ति दोनों को संभालने की विधि। हम एक parse_statements . की शुरुआत करके ऐसा करते हैं विधि जो बस parse_statement . को कॉल करती रहती है जब तक यह एक मान लौटाता है।

def parse
  Magicbars::Nodes::Template.new(parse_statements)
end
 
def parse_statements
  results = []
 
  while result = parse_statement
    results << result
  end
 
  results
end

parse_statement खुद सबसे पहले parse_content पर कॉल करता है और यदि वह कोई मान नहीं लौटाता है, तो वह parse_expression . को कॉल करता है ।

def parse_statement
  parse_content || parse_expression
end

क्या आपने देखा है कि parse_statement विधि बहुत हद तक statement के समान दिखने लगी है व्याकरण में नियम? यह वह जगह है जहां पहले से स्पष्ट रूप से व्याकरण लिखने के लिए समय निकालने से यह सुनिश्चित करने में बहुत मदद मिलती है कि हम सही रास्ते पर हैं।

इसके बाद, आइए parse_content को संशोधित करें विधि ताकि यह न केवल पहले टोकन को देखे। हम एक अतिरिक्त @position . की शुरुआत करके ऐसा करते हैं प्रारंभकर्ता में आवृत्ति चर और वर्तमान टोकन लाने के लिए इसका उपयोग करें।

attr_reader :tokens, :position
 
def initialize(tokens)
  @tokens = tokens
  @position = 0
end
 
# ...
 
def parse_content
  return unless token = tokens[position]
  return unless token[0] == :CONTENT
 
  @position += 1
 
  Magicbars::Nodes::Content.new(token[1])
end

parse_content विधि अब वर्तमान टोकन को देखती है और इसके प्रकार की जांच करती है। अगर यह एक Content है टोकन, यह स्थिति को बढ़ाता है (क्योंकि वर्तमान टोकन को सफलतापूर्वक पार्स किया गया था) और Content बनाने के लिए टोकन की सामग्री का उपयोग करता है नोड. यदि कोई वर्तमान टोकन नहीं है (क्योंकि हम टोकन के अंत में हैं) या प्रकार मेल नहीं खाता है, तो विधि जल्दी निकल जाती है और nil लौटाती है ।

बेहतर parse_content . के साथ जगह में विधि, आइए नए parse_expression . से निपटें विधि।

def parse_expression
  return unless token = tokens[position]
  return unless token[0] == :OPEN_EXPRESSION
 
  @position += 1
 
  identifier = parse_identifier
  arguments = parse_arguments
 
  if !tokens[position] || tokens[position][0] != :CLOSE
    raise "Unexpected token #{tokens[position][0]}. Expected :CLOSE."
  end
 
  @position += 1
 
  Magicbars::Nodes::Expression.new(identifier, arguments)
end

सबसे पहले, हम जांचते हैं कि एक मौजूदा टोकन है और इसका प्रकार OPEN_EXPRESSION है . यदि ऐसा है, तो हम अगले टोकन पर आगे बढ़ते हैं और parse_identifier पर कॉल करके पहचानकर्ता के साथ-साथ तर्कों को पार्स करते हैं और parse_arguments , क्रमश। दोनों विधियाँ संबंधित नोड्स लौटाएँगी और वर्तमान टोकन को आगे बढ़ाएँगी। जब यह हो जाता है, तो हम सुनिश्चित करते हैं कि वर्तमान टोकन मौजूद है और एक :CLOSE . है टोकन। यदि ऐसा नहीं है, तो हम एक त्रुटि उत्पन्न करते हैं। अन्यथा, हम नव निर्मित Expression . को वापस करने से पहले, एक आखिरी बार स्थिति को आगे बढ़ाते हैं नोड.

इस बिंदु पर, हम देखते हैं कि कुछ पैटर्न उभर कर आते हैं। हम कई बार अगले टोकन पर आगे बढ़ रहे हैं और हम यह भी जांच रहे हैं कि कोई मौजूदा टोकन है और उसका प्रकार है। क्योंकि उसके लिए कोड थोड़ा बोझिल है, आइए दो सहायक विधियों का परिचय दें।

def expect(*expected_tokens)
  upcoming = tokens[position, expected_tokens.size]
 
  if upcoming.map(&:first) == expected_tokens
    advance(expected_tokens.size)
    upcoming
  end
end
 
def advance(offset = 1)
  @position += offset
end

expect विधि टोकन प्रकारों की एक चर संख्या लेती है और उन्हें टोकन स्ट्रीम में अगले टोकन के विरुद्ध जांचती है। यदि वे सभी मेल खाते हैं, तो यह मेल खाने वाले टोकन से आगे बढ़ता है और उन्हें वापस कर देता है। advance विधि केवल @position को बढ़ा देती है दिए गए ऑफ़सेट द्वारा आवृत्ति चर।

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

def need(*required_tokens)
  upcoming = tokens[position, required_tokens.size]
  expect(*required_tokens) or raise "Unexpected tokens. Expected #{required_tokens.inspect} but got #{upcoming.inspect}"
end

इन सहायक विधियों का उपयोग करके, parse_content और parse_expression अब साफ और अधिक पठनीय हैं।

def parse_content
  if content = expect(:CONTENT)
    Magicbars::Nodes::Content.new(content[0][1])
  end
end
 
def parse_expression
  return unless expect(:OPEN_EXPRESSION)
 
  identifier = parse_identifier
  arguments = parse_arguments
 
  need(:CLOSE)
 
  Magicbars::Nodes::Expression.new(identifier, arguments)
end

अंत में, आइए parse_identifier को भी देखें और parse_arguments . सहायक विधियों के लिए धन्यवाद, parse_identifier विधि parse_content . जितनी सरल है तरीका। फर्क सिर्फ इतना है कि यह एक और नोड प्रकार देता है।

def parse_identifier
  if identifier = expect(:IDENTIFIER)
    Magicbars::Nodes::Identifier.new(identifier[0][1])
  end
end

parse_arguments को लागू करते समय विधि, हमने देखा कि यह लगभग parse_statements . के समान है तरीका। फर्क सिर्फ इतना है कि यह parse_identifier . को कॉल करता है parse_statement . के बजाय . हम एक और हेल्पर मेथड को शुरू करके डुप्लीकेट लॉजिक से छुटकारा पा सकते हैं।

def repeat(method)
  results = []
 
  while result = send(method)
    results << result
  end
 
  results
end

repeat विधि send . का उपयोग करती है दिए गए विधि नाम को तब तक कॉल करने के लिए जब तक कि यह एक नोड नहीं लौटाता। एक बार ऐसा होने पर, एकत्रित परिणाम (या केवल एक खाली सरणी) वापस कर दिए जाते हैं। इस सहायक के साथ, दोनों parse_statements और parse_arguments एक-पंक्ति विधियाँ बनें।

def parse_statements
  repeat(:parse_statement)
end
 
def parse_arguments
  repeat(:parse_identifier)
end

इन सभी परिवर्तनों के साथ, आइए टोकन स्ट्रीम को पार्स करने का प्रयास करें:

Magicbars::Parser.parse(tokens)
# => #<Magicbars::Nodes::Template:0x00007f91a602f910
#     @statements=
#      [#<Magicbars::Nodes::Content:0x00007f91a58802c8 @content="Welcome to ">,
#       #<Magicbars::Nodes::Expression:0x00007f91a602fcd0
#        @arguments=[],
#        @identifier=
#         #<Magicbars::Nodes::Identifier:0x00007f91a5880138 @value=:name>  >

इसे पढ़ना थोड़ा कठिन है, लेकिन वास्तव में, यह सही अमूर्त सिंटैक्स ट्री है। template नोड में एक Content है और एक Expression बयान। Content नोड का मान है "Welcome to " और Expression नोड का पहचानकर्ता IDENTIFIER है :name . के साथ नोड इसके मूल्य के रूप में।

ब्लॉक एक्सप्रेशन पार्स करना

हमारे पार्सर कार्यान्वयन को पूरा करने के लिए, हमें अभी भी ब्लॉक एक्सप्रेशन के पार्सिंग को लागू करना होगा। एक अनुस्मारक के रूप में, यहां वह टेम्प्लेट है जिसे हम पार्स करना चाहते हैं:

Welcome to {{name}}!
 
{{#if subscribed}}
  Thank you for subscribing to our mailing list.
{{else}}
  Please sign up for our mailing list to be notified about new articles!
{{/if}}
 
Your friends at {{company_name}}

ऐसा करने के लिए, आइए पहले एक BlockExpression introduce का परिचय दें नोड. हालांकि यह नोड थोड़ा अधिक डेटा संग्रहीत करता है, यह कुछ और नहीं करता है और इसलिए बहुत रोमांचक नहीं है।

module Magicbars
  module Nodes
    class BlockExpression
      attr_reader :identifier, :arguments, :statements, :inverse_statements
 
      def initialize(identifier, arguments, statements, inverse_statements)
        @identifier = identifier
        @arguments = arguments
        @statements = statements
        @inverse_statements = inverse_statements
      end
    end
  end
end

Expression की तरह नोड, यह पहचानकर्ता के साथ-साथ किसी भी तर्क को संग्रहीत करता है। इसके अतिरिक्त, यह ब्लॉक और इनवर्स ब्लॉक के स्टेटमेंट को भी स्टोर करता है।

व्याकरण को देखते हुए, हम देखते हैं कि ब्लॉक अभिव्यक्तियों को पार्स करने के लिए, हमें parse_statements में संशोधन करना होगा। parse_block_expression . पर कॉल के साथ विधि . यह अब व्याकरण के नियम जैसा ही दिखता है।

def parse_statement
  parse_content || parse_expression || parse_block_expression
end

parse_block_expression विधि अपने आप में थोड़ी अधिक जटिल है। लेकिन हमारे सहायक तरीकों के लिए धन्यवाद, यह अभी भी काफी पठनीय है।

def parse_block_expression
  return unless expect(:OPEN_BLOCK)
 
  identifier = parse_identifier
  arguments = parse_arguments
 
  need(:CLOSE)
 
  statements = parse_statements
 
  if expect(:OPEN_INVERSE, :CLOSE)
    inverse_statements = parse_statements
  end
 
  need(:OPEN_END_BLOCK)
 
  if identifier.value != parse_identifier.value
    raise("Error. Identifier in closing expression does not match identifier in opening expression")
  end
 
  need(:CLOSE)
 
  Magicbars::Nodes::BlockExpression.new(identifier, arguments, statements, inverse_statements)
end

पहला भाग parse_expression . से बहुत मिलता-जुलता है तरीका। यह प्रारंभिक ब्लॉक अभिव्यक्ति को पहचानकर्ता और तर्कों के साथ पार्स करता है। बाद में, यह parse_statements . को कॉल करता है ब्लॉक के अंदर पार्स करने के लिए।

एक बार यह हो जाने के बाद, हम एक {{else}} . की जांच करते हैं अभिव्यक्ति, जिसे OPEN_INVERSE . द्वारा पहचाना जाता है टोकन के बाद CLOSE टोकन। यदि दोनों टोकन मिलते हैं, तो हम parse_statements . को कॉल करते हैं फिर से उलटा ब्लॉक पार्स करने के लिए। अन्यथा, हम उस हिस्से को पूरी तरह से छोड़ देते हैं।

अंतिम बात के रूप में, हम सुनिश्चित करते हैं कि खुले ब्लॉक अभिव्यक्ति के समान पहचानकर्ता का उपयोग करके एक अंत ब्लॉक अभिव्यक्ति है। यदि पहचानकर्ता मेल नहीं खाते हैं, तो हम एक त्रुटि उत्पन्न करते हैं। अन्यथा, हम एक नया BlockExpression create बनाते हैं नोड और इसे वापस कर दें।

पार्सर को उन्नत ब्लॉक एक्सप्रेशन टेम्पलेट के टोकन के साथ कॉल करने से टेम्पलेट के लिए एएसटी वापस आ जाएगा। मैं यहां उदाहरण आउटपुट शामिल नहीं करूंगा, क्योंकि यह मुश्किल से पठनीय है। इसके बजाय, यहां जेनरेट किए गए एएसटी का एक दृश्य प्रतिनिधित्व है।

क्योंकि हम parse_statements को कॉल कर रहे हैं parse_block_expression . के अंदर , ब्लॉक और इनवर्स ब्लॉक दोनों में अधिक एक्सप्रेशन, ब्लॉक एक्सप्रेशन और साथ ही नियमित सामग्री शामिल हो सकती है।

यात्रा जारी है...

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

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


  1. रूबी में डेकोरेटर डिजाइन पैटर्न

    डेकोरेटर डिजाइन पैटर्न क्या है? और आप अपने रूबी प्रोजेक्ट्स में इस पैटर्न का उपयोग कैसे कर सकते हैं? डेकोरेटर डिज़ाइन पैटर्न नई क्षमताओं . जोड़कर किसी ऑब्जेक्ट को बेहतर बनाने में आपकी सहायता करता है इसमें बिना कक्षा बदले। आइए एक उदाहरण देखें! लॉगिंग और प्रदर्शन इस उदाहरण में हम रेस्ट-क्लाइंट जैस

  1. रूबी ट्रांसपोज़ विधि के साथ पंक्तियों को कॉलम में बदलें

    आज आप सीखेंगे कि रूबी ट्रांसपोज़ विधि का उपयोग करके रूबी में ग्रिड से कैसे निपटें। कल्पना कीजिए कि आपके पास एक पूर्ण ग्रिड है, मान लीजिए कि एक बहु-आयामी सरणी के रूप में एक 3×3 वर्ग है। और आप पंक्तियों को लेना और उन्हें स्तंभों में बदलना चाहते हैं । आप ऐसा क्यों करना चाहेंगे? क्लासिक गेम के लिए ए

  1. रूबी के साथ एक पार्सर कैसे बनाएं

    पार्सिंग स्ट्रिंग्स के एक गुच्छा को समझने और उन्हें किसी ऐसी चीज़ में बदलने की कला है जिसे हम समझ सकते हैं। आप रेगुलर एक्सप्रेशन का उपयोग कर सकते हैं, लेकिन वे हमेशा कार्य के लिए उपयुक्त नहीं होते हैं। उदाहरण के लिए, यह सामान्य ज्ञान है कि नियमित अभिव्यक्तियों के साथ HTML को पार्स करना शायद एक अच्छ