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

रूबी में एक नई प्रोग्रामिंग भाषा का निर्माण:दुभाषिया

<ब्लॉकक्वॉट>

Github पर पूर्ण स्रोत

स्टॉफ़ल प्रोग्रामिंग भाषा का पूर्ण कार्यान्वयन GitHub पर उपलब्ध है। अगर आपको बग मिलते हैं या आपके कोई प्रश्न हैं, तो बेझिझक कोई समस्या खोलें।

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

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

मैं दुभाषिया के कार्यान्वयन को दो भागों में दिखाने और समझाने जा रहा हूँ। इस पहले भाग में, हम मूल बातें काम करने जा रहे हैं:चर, सशर्त, यूनरी और बाइनरी ऑपरेटर, डेटा प्रकार, और कंसोल पर प्रिंटिंग। हम अपने दुभाषिया को लागू करने पर दूसरी और आखिरी पोस्ट के लिए अधिक भावपूर्ण सामग्री (फ़ंक्शन परिभाषा, फ़ंक्शन कॉलिंग, लूप, आदि) आरक्षित कर रहे हैं।

लेक्सर और पार्सर का एक त्वरित पुनर्कथन

इससे पहले कि हम इसमें गोता लगाएँ और दुभाषिया को लागू करना शुरू करें, आइए जल्दी से खुद को याद दिलाएँ कि हमने इस श्रृंखला की पिछली पोस्टों में क्या किया था। सबसे पहले, हमने लेक्सर बनाया, जो कच्चे स्रोत कोड को टोकन में बदल देता है। फिर, हमने पार्सर को लागू किया, जो एक ट्री स्ट्रक्चर (एएसटी) में टोकन को मॉर्फ करने के लिए जिम्मेदार घटक है। संक्षेप में, अब तक हमने जो परिवर्तन देखे हैं, वे ये हैं:

राज्य 0:स्रोत

my_var = 1

राज्य 1:Lexer कच्चे स्रोत कोड को टोकन में बदल देता है

[:identifier, :'=', :number]

राज्य 2:पार्सर टोकन को एक सार सिंटेक्स ट्री में बदल देता है

रूबी में एक नई प्रोग्रामिंग भाषा का निर्माण:दुभाषिया

इट्स ऑल अबाउट वॉकिंग

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

जैसा कि हमने इस श्रृंखला के पिछले भागों में किया था, हम एक उदाहरण कार्यक्रम को संभालने में शामिल कोड की सभी महत्वपूर्ण पंक्तियों के माध्यम से कार्यान्वयन का पता लगाने जा रहे हैं। स्टॉफ़ल कोड के जिस अंश की हमें व्याख्या करनी है, वह निम्नलिखित है:

num = -2
if num > 0
  println("The number is greater than zero.")
else
  println("The number is less than or equal to zero.")
end

यह उसी कार्यक्रम के लिए निर्मित एएसटी है:

रूबी में एक नई प्रोग्रामिंग भाषा का निर्माण:दुभाषिया

हमारे चलने का पहला चरण

जैसा कि आपको शायद इस श्रृंखला की पिछली पोस्ट से याद होगा, स्टॉफ़ल एएसटी का मूल हमेशा एक AST::Program होता है। नोड. इस जड़ में आम तौर पर कई बच्चे होते हैं। उनमें से कुछ उथले होंगे (एक साधारण चर असाइनमेंट के लिए उत्पादित एएसटी के बारे में सोचें)। अन्य बच्चे काफी गहरे उप-वृक्षों की जड़ हो सकते हैं (इसके शरीर के अंदर कई रेखाओं के साथ एक लूप के बारे में सोचें)। यह रूबी कोड है जिसे हमें एएसटी के माध्यम से चलना शुरू करने की आवश्यकता है जो हमारे दुभाषिया को पारित किया गया था:

module Stoffle
  class Interpreter
    attr_reader :program, :output, :env

    def initialize
      @output = []
      @env = {}
    end

    def interpret(ast)
      @program = ast

      interpret_nodes(program.expressions)
    end

    private

    def interpret_nodes(nodes)
      last_value = nil

      nodes.each do |node|
        last_value = interpret_node(node)
      end

      last_value
    end

    def interpret_node(node)
      interpreter_method = "interpret_#{node.type}"
      send(interpreter_method, node)
    end

    #...

  end
end

