আজ, আমরা রুবি টেমপ্লেটিং-এ আমাদের যাত্রা চালিয়ে যাচ্ছি। লেক্সারের জায়গায়, চলুন পরবর্তী ধাপে যাওয়া যাক:পার্সার।
শেষবার, আমরা স্ট্রিং ইন্টারপোলেশন দেখেছিলাম এবং পরবর্তীকালে, আমাদের নিজস্ব টেমপ্লেটিং ভাষা তৈরিতে ডুব দিয়েছিলাম। আমরা একটি লেক্সার প্রয়োগ করে শুরু করেছি যা একটি টেমপ্লেট পড়ে এবং এটিকে টোকেনের একটি প্রবাহে রূপান্তর করে। আজ, আমরা সহগামী পার্সার বাস্তবায়ন করব। আমরা আমাদের পায়ের আঙ্গুলগুলিকে কিছুটা ভাষা তত্ত্বে ডুবিয়ে দেব।
এই যে আমরা!
বিমূর্ত সিনট্যাক্স গাছ
আসুন Welcome to {{name}}
-এ স্বাগতম আমাদের সহজ উদাহরণ টেমপ্লেটের দিকে ফিরে তাকাই . স্ট্রিংকে টোকেনাইজ করার জন্য লেক্সার ব্যবহার করার পরে, আমরা এই ধরনের টোকেনগুলির একটি তালিকা পাই৷
Magicbars::Lexer.tokenize("Welcome to {{name}}")
# => [[:CONTENT, "Welcome to "], [:OPEN_EXPRESSION], [:IDENTIFIER, "name"], [:CLOSE]]
শেষ পর্যন্ত, আমরা টেমপ্লেটটি মূল্যায়ন করতে চাই এবং অভিব্যক্তিটিকে বাস্তব মান দিয়ে প্রতিস্থাপন করতে চাই। জিনিসগুলিকে একটু বেশি চ্যালেঞ্জিং করতে, আমরা পুনরাবৃত্তি এবং শর্তসাপেক্ষের অনুমতি দিয়ে জটিল ব্লক এক্সপ্রেশনগুলিকেও মূল্যায়ন করতে চাই৷
এটি করার জন্য, আমাদের একটি বিমূর্ত সিনট্যাক্স ট্রি (AST) তৈরি করতে হবে যা টেমপ্লেটের যৌক্তিক কাঠামো বর্ণনা করে। ট্রিটি নোড নিয়ে গঠিত যা অন্য নোডের উল্লেখ করতে পারে বা টোকেন থেকে অতিরিক্ত ডেটা সঞ্চয় করতে পারে।
আমাদের সাধারণ উদাহরণের জন্য, পছন্দসই বিমূর্ত সিনট্যাক্স ট্রি দেখতে এইরকম:
একটি ব্যাকরণ সংজ্ঞায়িত করা
ব্যাকরণ সংজ্ঞায়িত করতে, আসুন একটি ভাষার তাত্ত্বিক ভিত্তি দিয়ে শুরু করা যাক। অন্যান্য প্রোগ্রামিং ভাষার মতো, আমাদের টেমপ্লেটিং ভাষা একটি প্রসঙ্গ-মুক্ত ভাষা এবং তাই একটি প্রসঙ্গ-মুক্ত ব্যাকরণ দ্বারা বর্ণনা করা যেতে পারে।
একটি প্রসঙ্গ-মুক্ত ব্যাকরণ হল নিয়মগুলির একটি সেট যা বর্ণনা করে যে কীভাবে একটি ভাষার সমস্ত সম্ভাব্য স্ট্রিং তৈরি করা হয়। আসুন EBNF স্বরলিপিতে আমাদের টেমপ্লেটিং ভাষার ব্যাকরণটি দেখি:
প্রতিটি অ্যাসাইনমেন্ট একটি নিয়ম সংজ্ঞায়িত করে। নিয়মের নাম বাম দিকে এবং আমাদের লেক্সার থেকে অন্যান্য নিয়মের একটি গুচ্ছ (লোয়ার কেস) বা টোকেন (বড় হাতের) ডানদিকে রয়েছে। নিয়ম এবং টোকেন কমা
উপরের ব্যাকরণটি বর্ণনা করার একটি সংক্ষিপ্ত উপায় যে একটি টেমপ্লেট বিবৃতি নিয়ে গঠিত। একটি বিবৃতি হয় একটি
এমন সরঞ্জাম রয়েছে যা স্বয়ংক্রিয়ভাবে উপরের মত ব্যাকরণের সংজ্ঞা থেকে পার্সার তৈরি করে। কিন্তু সত্যিকারের রুবি ম্যাজিক ঐতিহ্যে, আসুন কিছু মজা করি এবং নিজেরাই পার্সার তৈরি করি, আশা করি এই প্রক্রিয়ায় একটি বা দুটি জিনিস শিখতে পারব।
ভাষা তত্ত্বকে একপাশে রেখে, আসুন প্রকৃতপক্ষে পার্সার তৈরিতে ঝাঁপিয়ে পড়ি। চলুন আরও ন্যূনতম, কিন্তু এখনও বৈধ, টেমপ্লেট দিয়ে শুরু করা যাক:
প্রথমত, আমরা আমাদের পার্সার ক্লাস সেট আপ করি। এটা এই মত দেখায়:
ক্লাস টোকেনগুলির একটি অ্যারে নেয় এবং এটি সঞ্চয় করে। এটিতে শুধুমাত্র
আমাদের ব্যাকরণের দিকে ফিরে তাকালে, সেরা নিয়ম হল
নোড হল সহজ ক্লাস যার নিজস্ব কোন আচরণ নেই। তারা শুধু অন্যান্য নোড সংযোগ করে বা টোকেন থেকে কিছু মান সঞ্চয় করে। এখানে
আমাদের উদাহরণ কাজ করার জন্য, আমাদের একটি
এর পরে,
যখন আমরা পার্সার চালাই, তখন আমরা সঠিক ফলাফল পাই:
স্বীকার্য, এটি শুধুমাত্র আমাদের সাধারণ উদাহরণের জন্য কাজ করে যার শুধুমাত্র একটি বিষয়বস্তু নোড রয়েছে। আসুন একটি আরও জটিল উদাহরণে স্যুইচ করি যা আসলে একটি অভিব্যক্তি অন্তর্ভুক্ত করে:
এর জন্য, আমাদের একটি
নতুন নোডের জায়গায়, আসুন
আপনি কি লক্ষ্য করেছেন যে
এর পরে, আসুন
উন্নত
প্রথমে, আমরা পরীক্ষা করে দেখছি যে একটি বর্তমান টোকেন আছে এবং এর ধরন হল
এই সময়ে, আমরা কিছু নিদর্শন উত্থান দেখতে. আমরা বেশ কয়েকবার পরবর্তী টোকেনে অগ্রসর হচ্ছি এবং আমরা এটিও পরীক্ষা করছি যে একটি বর্তমান টোকেন এবং এর ধরন রয়েছে। কারণ এটির জন্য কোডটি কিছুটা কষ্টকর, আসুন দুটি সহায়ক পদ্ধতি চালু করা যাক।
যে ক্ষেত্রে পরবর্তী প্রত্যাশিত টোকেন সম্পর্কে কোনো নমনীয়তা নেই, আমরা এমন একটি পদ্ধতিও চালু করি যা টোকেন না মিললে একটি সুন্দর ত্রুটির বার্তা উত্থাপন করে।
এই সহায়ক পদ্ধতিগুলি ব্যবহার করে,
অবশেষে, আসুন
এই সমস্ত পরিবর্তনের সাথে, আসুন টোকেন স্ট্রীমটি পার্স করার চেষ্টা করি:
এটা পড়া একটু কঠিন কিন্তু এটা আসলে সঠিক বিমূর্ত সিনট্যাক্স ট্রি।
আমাদের পার্সার বাস্তবায়ন সম্পূর্ণ করতে, আমাদের এখনও ব্লক এক্সপ্রেশনের পার্সিং বাস্তবায়ন করতে হবে। একটি অনুস্মারক হিসাবে, এখানে টেমপ্লেটটি আমরা পার্স করতে চাই:
এটি করার জন্য, প্রথমে একটি
ব্যাকরণের দিকে ফিরে তাকালে, আমরা লক্ষ্য করি যে ব্লক এক্সপ্রেশন পার্স করতে, আমাদের
প্রথম অংশটি
এটি হয়ে গেলে, আমরা একটি
একটি চূড়ান্ত জিনিস হিসাবে, আমরা নিশ্চিত করি যে খোলা ব্লক এক্সপ্রেশনের মতো একই শনাক্তকারী ব্যবহার করে একটি শেষ ব্লক অভিব্যক্তি রয়েছে। যদি শনাক্তকারী মেলে না, আমরা একটি ত্রুটি উত্থাপন করি। অন্যথায়, আমরা একটি নতুন
উন্নত ব্লক এক্সপ্রেশন টেমপ্লেটের টোকেন সহ পার্সারকে কল করলে টেমপ্লেটের জন্য AST ফিরে আসবে। আমি এখানে উদাহরণ আউটপুট অন্তর্ভুক্ত করব না, কারণ এটি সবেমাত্র পঠনযোগ্য। পরিবর্তে, এখানে তৈরি করা AST-এর একটি ভিজ্যুয়াল উপস্থাপনা রয়েছে।
কারণ আমরা
আমরা আমাদের নিজস্ব টেমপ্লেটিং ভাষা বাস্তবায়নের দিকে আমাদের যাত্রার সাথে শালীন অগ্রগতি করেছি। ভাষা তত্ত্বে সংক্ষিপ্ত ডুব দেওয়ার পরে, আমরা আমাদের টেমপ্লেটিং ভাষার জন্য একটি ব্যাকরণকে সংজ্ঞায়িত করেছি এবং এটিকে স্ক্র্যাচ থেকে একটি পার্সার প্রয়োগ করতে ব্যবহার করেছি৷
লেক্সার এবং পার্সার উভয়ের জায়গায়, আমরা আমাদের টেমপ্লেট থেকে ইন্টারপোলেটেড স্ট্রিং তৈরি করার জন্য শুধুমাত্র একজন দোভাষীকে মিস করছি। আমরা রুবিম্যাজিকের একটি আসন্ন সংস্করণে এই অংশটি কভার করব। রুবি ম্যাজিক মেইলিংলিস্টে সাবস্ক্রাইব করুন, এটি বের হলে সতর্ক হতে।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
নামে একটি সর্বজনীন পদ্ধতি রয়েছে যা টোকেনকে AST-তে রূপান্তর করে।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
প্রবর্তন করা যাক নোড যদিও এই নোডটি একটু বেশি ডেটা সঞ্চয় করে, এটি অন্য কিছু করে না এবং তাই খুব উত্তেজনাপূর্ণ নয়৷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
তৈরি করি নোড এবং এটি ফেরত দিন।parse_statements
কল করছি parse_block_expression
এর ভিতরে , ব্লক এবং ইনভার্স ব্লক উভয়ই আরও এক্সপ্রেশন, ব্লক এক্সপ্রেশন, সেইসাথে নিয়মিত বিষয়বস্তু অন্তর্ভুক্ত করতে পারে।যাত্রা অব্যাহত থাকে...