आप अपने टूल के बारे में जितना अधिक जानते हैं, एक डेवलपर के रूप में आप उतने ही बेहतर निर्णय लेंगे। यह अक्सर उपयोगी होता है - विशेष रूप से प्रदर्शन समस्याओं को डीबग करते समय - यह समझने के लिए कि रूबी वास्तव में आपके प्रोग्राम को चलाते समय क्या कर रहा है।
इस पोस्ट में हम एक सरल प्रोग्राम की यात्रा का अनुसरण करेंगे क्योंकि इसे लेक्स, पार्स और बाइटकोड में कंपाइल किया जाता है। हम हर कदम पर इंटरप्रेटर पर जासूसी करने के लिए रूबी द्वारा दिए गए टूल का उपयोग करेंगे।
चिंता न करें — भले ही आप विशेषज्ञ न हों, यह पोस्ट का पालन करना काफी आसान होना चाहिए। यह एक तकनीकी मैनुअल की तुलना में एक निर्देशित यात्रा अधिक है।
हमारे नमूना प्रोग्राम से मिलें
एक उदाहरण के रूप में, मैं एक एकल if/else स्टेटमेंट का उपयोग करने जा रहा हूँ। स्थान बचाने के लिए, मैं इसे टर्नरी ऑपरेटर का उपयोग करके लिखूंगा। लेकिन धोखा न खाएं, यह सिर्फ एक if/else है।
x > 100 ? 'foo' : 'bar'
जैसा कि आप देखेंगे, यहां तक कि इस तरह का एक सरल प्रोग्राम भी प्रोसेस होने पर काफी मात्रा में डेटा में अनुवादित हो जाता है।
नोट: इस पोस्ट के सभी उदाहरण रूबी (MRI) 2.2 में लिखे गए थे। यदि आप रूबी के अन्य कार्यान्वयन का उपयोग कर रहे हैं, तो वे शायद काम नहीं करेंगे।
टोकनाइज़िंग
इससे पहले कि रूबी इंटरप्रेटर आपका प्रोग्राम चला सके, उसे इसे कुछ हद तक फ्री-फॉर्म प्रोग्रामिंग भाषा से अधिक संरचित डेटा में परिवर्तित करना होगा।
पहला कदम प्रोग्राम को टुकड़ों में तोड़ना हो सकता है। इन टुकड़ों को टोकन कहा जाता है।
# This is a string "x > 1" # These are tokens ["x", ">", "1"]
रूबी स्टैंडर्ड लाइब्रेरी एक मॉड्यूल प्रदान करती है जिसे Ripper कहा जाता है जो हमें रूबी कोड को लगभग उसी तरह प्रोसेस करने देता है जैसे रूबी इंटरप्रेटर करता है।
नीचे दिए गए उदाहरण में हम अपने रूबी कोड पर tokenize मेथड का उपयोग कर रहे हैं। जैसा कि आप देख सकते हैं, यह टोकन की एक सरणी लौटाता है।
require 'ripper' Ripper.tokenize("x > 1 ? 'foo' : 'bar'") # => ["x", " ", ">", " ", "1", " ", "?", " ", "'", "foo", "'", " ", ":", " ", "'", "bar", "'"]
टोकनाइज़र काफी बेवकूफ है। आप इसे पूरी तरह से अमान्य रूबी खिला सकते हैं और यह फिर भी इसे टोकनाइज़ करेगा।
# bad code Ripper.tokenize("1var @= \/foobar`") # => ["1", "var"]
लेक्सिंग
लेक्सिंग टोकनाइज़ेशन से एक कदम आगे है। स्ट्रिंग को अभी भी टोकन में तोड़ा जाता है, लेकिन टोकन में अतिरिक्त डेटा जोड़ा जाता है।
नीचे दिए गए उदाहरण में हम अपने छोटे प्रोग्राम को लेक्स करने के लिए Ripper का उपयोग कर रहे हैं। जैसा कि आप देख सकते हैं, यह अब प्रत्येक टोकन को आइडेंटिफायर :on_ident, ऑपरेटर :on_op, इंटीजर :on_int, आदि के रूप में टैग कर रहा है।
require 'ripper' require 'pp' pp Ripper.lex("x > 100 ? 'foo' : 'bar'") # [[[1, 0], :on_ident, "x"], # [[1, 1], :on_sp, " "], # [[1, 2], :on_op, ">"], # [[1, 3], :on_sp, " "], # [[1, 4], :on_int, "100"], # [[1, 5], :on_sp, " "], # [[1, 6], :on_op, "?"], # [[1, 7], :on_sp, " "], # [[1, 8], :on_tstring_beg, "'"], # [[1, 9], :on_tstring_content, "foo"], # [[1, 12], :on_tstring_end, "'"], # [[1, 13], :on_sp, " "], # [[1, 14], :on_op, ":"], # [[1, 15], :on_sp, " "], # [[1, 16], :on_tstring_beg, "'"], # [[1, 17], :on_tstring_content, "bar"], # [[1, 20], :on_tstring_end, "'"]]
इस बिंदु पर अभी भी कोई वास्तविक सिंटैक्स जांच नहीं हो रही है। लेक्सर अमान्य कोड को खुशी से प्रोसेस करेगा।
पार्सिंग
अब जब रूबी ने कोड को अधिक प्रबंधनीय टुकड़ों में तोड़ दिया है, तो पार्सिंग शुरू करने का समय आ गया है।
पार्सिंग चरण के दौरान, रूबी टेक्स्ट को किसी चीज़ में बदल देता है जिसे एब्सट्रैक्ट सिंटैक्स ट्री, या AST कहा जाता है। एब्सट्रैक्ट सिंटैक्स ट्री आपके प्रोग्राम का मेमोरी में प्रतिनिधित्व है।
आप कह सकते हैं कि प्रोग्रामिंग भाषाएं सामान्य रूप से एब्सट्रैक्ट सिंटैक्स ट्री का वर्णन करने के अधिक उपयोगकर्ता-अनुकूल तरीके हैं।
require 'ripper' require 'pp' pp Ripper.sexp("x > 100 ? 'foo' : 'bar'") # [:program, # [[:ifop, # [:binary, [:vcall, [:@ident, "x", [1, 0]]], :>, [:@int, "100", [1, 4]]], # [:string_literal, [:string_content, [:@tstring_content, "foo", [1, 11]]]], # [:string_literal, [:string_content, [:@tstring_content, "foobar", [1, 19]]]]]]]
इस आउटपुट को पढ़ना आसान नहीं हो सकता है, लेकिन अगर आप इसे काफी देर तक घूरते हैं तो आप तरह से देख सकते हैं कि यह मूल प्रोग्राम से कैसे मैप होता है।
# Define a progam [:program, # Do an "if" operation [[:ifop, # Check the conditional (x > 100) [:binary, [:vcall, [:@ident, "x", [1, 0]]], :>, [:@int, "100", [1, 4]]], # If true, return "foo" [:string_literal, [:string_content, [:@tstring_content, "foo", [1, 11]]]], # If false, return "bar" [:string_literal, [:string_content, [:@tstring_content, "foobar", [1, 19]]]]]]]
इस बिंदु पर, रूबी इंटरप्रेटर ठीक से जानता है कि आप उससे क्या करना चाहते हैं। यह अभी आपका प्रोग्राम चला सकता था। और रूबी 1.9 से पहले, यह करता भी। लेकिन अब, एक और कदम है।
बाइटकोड में कंपाइल करना
एब्सट्रैक्ट सिंटैक्स ट्री को सीधे पार करने के बजाय, आजकल रूबी एब्सट्रैक्ट सिंटैक्स ट्री को निचले स्तर के बाइट कोड में कंपाइल करता है।
इस बाइट कोड को फिर रूबी वर्चुअल मशीन द्वारा चलाया जाता है।
हम RubyVM::InstructionSequence क्लास के माध्यम से वर्चुअल मशीन के आंतरिक कामकाज में झांक सकते हैं। नीचे दिए गए उदाहरण में, हम अपने नमूना प्रोग्राम को कंपाइल करते हैं और फिर इसे मानव-पठनीय बनाने के लिए डिसअसेंबल करते हैं।
puts RubyVM::InstructionSequence.compile("x > 100 ? 'foo' : 'bar'").disassemble # == disasm: <RubyVM::InstructionSequence:<compiled>@<compiled>>========== # 0000 trace 1 ( 1) # 0002 putself # 0003 opt_send_without_block <callinfo!mid:x, argc:0, FCALL|VCALL|ARGS_SIMPLE> # 0005 putobject 100 # 0007 opt_gt <callinfo!mid:>, argc:1, ARGS_SIMPLE> # 0009 branchunless 15 # 0011 putstring "foo" # 0013 leave # 0014 pop # 0015 putstring "bar" # 0017 leave
वाह! यह अचानक रूबी की तुलना में असेंबली भाषा की तरह अधिक दिखता है। आइए इसके माध्यम से कदम दर कदम चलें और देखें कि क्या हम इसका अर्थ समझ सकते हैं।
# Call the method `x` on self and save the result on the stack 0002 putself 0003 opt_send_without_block <callinfo!mid:x, argc:0, FCALL|VCALL|ARGS_SIMPLE> # Put the number 100 on the stack 0005 putobject 100 # Do the comparison (x > 100) 0007 opt_gt <callinfo!mid:>, argc:1, ARGS_SIMPLE> # If the comparison was false, go to line 15 0009 branchunless 15 # If the comparison was true, return "foo" 0011 putstring "foo" 0013 leave 0014 pop # Here's line 15. We jumped here if comparison was false. Return "bar" 0015 putstring "bar" 0017 leave
रूबी वर्चुअल मशीन (YARV) तब इन निर्देशों के माध्यम से कदम दर कदम चलती है और उन्हें निष्पादित करती है। बस इतना ही!
निष्कर्ष
यह रूबी इंटरप्रेटर के हमारे बहुत सरलीकृत, कार्टूनी दौरे को समाप्त करता है। मैंने आपको यहां जो टूल दिखाए हैं, उनके साथ रूबी द्वारा आपके प्रोग्राम की व्याख्या करने के तरीके से बहुत सारा अनुमान लगाना संभव है। मेरा मतलब है, AST से अधिक ठोस कुछ नहीं है। और अगली बार जब आप किसी अजीब प्रदर्शन समस्या से हैरान हों, तो बाइटकोड को देखने का प्रयास करें। यह शायद आपकी समस्या का समाधान नहीं करेगा, लेकिन यह आपका ध्यान इससे हटा सकता है। :)