paint-brush
स्क्रैच से अपनी खुद की प्रोग्रामिंग लैंग्वेज बनाना: भाग VII - कक्षाएंद्वारा@alexandermakeev
6,103 रीडिंग
6,103 रीडिंग

स्क्रैच से अपनी खुद की प्रोग्रामिंग लैंग्वेज बनाना: भाग VII - कक्षाएं

द्वारा Alexander Makeev22m2022/12/15
Read on Terminal Reader

बहुत लंबा; पढ़ने के लिए

अपनी स्वयं की प्रोग्रामिंग भाषा बनाने के इस भाग में हम कक्षाएं लागू करेंगे और अंत में वास्तविक स्टैक कार्यान्वयन लिखेंगे
featured image - स्क्रैच से अपनी खुद की प्रोग्रामिंग लैंग्वेज बनाना: भाग VII - कक्षाएं
Alexander Makeev HackerNoon profile picture

अपनी स्वयं की प्रोग्रामिंग भाषा बनाने के इस भाग में, हम कक्षाओं को पहले से परिभाषित संरचनाओं के विस्तार के रूप में लागू करेंगे। कृपया पिछले भागों को देखें:


  1. स्क्रैच से अपनी खुद की प्रोग्रामिंग लैंग्वेज बनाना
  2. स्क्रैच से अपनी खुद की प्रोग्रामिंग लैंग्वेज बनाना: भाग II - दिज्क्स्ट्रा का टू-स्टैक एल्गोरिथम
  3. बिल्ड योर ओन प्रोग्रामिंग लैंग्वेज पार्ट III: इम्प्रूविंग लेक्सिकल एनालिसिस विथ रेगेक्स लुकहेड्स
  4. स्क्रैच भाग IV से अपनी खुद की प्रोग्रामिंग लैंग्वेज बनाना: कार्यों को लागू करना
  5. स्क्रैच से अपनी खुद की प्रोग्रामिंग लैंग्वेज बनाना: भाग V - सारणियाँ
  6. स्क्रैच से अपनी खुद की प्रोग्रामिंग लैंग्वेज बनाना: भाग VI - लूप्स

पूर्ण स्रोत कोड उपलब्ध है गिटहब पर .


1. शाब्दिक विश्लेषण

पहले खंड में, हम शाब्दिक विश्लेषण को कवर करेंगे। संक्षेप में, यह स्रोत कोड को भाषा के शब्दों में विभाजित करने की एक प्रक्रिया है, जैसे कि कीवर्ड, चर, ऑपरेटर, आदि।


आप पिछले भागों से याद कर सकते हैं कि मैं टोकन टाइप एनम में रेगेक्स एक्सप्रेशन का उपयोग सभी लेक्सेम प्रकारों को परिभाषित करने के लिए कर रहा था।


आइए निम्न वर्ग के प्रोटोटाइप को देखें और लापता रेगेक्स भागों को हमारे TokenType में जोड़ें:


  1. सबसे पहले, हमें लेक्सिकल एनालाइज़र को यह बताने के लिए कि हमारा क्लास डिक्लेरेशन कहाँ से शुरू होता है, हमें 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; }


  1. दूसरा, हमें वर्तमान वस्तु के संदर्भ के लिए मार्कर के रूप में नया This लेक्सेम चाहिए:
 public enum TokenType { ... This("(this)(?=,|\\s|$)"); private final String regex; }


2. सिंटेक्स विश्लेषण

दूसरे खंड में, हम अपने भाषा नियमों का पालन करते हुए लेक्सिकल एनालाइज़र से प्राप्त शब्दांशों को अंतिम कथनों में रूपांतरित करेंगे।

2.1 परिभाषा का दायरा

जब हम एक वर्ग या एक समारोह की घोषणा करते हैं, तो यह घोषणा परिभाषित पृथक सीमाओं के भीतर उपलब्ध होनी चाहिए। उदाहरण के लिए, यदि हम निम्नलिखित सूची में turn_on [] नामक फ़ंक्शन की घोषणा करते हैं, तो यह घोषणा के बाद निष्पादन के लिए उपलब्ध होगा:


लेकिन अगर हम एक ही फंक्शन को क्लास स्कोप के अंदर घोषित करते हैं, तो यह फंक्शन सीधे मुख्य ब्लॉक से एक्सेस नहीं किया जाएगा:


  1. इन परिभाषा सीमाओं को लागू करने के लिए, हम 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<>(); } }


  1. इसके अलावा, हम माता-पिता की परिभाषा के दायरे तक पहुँचना चाह सकते हैं। उदाहरण के लिए, यदि हम दो अलग-अलग वर्गों की घोषणा करते हैं और दूसरे के अंदर प्रथम श्रेणी का उदाहरण बनाते हैं:


