अपनी स्वयं की प्रोग्रामिंग भाषा बनाने के इस भाग में, हम नेस्टेड कक्षाओं को लागू करके और पिछले भाग में पेश की गई कक्षाओं को थोड़ा उन्नत करके अपनी भाषा में सुधार करना जारी रखेंगे। कृपया पिछले भागों को देखें:
पूरा स्रोत कोड GitHub पर उपलब्ध है।
पहले खंड में, हम शाब्दिक विश्लेषण को कवर करेंगे। संक्षेप में, यह स्रोत कोड को भाषा के शब्दों में विभाजित करने की एक प्रक्रिया है, जैसे कि कीवर्ड, चर, ऑपरेटर, आदि।
आपको पिछले भागों से याद हो सकता है कि मैं टोकन टाइप एनम में सूचीबद्ध रेगेक्स एक्सप्रेशन का उपयोग सभी लेक्सेम प्रकारों को परिभाषित करने के लिए कर रहा था:
package org.example.toylanguage.token; public enum TokenType { Comment("\\#.*"), LineBreak("[\\n\\r]"), Whitespace("[\\s\\t]"), Keyword("(if|elif|else|end|print|input|class|fun|return|loop|in|by|break|next)(?=\\s|$)"), GroupDivider("(\\[|\\]|\\,|\\{|}|[.]{2})"), Logical("(true|false)(?=\\s|$)"), Numeric("([-]?(?=[.]?[0-9])[0-9]*(?![.]{2})[.]?[0-9]*)"), Null("(null)(?=,|\\s|$)"), This("(this)(?=,|\\s|$)"), Text("\"([^\"]*)\""), Operator("(\\+|-|\\*|/{1,2}|%|>=|>|<=|<{1,2}|={1,2}|!=|!|:{2}|\\(|\\)|(new|and|or)(?=\\s|$))"), Variable("[a-zA-Z_]+[a-zA-Z0-9_]*"); private final String regex; }
आइए इस नेस्टेड क्लास प्रोटोटाइप को देखें, और लापता रेगेक्स एक्सप्रेशन को हमारे TokenType
में जोड़ें:
जब हम नेस्टेड क्लास का एक उदाहरण बनाते हैं, तो हम निम्नलिखित निर्माण का उपयोग दो भावों और उनके बीच एक ऑपरेटर के साथ करते हैं:
बायाँ एक्सप्रेशन class_instance
उस क्लास का एक उदाहरण है जिससे हम एक नेस्टेड क्लास प्राप्त करने की बात कर रहे हैं। सही अभिव्यक्ति NestedClass [args]
एक नेस्टेड वर्ग है जिसमें एक उदाहरण बनाने के गुण हैं।
अंत में, एक नेस्टेड क्लास बनाने के लिए एक ऑपरेटर के रूप में, मैं निम्नलिखित एक्सप्रेशन का उपयोग करूंगा: :: new
, जिसका अर्थ है कि हम क्लास इंस्टेंस प्रॉपर्टी को दो कोलन ::
ऑपरेटर के साथ संदर्भित करते हैं, और फिर हम new
के साथ एक इंस्टेंस बनाते हैं ऑपरेटर।
लेक्सम के वर्तमान सेट के साथ, हमें केवल :: new
ऑपरेटर के लिए एक नियमित अभिव्यक्ति जोड़ने की आवश्यकता है। इस ऑपरेटर को निम्नलिखित रेगेक्स अभिव्यक्ति द्वारा मान्य किया जा सकता है:
:{2}\\s+new
चलिए इस एक्सप्रेशन को Operator
लेक्सेम में OR एक्सप्रेशन के रूप में जोड़ते हैं :{2}
क्लास प्रॉपर्टी तक पहुँचने के लिए खड़ा हिस्सा:
public enum TokenType { ... Operator("(\\+|-|\\*|/{1,2}|%|>=|>|<=|<{1,2}|={1,2}|!=|!|:{2}\\s+new|:{2}|\\(|\\)|(new|and|or)(?=\\s|$))"), ... }
दूसरे खंड में, हम अपने भाषा नियमों का पालन करते हुए शाब्दिक विश्लेषक से प्राप्त शब्दांशों को अंतिम कथनों में परिवर्तित करेंगे।
गणित के भावों का मूल्यांकन करने के लिए, हम दिज्क्स्ट्रा के टू-स्टैक एल्गोरिथम का उपयोग कर रहे हैं। इस एल्गोरिथम में प्रत्येक ऑपरेशन को एक यूनरी ऑपरेटर द्वारा एक ऑपरेंड के साथ या एक बाइनरी ऑपरेटर द्वारा क्रमशः दो ऑपरेंड के साथ प्रस्तुत किया जा सकता है:
नेस्टेड क्लास का इन्स्टेन्शियशन एक बाइनरी ऑपरेशन है जहाँ लेफ्ट ऑपरेंड एक क्लास इंस्टेंस है जिसका उपयोग हम उस क्लास को संदर्भित करने के लिए करते हैं जहाँ नेस्टेड क्लास को परिभाषित किया गया है, और दूसरा ऑपरेंड एक नेस्टेड क्लास है जिसका हम एक इंस्टेंस बनाते हैं:
चलिए विस्तार करके NestedClassInstanceOperator
कार्यान्वयन बनाते हैं
package org.example.toylanguage.expression.operator; public class NestedClassInstanceOperator extends BinaryOperatorExpression { public NestedClassInstanceOperator(Expression left, Expression right) { super(left, right); } @Override public Value<?> evaluate() { ... } }
अगला, हमें evaluate()
विधि को पूरा करना चाहिए जो नेस्टेड क्लास इंस्टेंटेशन करेगा:
सबसे पहले, हम बाएं ऑपरेंड की अभिव्यक्ति का Value
अभिव्यक्ति में मूल्यांकन करते हैं:
@Override public Value<?> evaluate() { // ClassExpression -> ClassValue Value<?> left = getLeft().evaluate(); }
अगला, हमें सही ऑपरेंड का evaluate()
करने की आवश्यकता है। इस मामले में, हम सीधे Expression#evaluate()
आह्वान नहीं कर सकते क्योंकि नेस्टेड क्लास की परिभाषा मूल वर्ग की परिभाषास्कोप (बाएं ऑपरेंड में) में घोषित की जाती है।
नेस्टेड कक्षाओं की परिभाषाओं तक पहुँचने के लिए, हमें एक सहायक ClassExpression#evaluate(ClassValue)
विधि बनानी चाहिए, जो बाएं ऑपरेंड को ले लेगी और नेस्टेड क्लास की परिभाषा तक पहुँचने के लिए इसके डेफिनिशनस्कोप का उपयोग करेगी और इसका एक उदाहरण बनाएगी:
@Override public Value<?> evaluate() { Value<?> left = getLeft().evaluate(); if (left instanceof ClassValue && getRight() instanceof ClassExpression) { // instantiate nested class // new Class [] :: new NestedClass [] return ((ClassExpression) getRight()).evaluate((ClassValue) left); } else { throw new ExecutionException(String.format("Unable to access class's nested class `%s``", getRight())); } }
अंत में, लापता ClassExpression#evaluate(ClassValue)
विधि को लागू करते हैं।
यह कार्यान्वयन ClassExpression#evaluate()
विधि के समान होगा, केवल अंतर यह है कि हमें नेस्टेड क्लास परिभाषाओं को पुनः प्राप्त करने के लिए ClassDefinition#getDefinitionScope()
सेट करना चाहिए:
package org.example.toylanguage.expression; … public class ClassExpression implements Expression { private final String name; private final List<Expression> argumentExpressions; @Override public Value<?> evaluate() { //initialize class arguments List<Value<?>> values = argumentExpressions.stream().map(Expression::evaluate).collect(Collectors.toList()); return evaluate(values); } /** * Evaluate nested class * * @param classValue instance of the parent class */ public Value<?> evaluate(ClassValue classValue) { //initialize class arguments List<Value<?>> values = argumentExpressions.stream().map(Expression::evaluate).collect(Collectors.toList()); //set parent class's definition ClassDefinition classDefinition = classValue.getValue(); DefinitionContext.pushScope(classDefinition.getDefinitionScope()); try { return evaluate(values); } finally { DefinitionContext.endScope(); } } private Value<?> evaluate(List<Value<?>> values) { //get class's definition and statement ClassDefinition definition = DefinitionContext.getScope().getClass(name); ClassStatement classStatement = definition.getStatement(); //set separate scope MemoryScope classScope = new MemoryScope(null); MemoryContext.pushScope(classScope); try { //initialize constructor arguments ClassValue classValue = new ClassValue(definition, classScope); ClassInstanceContext.pushValue(classValue); IntStream.range(0, definition.getArguments().size()).boxed() .forEach(i -> MemoryContext.getScope() .setLocal(definition.getArguments().get(i), values.size() > i ? values.get(i) : NullValue.NULL_INSTANCE)); //execute function body DefinitionContext.pushScope(definition.getDefinitionScope()); try { classStatement.execute(); } finally { DefinitionContext.endScope(); } return classValue; } finally { MemoryContext.endScope(); ClassInstanceContext.popValue(); } } }
गणित के भावों का मूल्यांकन करने के लिए हम जिन ऑपरेटरों का उपयोग कर रहे हैं, वे ऑपरेटर एनम में संबंधित पूर्वता, चरित्र और OperatorExpression
एक्सप्रेशन प्रकार के साथ संग्रहीत होते हैं, जिसे हम प्रत्येक ऑपरेशन के परिणाम की गणना करने के लिए संदर्भित करते हैं:
... public enum Operator { Not("!", NotOperator.class, 7), ClassInstance("new", ClassInstanceOperator.class, 7), ... private final String character; private final Class<? extends OperatorExpression> type; private final Integer precedence; Operator(String character, Class<? extends OperatorExpression> type, Integer precedence) { this.character = character; this.type = type; this.precedence = precedence; } public static Operator getType(String character) { return Arrays.stream(values()) .filter(t -> Objects.equals(t.getCharacter(), character)) .findAny().orElse(null); } ... }
हमारे पास पहले से ही एक नियमित वर्ग के आरंभीकरण के लिए ClassInstance
मान है। आइए नेस्टेड क्लास इंस्टेंस को प्रबंधित करने के लिए एक नया मान जोड़ें।
नए NestedClassInstance
मान में वही वर्ण अभिव्यक्ति होगी जैसा हमने पहले TokenType में परिभाषित किया था और नियमित वर्ग के उदाहरण के समान पूर्वता।
ऑपरेटर एक्सप्रेशन प्रकार के लिए, हम पहले से परिभाषित NestedClassInstanceOperator
का उपयोग करेंगे:
... public enum Operator { Not("!", NotOperator.class, 7), ClassInstance("new", ClassInstanceOperator.class, 7), NestedClassInstance(":{2}\\s+new", NestedClassInstanceOperator.class, 7), ... }
आप देख सकते हैं कि इस नए ऑपरेटर को छोड़कर कैरेक्टर प्रॉपर्टी में हमारे पास रेगेक्स एक्सप्रेशन नहीं हैं। रेगेक्स एक्सप्रेशन का उपयोग करके NestedClassInstance
ऑपरेटर को पढ़ने के लिए, हमें ऑपरेटर को रेगुलर एक्सप्रेशन के साथ मिलान करने के लिए Operator#getType()
विधि को अपडेट करना चाहिए:
public enum Operator { ... public static Operator getType(String character) { return Arrays.stream(values()) .filter(t -> character.matches(t.getCharacter())) .findAny().orElse(null); } ... }
अंत में, हमें निम्नलिखित प्रतीकों वाले संचालन के लिए एक वर्ण से पहले दो बैकस्लैश \\
जोड़ना चाहिए: +, *, (, )
यह सुनिश्चित करने के लिए कि इन वर्णों को रेगेक्स खोज प्रतीकों के रूप में नहीं माना जाता है:
Multiplication("\\*", MultiplicationOperator.class, 6), Addition("\\+", AdditionOperator.class, 5), LeftParen("\\(", 3), RightParen("\\)", 3),
NestedClassInstance
ऑपरेटर को पेश करने के बाद, हमें इसे ExpressionReader क्लास में इंजेक्ट करना चाहिए जो वास्तव में ऑपरेंड और ऑपरेटर्स में मैथ एक्सप्रेशंस को पार्स करता है। हमें केवल उस रेखा को खोजने की जरूरत है जहां हम कक्षा का उदाहरण पढ़ते हैं:
if (!operators.isEmpty() && operators.peek() == Operator.ClassInstance) { operand = readClassInstance(token); }
NestedClassInstance
ऑपरेटर को पढ़ने में सहायता के लिए, हम ऑपरेटर के स्टैक में वर्तमान ऑपरेटर के लिए संबंधित स्थिति जोड़ते हैं:
if (!operators.isEmpty() && (operators.peek() == Operator.ClassInstance || operators.peek() == Operator.NestedClassInstance)) { operand = readClassInstance(token); }
readClassInstance()
विधि नेस्टेड क्लास की घोषणा को गुणों के साथ उसी तरह पढ़ती है जैसे यह नियमित कक्षा घोषणा को पढ़ती है। यह विधि ClassExpression
इंस्टेंस को संपूर्ण ऑपरेंड एक्सप्रेशन के रूप में लौटाती है।
सब कुछ अब तैयार है। इस भाग में, हमने नेस्टेड कक्षाओं को एक संपूर्ण प्रोग्रामिंग भाषा बनाने की दिशा में एक और कदम के रूप में लागू किया।