जब कोई नया Interpreter तत्काल है, शुरू से ही हम दो आवृत्ति चर बनाते हैं:@output और @env . पूर्व की जिम्मेदारी कालानुक्रमिक क्रम में, हमारे कार्यक्रम द्वारा मुद्रित की गई हर चीज को संग्रहीत करना है। स्वचालित परीक्षण या डिबगिंग लिखते समय इस जानकारी को हाथ में रखना बहुत उपयोगी होता है। @env . की जिम्मेदारी थोड़ा अलग है। हमने इसे "पर्यावरण" के संदर्भ के रूप में नाम दिया है। जैसा कि नाम से पता चलता है, इसका कार्य हमारे चल रहे कार्यक्रम की स्थिति को बनाए रखना है। इसका एक कार्य एक पहचानकर्ता (जैसे, एक चर नाम) और उसके वर्तमान मूल्य के बीच बंधन को लागू करना होगा।

#interpret_nodes रूट नोड के सभी बच्चों के माध्यम से विधि लूप (AST::Program ) फिर, यह #interpret_node . को कॉल करता है प्रत्येक व्यक्तिगत नोड के लिए।

#interpret_node सरल है लेकिन फिर भी दिलचस्प है। यहां, हम वर्तमान में हाथ में नोड प्रकार को संभालने के लिए उपयुक्त विधि को कॉल करने के लिए रूबी मेटाप्रोग्रामिंग का थोड़ा सा उपयोग करते हैं। उदाहरण के लिए, AST::VarBinding . के लिए नोड, #interpret_var_binding विधि वह है जिसे कॉल किया जाता है।

हमेशा, हमें वैरिएबल के बारे में बात करनी है

हम जिस उदाहरण प्रोग्राम से गुजर रहे हैं उसके एएसटी में पहला नोड एक AST::VarBinding है। . इसका @left एक AST::Identifier है , और इसका @right एक AST::UnaryOperator है . आइए एक चर बाइंडिंग की व्याख्या करने के लिए जिम्मेदार विधि पर एक नज़र डालें:

def interpret_var_binding(var_binding)
  env[var_binding.var_name_as_str] = interpret_node(var_binding.right)
end

जैसा कि आप देख सकते हैं, यह काफी सीधा है। हम @env . में एक की-वैल्यू पेयर जोड़ते हैं (या ओवरराइट करते हैं) हैश।