यह क्षमता प्रदान करने के लिए, हम 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; } }


  1. अब, एक परिभाषा जोड़ने के लिए इंटरफेस प्रदान करके कार्यान्वयन को समाप्त करते हैं और पैरेंट स्कोप का उपयोग करके इसे नाम से पुनः प्राप्त करते हैं:
 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); } }


  1. अंत में, घोषित परिभाषा क्षेत्रों को प्रबंधित करने और उनके बीच स्विच करने के लिए, हम java.util.Stack संग्रह (LIFO) का उपयोग करके संदर्भ वर्ग बनाते हैं:
 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 को कवर करेंगे।


  1. प्रत्येक घोषित चर, इसी तरह वर्ग या फ़ंक्शन परिभाषा के लिए, कोड के एक पृथक ब्लॉक के भीतर ही पहुंच योग्य होना चाहिए। उदाहरण के लिए, यदि हम निम्नलिखित सूची में एक चर को परिभाषित करते हैं, तो आप इसे घोषणा के ठीक बाद एक्सेस कर सकते हैं:


लेकिन अगर हम किसी फ़ंक्शन या वर्ग के भीतर एक चर घोषित करते हैं, तो चर अब कोड के मुख्य (ऊपरी) ब्लॉक से उपलब्ध नहीं होगा:


एक विशिष्ट दायरे में परिभाषित इस तर्क और स्टोर चर को लागू करने के लिए, हम MemoryScope वर्ग बनाते हैं जिसमें चर नाम के साथ एक कुंजी के रूप में एक मानचित्र और मान के रूप में चर Value होगा:

 public class MemoryScope { private final Map<String, Value<?>> variables; public MemoryScope() { this.variables = new HashMap<>(); } }


  1. अगला, 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; } }


  1. अगला, हम चर प्राप्त करने और सेट करने के तरीके जोड़ते हैं। जब हम एक चर सेट करते हैं, तो हम हमेशा पहले से निर्धारित मान को फिर से असाइन करते हैं यदि ऊपरी 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); } }


  1. 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 की संपत्ति को अपडेट करने का प्रयास करते हैं:


  1. अंत में, वेरिएबल्स को प्रबंधित करने और मेमोरी स्कोप के बीच स्विच करने के लिए, हम 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 वर्ग परिभाषा

इस खंड में, हम वर्ग परिभाषाओं को पढ़ेंगे और संग्रहीत करेंगे।


  1. सबसे पहले, हम स्टेटमेंट इम्प्लीमेंटेशन बनाते हैं। हर बार जब हम कक्षा का उदाहरण बनाते हैं तो यह कथन निष्पादित किया जाएगा:
 package org.example.toylanguage.statement; public class ClassStatement { }


  1. प्रत्येक वर्ग में आरंभीकरण और अन्य कार्यों के लिए एक कंस्ट्रक्टर की तरह नेस्टेड स्टेटमेंट हो सकते हैं। इन बयानों को संग्रहीत करने के लिए, हम कंपोजिटस्टेटमेंट का विस्तार करते हैं जिसमें निष्पादित करने के लिए नेस्टेड बयानों की सूची होती है:
 public class ClassStatement extends CompositeStatement { }


  1. अगला, हम क्लास के नाम, उसके तर्कों, कंस्ट्रक्टर स्टेटमेंट्स और क्लास के कार्यों के साथ डेफिनिशन स्कोप को स्टोर करने के लिए 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; }


  1. अब, हम 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 } } }


  1. तर्कों के बाद, हम नेस्टेड कंस्ट्रक्टर स्टेटमेंट को पढ़ने की उम्मीद करते हैं:


