अपनी स्वयं की प्रोग्रामिंग भाषा बनाने के इस भाग में, हम कक्षाओं को पहले से परिभाषित संरचनाओं के विस्तार के रूप में लागू करेंगे। कृपया पिछले भागों को देखें: स्क्रैच से अपनी खुद की प्रोग्रामिंग लैंग्वेज बनाना स्क्रैच से अपनी खुद की प्रोग्रामिंग लैंग्वेज बनाना: भाग II - दिज्क्स्ट्रा का टू-स्टैक एल्गोरिथम बिल्ड योर ओन प्रोग्रामिंग लैंग्वेज पार्ट III: इम्प्रूविंग लेक्सिकल एनालिसिस विथ रेगेक्स लुकहेड्स स्क्रैच भाग IV से अपनी खुद की प्रोग्रामिंग लैंग्वेज बनाना: कार्यों को लागू करना स्क्रैच से अपनी खुद की प्रोग्रामिंग लैंग्वेज बनाना: भाग V - सारणियाँ स्क्रैच से अपनी खुद की प्रोग्रामिंग लैंग्वेज बनाना: भाग VI - लूप्स पूर्ण स्रोत कोड उपलब्ध है गिटहब पर . 1. शाब्दिक विश्लेषण पहले खंड में, हम शाब्दिक विश्लेषण को कवर करेंगे। संक्षेप में, यह स्रोत कोड को भाषा के शब्दों में विभाजित करने की एक प्रक्रिया है, जैसे कि कीवर्ड, चर, ऑपरेटर, आदि। आप पिछले भागों से याद कर सकते हैं कि मैं टोकन टाइप एनम में रेगेक्स एक्सप्रेशन का उपयोग सभी प्रकारों को परिभाषित करने के लिए कर रहा था। लेक्सेम आइए निम्न वर्ग के प्रोटोटाइप को देखें और लापता रेगेक्स भागों को हमारे में जोड़ें: TokenType सबसे पहले, हमें लेक्सिकल एनालाइज़र को यह बताने के लिए कि हमारा क्लास डिक्लेरेशन कहाँ से शुरू होता है, हमें लेक्सेम के एक्सप्रेशन में शब्द डालना होगा: Keyword class package org.example.toylanguage.token; ... public enum TokenType { ... Keyword("(if|elif|else|end|print|input|fun|return|loop|in|by|break|next|class)(?=\\s|$)"), ... private final String regex; } दूसरा, हमें वर्तमान वस्तु के संदर्भ के लिए मार्कर के रूप में नया लेक्सेम चाहिए: This public enum TokenType { ... This("(this)(?=,|\\s|$)"); private final String regex; } 2. सिंटेक्स विश्लेषण दूसरे खंड में, हम अपने भाषा नियमों का पालन करते हुए लेक्सिकल एनालाइज़र से प्राप्त शब्दांशों को अंतिम कथनों में रूपांतरित करेंगे। 2.1 परिभाषा का दायरा जब हम एक वर्ग या एक समारोह की घोषणा करते हैं, तो यह घोषणा परिभाषित पृथक सीमाओं के भीतर उपलब्ध होनी चाहिए। उदाहरण के लिए, यदि हम निम्नलिखित सूची में नामक फ़ंक्शन की घोषणा करते हैं, तो यह घोषणा के बाद निष्पादन के लिए उपलब्ध होगा: turn_on [] लेकिन अगर हम एक ही फंक्शन को क्लास स्कोप के अंदर घोषित करते हैं, तो यह फंक्शन सीधे मुख्य ब्लॉक से एक्सेस नहीं किया जाएगा: इन परिभाषा सीमाओं को लागू करने के लिए, हम वर्ग बनाएंगे और सभी घोषित परिभाषाओं को कक्षाओं और कार्यों के लिए दो सेटों के अंदर संग्रहीत करेंगे: DefinitionScope package org.example.toylanguage.context.definition; public class DefinitionScope { private final Set<ClassDefinition> classes; private final Set<FunctionDefinition> functions; public DefinitionScope() { this.classes = new HashSet<>(); this.functions = new HashSet<>(); } } इसके अलावा, हम माता-पिता की परिभाषा के दायरे तक पहुँचना चाह सकते हैं। उदाहरण के लिए, यदि हम दो अलग-अलग वर्गों की घोषणा करते हैं और दूसरे के अंदर प्रथम श्रेणी का उदाहरण बनाते हैं: यह क्षमता प्रदान करने के लिए, हम पैरेंट इंस्टेंस को ऊपरी परत के संदर्भ के रूप में जोड़ेंगे, जिसका उपयोग हम शीर्ष परिभाषा परत पर चढ़ने के लिए करेंगे। DefinitionScope public class DefinitionScope { private final Set<ClassDefinition> classes; private final Set<FunctionDefinition> functions; private final DefinitionScope parent; public DefinitionScope(DefinitionScope parent) { this.classes = new HashSet<>(); this.functions = new HashSet<>(); this.parent = parent; } } अब, एक परिभाषा जोड़ने के लिए इंटरफेस प्रदान करके कार्यान्वयन को समाप्त करते हैं और पैरेंट स्कोप का उपयोग करके इसे नाम से पुनः प्राप्त करते हैं: public class DefinitionScope { … public ClassDefinition getClass(String name) { Optional<ClassDefinition> classDefinition = classes.stream() .filter(t -> t.getName().equals(name)) .findAny(); if (classDefinition.isPresent()) return classDefinition.get(); else if (parent != null) return parent.getClass(name); else throw new ExecutionException(String.format("Class is not defined: %s", name)); } public void addClass(ClassDefinition classDefinition) { classes.add(classDefinition); } public FunctionDefinition getFunction(String name) { Optional<FunctionDefinition> functionDefinition = functions.stream() .filter(t -> t.getName().equals(name)) .findAny(); if (functionDefinition.isPresent()) return functionDefinition.get(); else if (parent != null) return parent.getFunction(name); else throw new ExecutionException(String.format("Function is not defined: %s", name)); } public void addFunction(FunctionDefinition functionDefinition) { functions.add(functionDefinition); } } अंत में, घोषित परिभाषा क्षेत्रों को प्रबंधित करने और उनके बीच स्विच करने के लिए, हम संग्रह (LIFO) का उपयोग करके संदर्भ वर्ग बनाते हैं: java.util.Stack package org.example.toylanguage.context.definition; public class DefinitionContext { private final static Stack<DefinitionScope> scopes = new Stack<>(); public static DefinitionScope getScope() { return scopes.peek(); } public static DefinitionScope newScope() { return new DefinitionScope(scopes.isEmpty() ? null : scopes.peek()); } public static void pushScope(DefinitionScope scope) { scopes.push(scope); } public static void endScope() { scopes.pop(); } } 2.2 मेमोरी स्कोप इस सेक्शन में, हम क्लास और फंक्शन वेरिएबल्स को मैनेज करने के लिए को कवर करेंगे। MemoryScope प्रत्येक घोषित चर, इसी तरह वर्ग या फ़ंक्शन परिभाषा के लिए, कोड के एक पृथक ब्लॉक के भीतर ही पहुंच योग्य होना चाहिए। उदाहरण के लिए, यदि हम निम्नलिखित सूची में एक चर को परिभाषित करते हैं, तो आप इसे घोषणा के ठीक बाद एक्सेस कर सकते हैं: लेकिन अगर हम किसी फ़ंक्शन या वर्ग के भीतर एक चर घोषित करते हैं, तो चर अब कोड के मुख्य (ऊपरी) ब्लॉक से उपलब्ध नहीं होगा: एक विशिष्ट दायरे में परिभाषित इस तर्क और स्टोर चर को लागू करने के लिए, हम वर्ग बनाते हैं जिसमें चर नाम के साथ एक कुंजी के रूप में एक मानचित्र और मान के रूप में चर होगा: MemoryScope Value public class MemoryScope { private final Map<String, Value<?>> variables; public MemoryScope() { this.variables = new HashMap<>(); } } अगला, के समान, हम पैरेंट के स्कोप वेरिएबल्स तक पहुंच प्रदान करते हैं: DefinitionScope public class MemoryScope { private final Map<String, Value<?>> variables; private final MemoryScope parent; public MemoryScope(MemoryScope parent) { this.variables = new HashMap<>(); this.parent = parent; } } अगला, हम चर प्राप्त करने और सेट करने के तरीके जोड़ते हैं। जब हम एक चर सेट करते हैं, तो हम हमेशा पहले से निर्धारित मान को फिर से असाइन करते हैं यदि ऊपरी परत में पहले से ही एक परिभाषित चर है: MemoryScope public class MemoryScope { ... public Value<?> get(String name) { Value<?> value = variables.get(name); if (value != null) return value; else if (parent != null) return parent.get(name); else return NullValue.NULL_INSTANCE; } public void set(String name, Value<?> value) { MemoryScope variableScope = findScope(name); if (variableScope == null) { variables.put(name, value); } else { variableScope.variables.put(name, value); } } private MemoryScope findScope(String name) { if (variables.containsKey(name)) return this; return parent == null ? null : parent.findScope(name); } } और तरीकों अलावा, हम की वर्तमान (स्थानीय) परत के साथ बातचीत करने के लिए दो और कार्यान्वयन जोड़ते हैं: set get MemoryScope public class MemoryScope { ... public Value<?> getLocal(String name) { return variables.get(name); } public void setLocal(String name, Value<?> value) { variables.put(name, value); } } इन विधियों का उपयोग बाद में फ़ंक्शन तर्कों या कक्षा के उदाहरण तर्कों को प्रारंभ करने के लिए किया जाएगा। उदाहरण के लिए, यदि हम वर्ग का एक उदाहरण बनाते हैं और पूर्वनिर्धारित वैश्विक के चर को पास करते हैं, तो इस चर को तब नहीं बदला जाना चाहिए जब हम की संपत्ति को अपडेट करने का प्रयास करते हैं: Lamp type lamp_instance :: type अंत में, वेरिएबल्स को प्रबंधित करने और मेमोरी स्कोप के बीच स्विच करने के लिए, हम संग्रह का उपयोग करके कार्यान्वयन बनाते हैं: java.util.Stack MemoryContext package org.example.toylanguage.context; public class MemoryContext { private static final Stack<MemoryScope> scopes = new Stack<>(); public static MemoryScope getScope() { return scopes.peek(); } public static MemoryScope newScope() { return new MemoryScope(scopes.isEmpty() ? null : scopes.peek()); } public static void pushScope(MemoryScope scope) { scopes.push(scope); } public static void endScope() { scopes.pop(); } } 2.3 वर्ग परिभाषा इस खंड में, हम वर्ग परिभाषाओं को पढ़ेंगे और संग्रहीत करेंगे। सबसे पहले, हम इम्प्लीमेंटेशन बनाते हैं। हर बार जब हम कक्षा का उदाहरण बनाते हैं तो यह कथन निष्पादित किया जाएगा: स्टेटमेंट package org.example.toylanguage.statement; public class ClassStatement { } प्रत्येक वर्ग में आरंभीकरण और अन्य कार्यों के लिए एक कंस्ट्रक्टर की तरह नेस्टेड स्टेटमेंट हो सकते हैं। इन बयानों को संग्रहीत करने के लिए, हम का विस्तार करते हैं जिसमें निष्पादित करने के लिए नेस्टेड बयानों की सूची होती है: कंपोजिटस्टेटमेंट public class ClassStatement extends CompositeStatement { } अगला, हम क्लास के नाम, उसके तर्कों, कंस्ट्रक्टर स्टेटमेंट्स और क्लास के कार्यों के साथ डेफिनिशन स्कोप को स्टोर करने के लिए की घोषणा करते हैं: ClassDefinition package org.example.toylanguage.context.definition; import java.util.List; @RequiredArgsConstructor @Getter @EqualsAndHashCode(onlyExplicitlyIncluded = true) public class ClassDefinition implements Definition { @EqualsAndHashCode.Include private final String name; private final List<String> arguments; private final ClassStatement statement; private final DefinitionScope definitionScope; } अब, हम का उपयोग करके वर्ग घोषणा को पढ़ने के लिए तैयार हैं। जब हम मूल्य के साथ कीवर्ड शब्दांश से मिलते हैं, तो सबसे पहले हमें वर्ग कोष्ठक के अंदर वर्ग के नाम और उसके तर्कों को पढ़ने की आवश्यकता होती है: StatementParser class package org.example.toylanguage; public class StatementParser { ... private void parseClassDefinition() { Token type = tokens.next(TokenType.Variable); List<String> arguments = new ArrayList<>(); if (tokens.peek(TokenType.GroupDivider, "[")) { tokens.next(TokenType.GroupDivider, "["); //skip opening square bracket while (!tokens.peek(TokenType.GroupDivider, "]")) { Token argumentToken = tokens.next(TokenType.Variable); arguments.add(argumentToken.getValue()); if (tokens.peek(TokenType.GroupDivider, ",")) tokens.next(); } tokens.next(TokenType.GroupDivider, "]"); //skip closing square bracket } } } तर्कों के बाद, हम नेस्टेड कंस्ट्रक्टर स्टेटमेंट को पढ़ने की उम्मीद करते हैं: इन बयानों को स्टोर करने के लिए, हम पहले परिभाषित का एक उदाहरण बनाते हैं: ClassStatement private void parseClassDefinition() { ... ClassStatement classStatement = new ClassStatement(); } तर्कों और नेस्टेड बयानों के अलावा, हमारी कक्षाओं में कार्य भी हो सकते हैं। इन कार्यों को केवल वर्ग परिभाषा के भीतर सुलभ बनाने के लिए, हम की एक नई परत शुरू करते हैं: DefinitionScope private void parseClassDefinition() { ... ClassStatement classStatement = new ClassStatement(); DefinitionScope classScope = DefinitionContext.newScope(); } अगला, हम के एक उदाहरण को इनिशियलाइज़ करते हैं और इसे में डालते हैं: ClassDefinition DefinitionContext private void parseClassDefinition() { ... ClassStatement classStatement = new ClassStatement(); DefinitionScope classScope = DefinitionContext.newScope(); ClassDefinition classDefinition = new ClassDefinition(type.getValue(), arguments, classStatement, classScope); DefinitionContext.getScope().addClass(classDefinition); } अंत में, क्लास कंस्ट्रक्टर स्टेटमेंट और फ़ंक्शंस को पढ़ने के लिए, हम स्टैटिक मेथड को कॉल करते हैं, जो इंस्टेंस के अंदर स्टेटमेंट्स को तब तक कलेक्ट करेगा, जब तक कि हम फाइनलिंग लेक्सेम को पूरा नहीं कर लेते, जिसे अंत में छोड़ देना चाहिए: स्टेटमेंटपर्सर # पार्स () classStatement end private void parseClassDefinition() { ... //parse class statements StatementParser.parse(this, classStatement, classScope); tokens.next(TokenType.Keyword, "end"); } 2.4 वर्ग उदाहरण इस बिंदु पर, हम पहले से ही कंस्ट्रक्टर स्टेटमेंट और फ़ंक्शंस के साथ क्लास की परिभाषाएँ पढ़ सकते हैं। अब, वर्ग के उदाहरण को पार्स करते हैं: सबसे पहले, हम को परिभाषित करते हैं, जिसमें प्रत्येक वर्ग के उदाहरण की स्थिति होगी। कक्षाओं के विपरीत, कार्यों के विपरीत, एक स्थायी होना चाहिए और यह दायरा हर बार जब हम कक्षा के उदाहरण के साथ बातचीत करते हैं तो सभी वर्ग के उदाहरण तर्कों और राज्य चर के साथ उपलब्ध होना चाहिए: ClassValue MemoryScope public class ClassValue extends IterableValue<ClassDefinition> { private final MemoryScope memoryScope; public ClassValue(ClassDefinition definition, MemoryScope memoryScope) { super(definition); this.memoryScope = memoryScope; } } अगला, हम का उपयोग करके क्लास के इंस्टेंस गुणों के साथ काम करने के तरीके प्रदान करते हैं: MemoryContext public class ClassValue extends IterableValue<ClassDefinition> { private final MemoryScope memoryScope; public ClassValue(ClassDefinition definition, MemoryScope memoryScope) { super(definition); this.memoryScope = memoryScope; } @Override public String toString() { return getValue().getArguments().stream() .map(t -> t + " = " + getValue(t)) .collect(Collectors.joining(", ", getValue().getName() + " [ ", " ]")); } public Value<?> getValue(String name) { Value<?> result = MemoryContext.getScope().getLocal(name); return result != null ? result : NULL_INSTANCE; } public void setValue(String name, Value<?> value) { MemoryContext.getScope().setLocal(name, value); } } कृपया ध्यान दें कि और विधियों को कॉल करके, हम चर की वर्तमान परत के साथ काम करते हैं। लेकिन क्लास के इंस्टेंस स्टेट तक पहुँचने से पहले, हमें इसके को मेमोरी कॉन्टेक्स्ट में डालना होगा और जब हम समाप्त कर लेंगे तो इसे रिलीज़ कर : MemoryScope#getLocal() MemoryScope#setLocal() MemoryScope MemoryScope MemoryContext public class ClassValue extends IterableValue<ClassDefinition> { ... public Value<?> getValue(String name) { MemoryContext.pushScope(memoryScope); try { Value<?> result = MemoryContext.getScope().getLocal(name); return result != null ? result : NULL_INSTANCE; } finally { MemoryContext.endScope(); } } public void setValue(String name, Value<?> value) { MemoryContext.pushScope(memoryScope); try { MemoryContext.getScope().setLocal(name, value); } finally { MemoryContext.endScope(); } } } अगला, हम शेष को लागू कर सकते हैं जिसका उपयोग सिंटैक्स विश्लेषण के दौरान परिभाषित वर्ग उदाहरणों के निर्माण के लिए किया जाएगा। वर्ग की उदाहरण परिभाषा घोषित करने के लिए, हम और तर्कों की सूची प्रदान करते हैं जो कथन निष्पादन के दौरान अंतिम उदाहरणों में परिवर्तित हो जाएंगे: ClassExpression ClassDefinition Expression Value package org.example.toylanguage.expression; @RequiredArgsConstructor @Getter public class ClassExpression implements Expression { private final ClassDefinition definition; private final List<Expression> arguments; @Override public Value<?> evaluate() { ... } } आइए विधि को लागू करें जिसका उपयोग निष्पादन के दौरान पहले से परिभाषित का एक उदाहरण बनाने के लिए किया जाएगा। सबसे पहले, हम तर्कों का तर्कों में मूल्यांकन करते हैं: Expression#evaluate() ClassValue Expression Value @Override public Value<?> evaluate() { //initialize class arguments List<Value<?>> values = arguments.stream() .map(Expression::evaluate) .collect(Collectors.toList()); } अगला, हम एक खाली मेमोरी स्कोप बनाते हैं जिसे अन्य वेरिएबल्स से अलग किया जाना चाहिए और इसमें केवल क्लास के इंस्टेंस स्टेट वेरिएबल्स शामिल हो सकते हैं: @Override public Value<?> evaluate() { //initialize class arguments List<Value<?>> values = arguments.stream().map(Expression::evaluate).collect(Collectors.toList()); //get class's definition and statement ClassStatement classStatement = definition.getStatement(); //set separate scope MemoryScope classScope = new MemoryScope(null); MemoryContext.pushScope(classScope); try { ... } finally { MemoryContext.endScope(); } } इसके बाद, हम का एक उदाहरण बनाते हैं और वर्ग के तर्कों को अलग-अलग मेमोरी स्कोप में लिखते हैं: ClassValue Value try { //initialize constructor arguments ClassValue classValue = new ClassValue(definition, classScope); 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)); ... } finally { MemoryContext.endScope(); } कृपया ध्यान दें कि खाली सेट करने से पहले हमने तर्कों को तर्कों में बदल दिया। अन्यथा, हम उदाहरण के लिए, वर्ग के उदाहरण तर्कों तक पहुँचने में सक्षम नहीं होंगे: MemoryScope Expression Value और अंत में, हम को निष्पादित कर सकते हैं। लेकिन इससे पहले, हमें क्लास के को कन्स्ट्रक्टर स्टेटमेंट के भीतर क्लास के कार्यों तक पहुंचने में सक्षम होना चाहिए: ClassStatement DefinitionScope 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(); } 9*. एक आखिरी बात, हम कक्षाओं को अधिक लचीला बना सकते हैं और कक्षा की परिभाषा घोषित करने से पहले उपयोगकर्ता को कक्षा के उदाहरण बनाने की अनुमति दे सकते हैं: यह इनिशियलाइज़ेशन को को सौंप कर किया जा सकता है और इसे केवल तभी एक्सेस किया जा सकता है जब हम किसी एक्सप्रेशन का मूल्यांकन करते हैं: ClassDefinition DefinitionContext public class ClassExpression implements Expression { private final String name; private final List<Expression> arguments; @Override public Value<?> evaluate() { //get class's definition and statement ClassDefinition definition = DefinitionContext.getScope().getClass(name); ... } } परिभाषा से पहले कार्यों को लागू करने के लिए आप के लिए एक ही प्रतिनिधिमंडल कर सकते हैं। FunctionExpression अंत में, हम के साथ कक्षा के उदाहरणों को पढ़ना समाप्त कर सकते हैं। के बीच कोई अंतर नहीं है। हमें केवल के तर्कों को पढ़ने और का निर्माण करने की आवश्यकता है: ExpressionReader पहले परिभाषित संरचना उदाहरणों Expression ClassExpression public class ExpressionReader ... // read class instance: new Class[arguments] private ClassExpression readClassInstance(Token token) { List<Expression> arguments = new ArrayList<>(); if (tokens.peekSameLine(TokenType.GroupDivider, "[")) { tokens.next(TokenType.GroupDivider, "["); //skip opening square bracket while (!tokens.peekSameLine(TokenType.GroupDivider, "]")) { Expression value = ExpressionReader.readExpression(this); arguments.add(value); if (tokens.peekSameLine(TokenType.GroupDivider, ",")) tokens.next(); } tokens.next(TokenType.GroupDivider, "]"); //skip closing square bracket } return new ClassExpression(token.getValue(), arguments); } } 2.5 क्लास फंक्शन इस समय, हम एक क्लास बना सकते हैं और क्लास के कंस्ट्रक्टर स्टेटमेंट को निष्पादित कर सकते हैं। लेकिन हम अभी भी वर्ग के कार्यों को क्रियान्वित करने में असमर्थ हैं। चलिए मेथड को ओवरलोड करते हैं जो को उस क्लास इंस्टेंस के संदर्भ के रूप में स्वीकार करेगा जिससे हम एक फंक्शन इनवॉइस करना चाहते हैं: FunctionExpression#evaluate ClassValue package org.example.toylanguage.expression; public class FunctionExpression implements Expression { ... public Value<?> evaluate(ClassValue classValue) { } } अगला कदम फ़ंक्शन तर्कों को वर्तमान का उपयोग करके तर्कों में बदलना है: Expression MemoryScope Value public Value<?> evaluate(ClassValue classValue) { //initialize function arguments List<Value<?>> values = argumentExpressions.stream() .map(Expression::evaluate) .collect(Collectors.toList()); } अगला, हमें कक्षा के और को संदर्भ में पारित करने की आवश्यकता है: MemoryScope DefinitionScope ... //get definition and memory scopes from class definition ClassDefinition classDefinition = classValue.getValue(); DefinitionScope classDefinitionScope = classDefinition.getDefinitionScope(); //set class's definition and memory scopes DefinitionContext.pushScope(classDefinitionScope); MemoryContext.pushScope(memoryScope); अंत में इस कार्यान्वयन के लिए, हम डिफ़ॉल्ट विधि का आह्वान करते हैं और मूल्यांकित तर्क पास करते हैं: FunctionExpression#evaluate(List<Value<?>> values) Value public Value<?> evaluate(ClassValue classValue) { //initialize function arguments List<Value<?>> values = argumentExpressions.stream().map(Expression::evaluate).collect(Collectors.toList()); //get definition and memory scopes from class definition ClassDefinition classDefinition = classValue.getValue(); DefinitionScope classDefinitionScope = classDefinition.getDefinitionScope(); MemoryScope memoryScope = classValue.getMemoryScope(); //set class's definition and memory scopes DefinitionContext.pushScope(classDefinitionScope); MemoryContext.pushScope(memoryScope); try { //proceed function return evaluate(values); } finally { DefinitionContext.endScope(); MemoryContext.endScope(); } } क्लास के फंक्शन को इनवोक करने के लिए, हम डबल कोलन ऑपरेटर का उपयोग करेंगे। वर्तमान में, इस ऑपरेटर को ( ) कार्यान्वयन द्वारा प्रबंधित किया जाता है, जो वर्ग की संपत्तियों तक पहुँचने के लिए ज़िम्मेदार है: :: ClassPropertyOperator स्ट्रक्चरवैल्यूऑपरेटर आइए इसे एक ही डबल कोलन के साथ फ़ंक्शन इनवोकेशन का समर्थन करने के लिए सुधारें वर्ण: :: इस ऑपरेटर द्वारा क्लास के फ़ंक्शन को केवल तभी प्रबंधित किया जा सकता है जब बायां एक्सप्रेशन और दूसरा एक्सप्रेशन हो: ClassExpression FunctionExpression package org.example.toylanguage.expression.operator; public class ClassPropertyOperator extends BinaryOperatorExpression implements AssignExpression { public ClassPropertyOperator(Expression left, Expression right) { super(left, right); } @Override public Value<?> evaluate() { Value<?> left = getLeft().evaluate(); if (left instanceof ClassValue) { if (getRight() instanceof VariableExpression) { // access class's property // new ClassInstance[] :: class_argument return ((ClassValue) left).getValue(((VariableExpression) getRight()).getName()); } else if (getRight() instanceof FunctionExpression) { // execute class's function // new ClassInstance[] :: class_function [] return ((FunctionExpression) getRight()).evaluate((ClassValue) left); } } throw new ExecutionException(String.format("Unable to access class's property `%s``", getRight())); } @Override public void assign(Value<?> value) { Value<?> left = getLeft().evaluate(); if (left instanceof ClassValue && getRight() instanceof VariableExpression) { String propertyName = ((VariableExpression) getRight()).getName(); ((ClassValue) left).setValue(propertyName, value); } } } 3. रैप अप इस भाग में, हमने कक्षाएं लागू कीं। अब हम और अधिक जटिल चीजें बना सकते हैं, जैसे कि ढेर कार्यान्वयन: main [] fun main [] stack = new Stack [] loop num in 0..5 # push 0,1,2,3,4 stack :: push [ num ] end size = stack :: size [] loop i in 0..size # should print 4,3,2,1,0 print stack :: pop [] end end class Stack [] arr = {} n = 0 fun push [ item ] this :: arr << item n = n + 1 end fun pop [] n = n - 1 item = arr { n } arr { n } = null return item end fun size [] return this :: n end end