कुंजी चर का नाम है (#var_name_as_str var_binding.left.name . के बराबर एक सहायक विधि है ) फिलहाल, सभी चर वैश्विक हैं। हम अगले पोस्ट में स्कोपिंग को संभालेंगे।

मान असाइनमेंट के दाईं ओर के व्यंजक की व्याख्या का परिणाम है। ऐसा करने के लिए, हम #interpret_node . का उपयोग करते हैं फिर से। चूँकि हमारे पास एक AST::UnaryOperator . है दाईं ओर, अगली विधि जिसे कॉल किया जाता है वह है #interpret_unary_operator :

def interpret_unary_operator(unary_op)
  case unary_op.operator
  when :'-'
    -(interpret_node(unary_op.operand))
  else # :'!'
    !(interpret_node(unary_op.operand))
  end
end

स्टॉफ़ल के समर्थित यूनरी ऑपरेटरों के शब्दार्थ (- और ! ) रूबी में समान हैं। परिणामस्वरूप, कार्यान्वयन आसान नहीं हो सकता:हम रूबी के - . को लागू करते हैं संकार्य की व्याख्या के परिणाम के लिए संचालिका। सामान्य संदिग्ध, #interpret_node , यहाँ फिर से प्रकट होता है। जैसा कि आप हमारे कार्यक्रम के एएसटी से याद कर सकते हैं, - . के लिए संकार्य एक AST::Number है (संख्या 2 ) इसका मतलब है कि हमारा अगला पड़ाव #interpret_number . पर है :

def interpret_number(number)
  number.value
end

#interpret_number . का क्रियान्वयन केक का एक टुकड़ा है। रूबी फ्लोट को संख्या अक्षर के प्रतिनिधित्व के रूप में अपनाने का हमारा निर्णय (यह लेक्सर में होता है!) यहां भुगतान करता है। @value AST::Number . का नोड में पहले से ही संख्याओं का हमारा वांछित आंतरिक प्रतिनिधित्व है, इसलिए हम इसे अभी पुनः प्राप्त करते हैं।

इसके साथ, हम AST::Program . के पहले प्रत्यक्ष बच्चे की व्याख्या करना समाप्त कर देते हैं . अब, हमारे कार्यक्रम की व्याख्या करने के लिए, हमें इसके अन्य, अधिक बालों वाले, बच्चे को संभालना होगा:एक प्रकार का नोड AST::Conditional

नियम और शर्तें लागू हो सकती हैं

वापस #interpret_nodes . में , हमारा सबसे अच्छा दोस्त #interpret_node AST::Program . के अगले प्रत्यक्ष बच्चे की व्याख्या करने के लिए फिर से कॉल किया जाता है ।

def interpret_nodes(nodes)
  last_value = nil

  nodes.each do |node|
    last_value = interpret_node(node)
  end

  last_value
end

AST::Conditional . की व्याख्या करने के लिए उत्तरदायी विधि #interpret_conditional है . हालांकि, इस पर एक नज़र डालने से पहले, आइए AST::Conditional के कार्यान्वयन की समीक्षा करके अपनी यादों को ताज़ा करें स्वयं:

class Stoffle::AST::Conditional < Stoffle::AST::Expression
  attr_accessor :condition, :when_true, :when_false

  def initialize(cond_expr = nil, true_block = nil, false_block = nil)
    @condition = cond_expr
    @when_true = true_block
    @when_false = false_block
  end

  def ==(other)
    children == other&.children
  end

  def children
    [condition, when_true, when_false]
  end
end

ठीक है, तो @condition एक अभिव्यक्ति रखता है जो या तो सत्य या असत्य होगा; @when_true @condition . के मामले में निष्पादित होने के लिए एक या अधिक अभिव्यक्तियों के साथ एक ब्लॉक रखता है सत्य है, और @when_false (ELSE क्लॉज) @condition . के मामले में चलाने के लिए ब्लॉक रखता है झूठा होता है।

अब, आइए एक नजर डालते हैं #interpret_condition . पर :

def interpret_conditional(conditional)
  evaluated_cond = interpret_node(conditional.condition)

  # We could implement the line below in a shorter way, but better to be explicit about truthiness in Stoffle.
  if evaluated_cond == nil || evaluated_cond == false
    return nil if conditional.when_false.nil?

    interpret_nodes(conditional.when_false.expressions)
  else
    interpret_nodes(conditional.when_true.expressions)
  end
end

स्टॉफ़ल में सत्यता रूबी के समान ही है। दूसरे शब्दों में, स्टॉफ़ल में, केवल nil और false झूठे हैं। किसी शर्त के लिए कोई अन्य इनपुट सत्य है।

हम पहले conditional.condition . द्वारा धारित व्यंजक की व्याख्या करके स्थिति का मूल्यांकन करते हैं . आइए अपने कार्यक्रम के एएसटी पर फिर से एक नज़र डालते हैं यह पता लगाने के लिए कि हम किस नोड के साथ काम कर रहे हैं:

रूबी में एक नई प्रोग्रामिंग भाषा का निर्माण:दुभाषिया

यह पता चला है कि हमारे पास एक AST::BinaryOperator . है (> num > 0 . में उपयोग किया जाता है ) ठीक है, फिर से वही रास्ता है:पहला #interpret_node , जो #interpret_binary_operator . को कॉल करता है इस बार:

def interpret_binary_operator(binary_op)
  case binary_op.operator
  when :and
    interpret_node(binary_op.left) && interpret_node(binary_op.right)
  when :or
    interpret_node(binary_op.left) || interpret_node(binary_op.right)
  else
    interpret_node(binary_op.left).send(binary_op.operator, interpret_node(binary_op.right))
  end
end

हमारे लॉजिकल ऑपरेटर्स (and और or ) को बाइनरी ऑपरेटर माना जा सकता है, इसलिए हम उन्हें यहां भी हैंडल करते हैं। चूँकि उनका सिमेंटिक रूबी के && . के बराबर है और || , कार्यान्वयन सादा नौकायन है, जैसा कि आप ऊपर देख सकते हैं।

आगे उस पद्धति का भाग है जिसमें हम सबसे अधिक रुचि रखते हैं; यह खंड अन्य सभी बाइनरी ऑपरेटरों को संभालता है (> . सहित) ) यहां, हम अपने पक्ष में रूबी की गतिशीलता का लाभ उठा सकते हैं और एक बहुत ही संक्षिप्त समाधान के साथ आ सकते हैं। रूबी में, बाइनरी ऑपरेटर एक ऑपरेशन में भाग लेने वाली वस्तुओं में विधियों के रूप में उपलब्ध हैं:

-2 > 0           # is equivalent to
-2.send(:'>', 0) # this
# and the following line would be a general solution,
# very similar to what we have in the interpreter
operand_1.send(binary_operator, operand_2)
<ब्लॉकक्वॉट>

