हमें उम्मीद है कि आपने अपनी कॉफी के ऊपर अपने स्ट्रूपवाफल्स को गर्म कर लिया होगा क्योंकि आज हम चीजों को चिपचिपे स्ट्रूप (वह सिरप जो एक स्ट्रूपवाफेल के दो हिस्सों को एक साथ चिपकाते हैं) के साथ चिपका रहे हैं। हमारी श्रृंखला के पहले दो भागों में, हमने एक लेक्सर और एक पार्सर को बेक किया और अब, हम इंटरप्रेटर और ग्लूइंग चीजों को एक साथ जोड़ रहे हैं, उन पर स्ट्रूप डालकर।
सामग्री
ठीक है! आइए रसोई को पकाने के लिए तैयार करें और हमारी सामग्री को टेबल पर रख दें। हमारे दुभाषिया को अपना काम करने के लिए दो अवयवों या जानकारी के टुकड़ों की आवश्यकता होती है:पहले से उत्पन्न सार सिंटैक्स ट्री (एएसटी) और वह डेटा जिसे हम टेम्पलेट में एम्बेड करना चाहते हैं। हम इस डेटा को environment
. कहेंगे ।
एएसटी को पार करने के लिए, हम विज़िटर पैटर्न का उपयोग करके दुभाषिया को लागू करेंगे। एक आगंतुक (और इसलिए हमारा दुभाषिया) एक सामान्य विज़िट पद्धति को लागू करता है जो एक नोड को एक पैरामीटर के रूप में स्वीकार करता है, इस नोड को संसाधित करता है और संभावित रूप से visit
को कॉल करता है नोड के कुछ (या सभी) बच्चों के साथ फिर से विधि, जो वर्तमान नोड के लिए समझ में आता है।
module Magicbars
class Interpreter
attr_reader :root, :environment
def self.render(root, environment = {})
new(root, environment).render
end
def initialize(root, environment = {})
@root = root
@environment = environment
end
def render
visit(root)
end
def visit(node)
# Process node
end
end
end
जारी रखने से पहले, आइए एक छोटा Magicbars.render
भी बनाएं वह विधि जो किसी टेम्पलेट और परिवेश को स्वीकार करती है और रेंडर किए गए टेम्पलेट को आउटपुट करती है।
module Magicbars
def self.render(template, environment = {})
tokens = Lexer.tokenize(template)
ast = Parser.parse(tokens)
Interpreter.render(ast, environment)
end
end
इसके साथ, हम हाथ से एएसटी का निर्माण किए बिना दुभाषिया का परीक्षण करने में सक्षम होंगे।
Magicbars.render('Welcome to {{name}}', name: 'Ruby Magic')
# => nil
कोई आश्चर्य नहीं, यह वर्तमान में कुछ भी वापस नहीं करता है। तो चलिए visit
को लागू करना शुरू करते हैं तरीका। एक त्वरित अनुस्मारक के रूप में, इस टेम्पलेट के लिए एएसटी कैसा दिखता है।
इस टेम्पलेट के लिए, हमें चार अलग-अलग नोड प्रकारों को संसाधित करना होगा:Template
, Content
, Expression
, और Identifier
. ऐसा करने के लिए, हम बस एक बड़ा case
डाल सकते हैं हमारे visit
. के अंदर स्टेटमेंट तरीका। हालाँकि, यह बहुत जल्दी अपठनीय हो जाएगा। इसके बजाय, आइए अपने कोड को थोड़ा अधिक व्यवस्थित और पठनीय रखने के लिए रूबी की मेटाप्रोग्रामिंग क्षमताओं का उपयोग करें।
module Magicbars
class Interpreter
# ...
def visit(node)
short_name = node.class.to_s.split('::').last
send("visit_#{short_name}", node)
end
end
end
विधि एक नोड को स्वीकार करती है, उसके वर्ग का नाम प्राप्त करती है, और उसमें से किसी भी मॉड्यूल को हटा देती है (यदि आप ऐसा करने के विभिन्न तरीकों में रुचि रखते हैं, तो स्ट्रिंग्स की सफाई पर हमारा लेख देखें)। बाद में, हम send
. का उपयोग करते हैं इस विशिष्ट प्रकार के नोड को संभालने वाली विधि को कॉल करने के लिए। प्रत्येक प्रकार के लिए विधि का नाम डिमॉड्यूलाइज्ड वर्ग के नाम और visit_
से बना होता है उपसर्ग। विधि नामों में बड़े अक्षरों का होना थोड़ा असामान्य है, लेकिन यह विधि के इरादे को बहुत स्पष्ट करता है।
module Magicbars
class Interpreter
# ...
def visit_Template(node)
# Process template nodes
end
def visit_Content(node)
# Process content nodes
end
def visit_Expression(node)
# Process expression nodes
end
def visit_Identifier(node)
# Process identifier nodes
end
end
end
आइए visit_Template
. को लागू करके शुरुआत करें तरीका। इसे बस सभी statements
. को प्रोसेस करना चाहिए नोड के और परिणामों में शामिल हों।
def visit_Template(node)
node.statements.map { |statement| visit(statement) }.join
end
इसके बाद, आइए visit_Content
देखें तरीका। एक सामग्री नोड के रूप में बस एक स्ट्रिंग लपेटता है, विधि जितनी सरल हो जाती है।
def visit_Content(node)
node.content
end
अब, चलिए visit_Expression
पर चलते हैं विधि जहां वास्तविक मूल्य के साथ प्लेसहोल्डर का प्रतिस्थापन होता है।
def visit_Expression(node)
key = visit(node.identifier)
environment.fetch(key, '')
end
और अंत में, visit_Expression
. के लिए यह जानने के लिए कि पर्यावरण से कौन सी कुंजी प्राप्त करनी है, आइए visit_Identifier
को लागू करें विधि।
def visit_Identifier(node)
node.value
end
इन चार विधियों के साथ, जब हम टेम्पलेट को फिर से प्रस्तुत करने का प्रयास करते हैं तो हमें वांछित परिणाम मिलता है।
Magicbars.render('Welcome to {{name}}', name: 'Ruby Magic')
# => Welcome to Ruby Magic
ब्लॉक एक्सप्रेशन की व्याख्या करना
हमने एक साधारण gsub
. को लागू करने के लिए बहुत सारे कोड लिखे हैं कर सकता है। तो चलिए एक अधिक जटिल उदाहरण की ओर बढ़ते हैं।
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}}
एक अनुस्मारक के रूप में, संबंधित एएसटी कैसा दिखता है।
केवल एक नोड प्रकार है जिसे हम अभी तक संभाल नहीं पाए हैं। यह है visit_BlockExpression
नोड. एक तरह से यह visit_Expression
. के समान है नोड, लेकिन मान के आधार पर यह या तो statements
. को संसाधित करना जारी रखता है या inverse_statements
BlockExpression
. का नोड.
def visit_BlockExpression(node)
key = visit(node.identifier)
if environment[key]
node.statements.map { |statement| visit(statement) }.join
else
node.inverse_statements.map { |statement| visit(statement) }.join
end
end
विधि को देखते हुए, हम देखते हैं कि दोनों शाखाएँ बहुत समान हैं, और वे visit_Template
के समान भी दिखती हैं। तरीका। वे सभी Array
. के सभी नोड्स के विज़िटिंग को संभालते हैं , तो आइए एक visit_Array
निकालें चीजों को थोड़ा साफ करने का तरीका।
def visit_Array(nodes)
nodes.map { |node| visit(node) }
end
नई विधि के साथ, हम visit_Template
. से कुछ कोड हटा सकते हैं और visit_BlockExpression
तरीके।
def visit_Template(node)
visit(node.statements).join
end
def visit_BlockExpression(node)
key = visit(node.identifier)
if environment[key]
visit(node.statements).join
else
visit(node.inverse_statements).join
end
end
अब जबकि हमारा दुभाषिया सभी नोड प्रकारों को संभालता है, आइए जटिल टेम्पलेट को प्रस्तुत करने का प्रयास करें।
Magicbars.render(template, { name: 'Ruby Magic', subscribed: true, company_name: 'AppSignal' })
# => Welcome to Ruby Magic!
#
#
# Please sign up for our mailing list to be notified about new articles!
#
#
# Your friends at AppSignal
वह लगभग सही दिखता है। लेकिन करीब से देखने पर, हम देखते हैं कि संदेश हमें मेलिंग सूची के लिए साइन अप करने के लिए प्रेरित कर रहा है, भले ही हमने subscribed: true
प्रदान किया हो पर्यावरण में। यह सही नहीं लगता...
सहायक विधियों के लिए समर्थन जोड़ना
टेम्प्लेट पर पीछे मुड़कर देखने पर, हम देखते हैं कि एक if
. है ब्लॉक अभिव्यक्ति में। subscribed
. का मान देखने के बजाय परिवेश में, visit_BlockExpression
if
. का मान देख रहा है . चूंकि यह वातावरण में मौजूद नहीं है, कॉल रिटर्न nil
, जो गलत है।
हम यहां रुक सकते हैं और घोषणा कर सकते हैं कि हम हैंडलबार्स की नकल करने की कोशिश नहीं कर रहे हैं, लेकिन मूंछें, और if
से छुटकारा पाएं टेम्पलेट में, जो हमें वांछित परिणाम देगा।
Welcome to {{name}}!
{{#subscribed}}
Thank you for subscribing to our mailing list.
{{else}}
Please sign up for our mailing list to be notified about new articles!
{{/subscribed}}
Your friends at {{company_name}}
लेकिन जब हम मस्ती कर रहे हैं तो रुकें क्यों? आइए अतिरिक्त मील जाएं और सहायक विधियों को लागू करें। वे अन्य चीजों के लिए भी काम आ सकते हैं।
आइए सरल अभिव्यक्तियों के लिए एक सहायक विधि समर्थन जोड़कर शुरू करें। हम एक reverse
जोड़ेंगे सहायक, जो इसे पारित तारों को उलट देता है। इसके अलावा, हम एक debug
जोड़ेंगे विधि जो हमें दिए गए मान का वर्ग नाम बताती है।
def helpers
@helpers ||= {
reverse: ->(value) { value.to_s.reverse },
debug: ->(value) { value.class }
}
end
हम इन हेल्पर्स को लागू करने के लिए साधारण लैम्ब्डा का उपयोग करते हैं और उन्हें हैश में स्टोर करते हैं ताकि हम उन्हें उनके नाम से देख सकें।
इसके बाद, आइए visit_Expression
को संशोधित करें परिवेश में मूल्य देखने की कोशिश करने से पहले एक सहायक लुकअप करने के लिए।
def visit_Expression(node)
key = visit(node.identifier)
if helper = helpers[key]
arguments = visit(node.arguments).map { |k| environment[k] }
return helper.call(*arguments)
end
environment[key]
end
यदि दिए गए पहचानकर्ता से मेल खाने वाला कोई सहायक है, तो विधि सभी तर्कों पर जाएगी और उनके लिए मूल्यों को देखने का प्रयास करेगी। बाद में, यह विधि को कॉल करेगा और सभी मानों को तर्क के रूप में पारित करेगा।
Magicbars.render('Welcome to {{reverse name}}', name: 'Ruby Magic')
# => Welcome to cigaM ybuR
Magicbars.render('Welcome to {{debug name}}', name: 'Ruby Magic')
# => Welcome to String
उस जगह के साथ, आइए अंत में एक if
. लागू करें और एक unless
सहायक। तर्कों के अलावा, हम उन्हें दो लैम्ब्डा देंगे ताकि वे तय कर सकें कि हमें नोड के statements
की व्याख्या जारी रखनी चाहिए या नहीं या inverse_statements
।
def helpers
@helpers ||= {
if: ->(value, block:, inverse_block:) { value ? block.call : inverse_block.call },
unless: ->(value, block:, inverse_block:) { value ? inverse_block.call : block.call },
# ...
}
end
visit_BlockExpression
. में आवश्यक परिवर्तन जैसा हमने visit_Expression
. के साथ किया था, वैसा ही है , केवल इस बार, हम दो लैम्ब्डा भी पास करते हैं।
def visit_BlockExpression(node)
key = visit(node.identifier)
if helper = helpers[key]
arguments = visit(node.arguments).map { |k| environment[k] }
return helper.call(
*arguments,
block: -> { visit(node.statements).join },
inverse_block: -> { visit(node.inverse_statements).join }
)
end
if environment[key]
visit(node.statements).join
else
visit(node.inverse_statements).join
end
end
और इससे हमारी बेकिंग हो जाती है! हम उस जटिल टेम्पलेट को प्रस्तुत कर सकते हैं जिसने इस यात्रा को लेक्सर्स, पार्सर्स और दुभाषियों की दुनिया में शुरू किया।
Magicbars.render(template, { name: 'Ruby Magic', subscribed: true, company_name: 'AppSignal' })
# => Welcome to Ruby Magic!
#
#
# Thank you for subscribing to our mailing list.
#
#
# Your friends at AppSignal
केवल सतह को खरोंचना
इस तीन-भाग श्रृंखला में, हमने एक टेम्प्लेटिंग भाषा बनाने की मूल बातें शामिल की हैं। इन अवधारणाओं का उपयोग व्याख्या की गई प्रोग्रामिंग भाषाओं (जैसे रूबी) को बनाने के लिए भी किया जा सकता है। बेशक, हमने कुछ चीजों पर प्रकाश डाला (जैसे उचित त्रुटि प्रबंधन 🙀) और केवल आज की प्रोग्रामिंग भाषाओं के आधार की सतह को खरोंच दिया।
हमें उम्मीद है कि आपने श्रृंखला का आनंद लिया है और यदि आप इससे अधिक चाहते हैं, तो रूबी मैजिक सूची की सदस्यता लें। यदि आप अब स्ट्रूपवाफल्स के भूखे हैं, तो हमें एक पंक्ति दें और हम आपको उनसे भी ईंधन भरने में सक्षम हो सकते हैं!