इन बयानों को स्टोर करने के लिए, हम पहले परिभाषित ClassStatement का एक उदाहरण बनाते हैं:

 private void parseClassDefinition() { ... ClassStatement classStatement = new ClassStatement(); }


  1. तर्कों और नेस्टेड बयानों के अलावा, हमारी कक्षाओं में कार्य भी हो सकते हैं। इन कार्यों को केवल वर्ग परिभाषा के भीतर सुलभ बनाने के लिए, हम DefinitionScope की एक नई परत शुरू करते हैं:
 private void parseClassDefinition() { ... ClassStatement classStatement = new ClassStatement(); DefinitionScope classScope = DefinitionContext.newScope(); }


  1. अगला, हम 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); }


  1. अंत में, क्लास कंस्ट्रक्टर स्टेटमेंट और फ़ंक्शंस को पढ़ने के लिए, हम स्टैटिक स्टेटमेंटपर्सर # पार्स () मेथड को कॉल करते हैं, जो classStatement इंस्टेंस के अंदर स्टेटमेंट्स को तब तक कलेक्ट करेगा, जब तक कि हम फाइनलिंग end लेक्सेम को पूरा नहीं कर लेते, जिसे अंत में छोड़ देना चाहिए:
 private void parseClassDefinition() { ... //parse class statements StatementParser.parse(this, classStatement, classScope); tokens.next(TokenType.Keyword, "end"); }


2.4 वर्ग उदाहरण

इस बिंदु पर, हम पहले से ही कंस्ट्रक्टर स्टेटमेंट और फ़ंक्शंस के साथ क्लास की परिभाषाएँ पढ़ सकते हैं। अब, वर्ग के उदाहरण को पार्स करते हैं:


  1. सबसे पहले, हम ClassValue को परिभाषित करते हैं, जिसमें प्रत्येक वर्ग के उदाहरण की स्थिति होगी। कक्षाओं के विपरीत, कार्यों के विपरीत, एक स्थायी MemoryScope होना चाहिए और यह दायरा हर बार जब हम कक्षा के उदाहरण के साथ बातचीत करते हैं तो सभी वर्ग के उदाहरण तर्कों और राज्य चर के साथ उपलब्ध होना चाहिए:
 public class ClassValue extends IterableValue<ClassDefinition> { private final MemoryScope memoryScope; public ClassValue(ClassDefinition definition, MemoryScope memoryScope) { super(definition); this.memoryScope = memoryScope; } }


  1. अगला, हम 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); } }


  1. कृपया ध्यान दें कि 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(); } } }


  1. अगला, हम शेष 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() { ... } }


  1. आइए Expression#evaluate() विधि को लागू करें जिसका उपयोग निष्पादन के दौरान पहले से परिभाषित ClassValue का एक उदाहरण बनाने के लिए किया जाएगा। सबसे पहले, हम Expression तर्कों का Value तर्कों में मूल्यांकन करते हैं:
 @Override public Value<?> evaluate() { //initialize class arguments List<Value<?>> values = arguments.stream() .map(Expression::evaluate) .collect(Collectors.toList()); }


  1. अगला, हम एक खाली मेमोरी स्कोप बनाते हैं जिसे अन्य वेरिएबल्स से अलग किया जाना चाहिए और इसमें केवल क्लास के इंस्टेंस स्टेट वेरिएबल्स शामिल हो सकते हैं:
 @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(); } }


  1. इसके बाद, हम 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 तर्कों में बदल दिया। अन्यथा, हम उदाहरण के लिए, वर्ग के उदाहरण तर्कों तक पहुँचने में सक्षम नहीं होंगे:


  1. और अंत में, हम 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 के लिए एक ही प्रतिनिधिमंडल कर सकते हैं।


  1. अंत में, हम 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 क्लास फंक्शन

इस समय, हम एक क्लास बना सकते हैं और क्लास के कंस्ट्रक्टर स्टेटमेंट को निष्पादित कर सकते हैं। लेकिन हम अभी भी वर्ग के कार्यों को क्रियान्वित करने में असमर्थ हैं।

  1. चलिए FunctionExpression#evaluate मेथड को ओवरलोड करते हैं जो ClassValue को उस क्लास इंस्टेंस के संदर्भ के रूप में स्वीकार करेगा जिससे हम एक फंक्शन इनवॉइस करना चाहते हैं:
 package org.example.toylanguage.expression; public class FunctionExpression implements Expression { ... public Value<?> evaluate(ClassValue classValue) { } }


  1. अगला कदम फ़ंक्शन Expression तर्कों को वर्तमान MemoryScope का उपयोग करके Value तर्कों में बदलना है:
 public Value<?> evaluate(ClassValue classValue) { //initialize function arguments List<Value<?>> values = argumentExpressions.stream() .map(Expression::evaluate) .collect(Collectors.toList()); }


  1. अगला, हमें कक्षा के 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);


  1. अंत में इस कार्यान्वयन के लिए, हम डिफ़ॉल्ट 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(); } }


  1. क्लास के फंक्शन को इनवोक करने के लिए, हम डबल कोलन :: ऑपरेटर का उपयोग करेंगे। वर्तमान में, इस ऑपरेटर को 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