बाइनरी ऑपरेटर्स का वर्बोज़ इम्प्लीमेंटेशन

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

यदि आप कभी भी खुद को भाषा डिजाइनर/कार्यान्वयनकर्ता के रूप में ऐसी स्थिति में पाते हैं, तो आप हमेशा एक सरल (लेकिन उस सुरुचिपूर्ण नहीं) समाधान पर वापस आ सकते हैं:स्विच निर्माण का उपयोग करना। हमारे मामले में, कार्यान्वयन कुछ इस तरह दिखेगा:

# ... inside #interpret_binary_operator ...

case binary_op.operator
when :'+'
  interpret_node(binary_op.left) + interpret_node(binary_op.right)
# ... other operators
end

#interpret_conditional पर वापस जाने से पहले , आइए यह सुनिश्चित करने के लिए एक त्वरित चक्कर लगाएं कि कुछ भी अनदेखा नहीं किया गया है। यदि आपको वह प्रोग्राम याद है जिसकी हम व्याख्या कर रहे हैं, तो num चर का उपयोग तुलना में किया जाता है (बाइनरी ऑपरेटर का उपयोग करके > ) हमने अभी एक साथ एक्सप्लोर किया। हमने बाएं ऑपरेंड को कैसे पुनः प्राप्त किया (यानी, num . में संग्रहीत मान चर) उस तुलना का? इसके लिए जिम्मेदार तरीका है #interpret_identifier , और इसका क्रियान्वयन आसान-पेसी है:

def interpret_identifier(identifier)
  if env.has_key?(identifier.name)
    env[identifier.name]
  else
    # Undefined variable.
    raise Stoffle::Error::Runtime::UndefinedVariable.new(identifier.name)
  end
end

अब, #interpret_conditional पर वापस जाएं . हमारे छोटे से कार्यक्रम के मामले में, स्थिति का मूल्यांकन रूबी false . के रूप में किया जाता है मूल्य। हम इस मान का उपयोग यह निर्धारित करने के लिए करते हैं कि हमें सशर्त संरचना की IF या ELSE शाखा को निष्पादित करना है या नहीं। हम ईएलएसई शाखा की व्याख्या करने के लिए आगे बढ़ते हैं, जिसका संबद्ध कोड ब्लॉक conditional.when_false में संग्रहीत है। . यहाँ, हमारे पास एक AST::Block है , जो हमारे AST (AST::Program . के रूट नोड से बहुत मिलता-जुलता है ) इसी तरह, ब्लॉक में संभावित रूप से अभिव्यक्तियों का एक समूह होता है जिसे व्याख्या करने की आवश्यकता होती है। इस उद्देश्य के लिए, हम #interpret_nodes . का भी उपयोग करते हैं ।

def interpret_conditional(conditional)
  evaluated_cond = interpret_node(conditional.condition)

  # We could implement the line below in a shorter way, but better to be explicit about truthiness in Stoffle.
  if evaluated_cond == nil || evaluated_cond == false
    return nil if conditional.when_false.nil?

    interpret_nodes(conditional.when_false.expressions)
  else
    interpret_nodes(conditional.when_true.expressions)
  end
end

अगला एएसटी नोड जिसे हमें संभालना है वह है AST::FunctionCall . फ़ंक्शन कॉल की व्याख्या करने के लिए जिम्मेदार विधि है #interpret_function_call :

def interpret_function_call(fn_call)
  return if println(fn_call)
end

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

def println(fn_call)
  return false if fn_call.function_name_as_str != 'println'

  result = interpret_node(fn_call.args.first).to_s
  output << result
  puts result
  true
end

हमारे AST::FunctionCall . का पहला और एकमात्र तर्क एक AST::String है , जिसे #interpret_string . द्वारा नियंत्रित किया जाता है :

def interpret_string(string)
  string.value
end

#interpret_string . में , हमारे पास #interpret_number . का ठीक वैसा ही मामला है . एक AST::String पहले से ही उपयोग के लिए तैयार रूबी स्ट्रिंग मान रखता है, इसलिए हमें इसे पुनः प्राप्त करना होगा।

अब, #println पर वापस जाएं :

def println(fn_call)
  return false if fn_call.function_name_as_str != 'println'

  result = interpret_node(fn_call.args.first).to_s
  output << result
  puts result
  true
end

result . में फ़ंक्शन तर्क (रूबी स्ट्रिंग में कनवर्ट) को संग्रहीत करने के बाद , हमारे पास पूरा करने के लिए दो और चरण हैं। सबसे पहले, हम जो प्रिंट करने वाले हैं उसे हम कंसोल में @output . में स्टोर करते हैं . जैसा कि पहले बताया गया है, यहां विचार आसानी से निरीक्षण करने में सक्षम होना है कि क्या मुद्रित किया गया था (और किस क्रम में)। इसे हाथ में रखने से दुभाषिया डिबगिंग या परीक्षण करते समय हमारा जीवन आसान हो जाता है। अंत में, कंसोल पर कुछ प्रिंट करने को लागू करने के लिए, हम रूबी के puts . का उपयोग करते हैं ।

निष्पादन के मामले

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

#!/usr/bin/env ruby

require_relative '../lib/stoffle'

path = ARGV[0]
source = File.read(path)
lexer = Stoffle::Lexer.new(source)
parser = Stoffle::Parser.new(lexer.start_tokenization)
interpreter = Stoffle::Interpreter.new

interpreter.interpret(parser.parse)

exit(0)
<ब्लॉकक्वॉट>

टिप: स्टॉफ़ल के दुभाषिया का कहीं से भी उपयोग करने के लिए, अपने PATH में एक्ज़ीक्यूटेबल जोड़ना याद रखें।

अंत में हमारे कार्यक्रम को चलाने का समय आ गया है। यदि सब कुछ ठीक से काम करता है, तो हमें कंसोल पर मुद्रित स्ट्रिंग "संख्या शून्य से कम या बराबर है" देखना चाहिए। जब हम दुभाषिया चलाते हैं तो ठीक यही होता है:

रूबी में एक नई प्रोग्रामिंग भाषा का निर्माण:दुभाषिया

<ब्लॉकक्वॉट>

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

रैपिंग अप

इस पोस्ट में, हमने स्टॉफ़ल के दुभाषिया की शुरुआत देखी। हमने भाषा की कुछ बुनियादी बातों को संभालने के लिए इसके लिए पर्याप्त दुभाषिया लागू किया:चर, सशर्त, यूनरी और बाइनरी ऑपरेटर, डेटा प्रकार, और कंसोल पर प्रिंटिंग। दुभाषिया पर अगले और अंतिम भाग में, हम अपनी छोटी खिलौना भाषा को डिज़ाइन के अनुसार काम करने के लिए आवश्यक शेष बिट्स से निपटेंगे:चर स्कोपिंग, फ़ंक्शन परिभाषा, फ़ंक्शन कॉलिंग और लूप। मुझे आशा है कि आपको लेख पढ़ने में मज़ा आया (मुझे निश्चित रूप से इसे लिखने में मज़ा आया!), और हम जल्द ही श्रृंखला की अगली पोस्ट में आपसे मिलेंगे!


  1. रूबी में स्थैतिक विश्लेषण

    मान लें कि आप अपने सभी तरीकों को खोजने के लिए अपने स्रोत कोड को पार्स करना चाहते हैं, जहां वे परिभाषित हैं और वे कौन से तर्क लेते हैं। आप यह कैसे कर सकते हैं? आपका पहला विचार इसके लिए एक रेगेक्सपी लिखना हो सकता है... लेकिन क्या कोई बेहतर तरीका है? हाँ! स्थिर विश्लेषण एक ऐसी तकनीक है जिसका उपय

  1. रूबी नेटवर्क प्रोग्रामिंग

    क्या आप रूबी में कस्टम नेटवर्क क्लाइंट और सर्वर बनाना चाहते हैं? या बस समझें कि यह कैसे काम करता है? फिर आपको सॉकेट से निपटना होगा। रूबी नेटवर्क प्रोग्रामिंग . के इस दौरे में मेरे साथ शामिल हों मूल बातें सीखने के लिए, और रूबी का उपयोग करके अन्य सर्वर और क्लाइंट से बात करना शुरू करें! तो सॉकेट क्य

  1. प्रोग्रामिंग भाषा प्रभाव ग्राफ की कल्पना करें

    Gephi और Sigma.js के साथ एक नेटवर्क विज़ुअलाइज़ेशन ट्यूटोरियल आज हम जो बना रहे हैं उसका पूर्वावलोकन यहां दिया गया है:प्रोग्रामिंग भाषाएं ग्राफ को प्रभावित करती हैं। अतीत और वर्तमान में 250 से अधिक प्रोग्रामिंग भाषाओं के बीच डिज़ाइन प्रभाव संबंधों का पता लगाने के लिए लिंक देखें! आपकी बारी! आज की हा