Κυριακή 10 Οκτωβρίου 2021

Classes, Groups and some tricks in M2000

Let see an example in OOP in M2000:

We have a global group coGroup, which have a state as NoUse (hold the times we write to an output, here using Print). Every time we call WriteOnce we add one to NoUse (as This.NoUse or .NoUse). Also another method (module PrintMany) return the current value of .NoUse

We have a class definition (is global by default),  with a ThisOne private which take a pointer to coGroup. Because coGroup is a "static" object on a module (life of a group is bound to module's life) the pointer() function return a pointer object which hold a weak reference to group. A pointer for group can point as a real pointer: a=pointer((coGroup)) which give a pointer to a copy of coGroup. Because we want to use coGroup as is, we only need a reference for two classes, the One() and Two().

A class in M2000 is a function. The M2000 Interpreter get the definition and make a function. When we set a second class (here MyClass), so one and two classes inherit from myClass, interpreter combine definitions. Also Classes add a type: label with the name of class. The combined classes, One with MyClass and Two with MyClass,  have both of them at the start of code the MyClass code. Also the type: label has now both names (type: MyClass, One ), for One() class and (type: MyClass, Two) for Two() class.

When we call One() a group returned (with no name), so we can put it in a name, here variable a. The same for Two() but we get a pointer of that group to b using ->. We can use b=pointer(Two()) is the same as b->Two(). We get real pointer because Two() return an not named group. This is different from ThisOne=Pointer(coGroup) because coGroup is a named Group.  The coGroup name isn't a pointer, is actual the Group (internal there is a pointer, but that pointer not exposed to user), A statement c=coGroup, where c is a new name create a new group as a copy of coGroup (internal a new pointer point to actual object). So for M2000 there are objects of type Group which have name, and used as variables, and there are pointers for groups, which can point using a weak reference to variable (a named group) or using a real pointer (which hold alive a group to a nameless group). In an array item we can hold a nameless group, so if A() is an array (no type needed, internal is a variant type) the A(3)=coGroup pass a copy of coGroup to A(3).

In a module A (type edit A, press enter, in M2000 console) copy this, then press Esc and type A and press enter to execute it.

global group coGroup {
private:
NoUse=0
public:
Module WriteOnce (a$) {
.NoUse++
Print a$
}
Module PrintMany {
Print .Nouse
}
}
Class MyClass {
private:
ThisOne=Pointer(coGroup)
public:
module doSomething {
Error "Abstract"
}
}
Class One as MyCLass {
module doSomething {
.ThisOne=>WriteOnce "Hello"
.ThisOne=>WriteOnce "------"
}
}
Class Two as MyCLass {
module doSomething {
.ThisOne=>WriteOnce "ok"
}
}
a=One() // a is a One() Group
b->Two() // b is a pointer to a new Two()
b=>doSomething
b=>doSomething
a.doSomething
b=>doSomething
coGroup.PrintMany


Look that for b we use => to access the doSomething method (which is a module). Modules by definition have own scope, local, and can see any global. For modules defined as methods in groups, there is also the This.NameOfField  or just use a dot and the name of field to access the object's field. Because the = used to define a local variable, when we have to assign a new value to a group field, we use the <= which used for altering global values too (the <= operator can't define new variables), For this example we don't alter any field with <=. We alter the NoUse (number of use), using ++.

Module WriteOnce has a parameter a$. Interpreter get this and insert a Read a$ statement. The Read statement pop values from stack of values. When we call modules we pass the current stack and if we pass values then that values form a stack and join to current stack. M2000 interpreter never count how many parameters we send (except for special cases). Modules have the responsibility to leave stack of values at a proper state. The stack of values are different from process stack. So modules can return values handling the stack of values.

Second variation. (copy the code in a B module, type edit B press enter, copy the code, press esc, type B press enter),

Now group coGroup has another group inside. This called a property, if return or set value. If the value return or set a string then we have to declare it in the name (WriteOnce$). An inner group can't access anything above it. We can use a link parent statement to link by reference a field name from parent group. Here in Group WriteOnce$ we have a set part which get a string (using letter$ which pop a string from stack of values, or we can use Read aValue$ and then Print aValue$, but there is no reason to make a variable when we can work with immediate use of a value).

Now we have in DoSomething in One() class this: .ThisOne=>WriteOnce$="Hello" (assign a value to a property, which is an object of type group) and not .ThisOne=>WriteOnce "Hello"  (calling a module). We can't assign a group in an inner group if that group has a set or value or both parts. 



global group coGroup {
private:
NoUse=0
public:
Group WriteOnce$ {
set {
link parent NoUse to NoUse
NoUse++
Print Letter$
}
}
Module PrintMany {
Print .Nouse
}
}
Class MyClass {
private:
ThisOne=Pointer(coGroup)
public:
module doSomething {
Error "Abstract"
}
}
Class One as MyCLass {
module doSomething {
.ThisOne=>WriteOnce$="Hello"
.ThisOne=>WriteOnce$="------"
}
}
Class Two as MyCLass {
module doSomething {
.ThisOne=>WriteOnce$="ok"
}
}
a=One() // a is a One() Group
b->Two() // b is a pointer to a new Two()
b=>doSomething
b=>doSomething
a.doSomething
b=>doSomething
coGroup.PrintMany


Third variation

For this variation we have a PrintMany out of group coGroup. We need to read the private field NoUse. We can read a private field only in a module or function on a group we defined that field. Also if we have more than one groups with same type and we pass one of them as parameter to other, then from other and inside a method (module or function) we can read private fields. 

So PrintMany get a copy of coGroup (checking if is type coGroup). We can define types for groups (automatic for classes) using type: label (we can place more than one name using comma, but we have to use one line). In the code of PrintMany we have a definition for ins group. This group merging to group a (the copy of coGroup) and through the method value we set to group a the value part. So a simple Print a  call the value part which return the .NoUse

Now inner group WriteOnce$ has a field oldvalue$ and a method PrintOld. See that in module Repeat we can call .WriteOnce.PrintOld, because PrintOld is public in .WriteOnce. Group name WriteOnce$ has also the WriteOnce without $ for using it with methods and or public fields.


global group coGroup {
type: coGroup
private:
NoUse=0
public:
Module Repeat (n as integer) {
while n>0
n--
.WriteOnce.PrintOld
end while
}
Group WriteOnce$ {
private:
oldvalue$
public:
Module PrintOld {
link parent NoUse to NoUse
NoUse++
Print .oldvalue$
}
set {
.oldvalue$<=Letter$ // read a string value else we get error
.PrintOld
}
}
}
// PrintMany get  a copy of a coGroup
Module PrintMany (a as coGroup) {
group ins {
value {=.NoUse}
}
// adding a value property to a
a=group(ins)
Print a
}
Class MyClass {
private:
ThisOne=Pointer(coGroup)
public:
module doSomething {
Error "Abstract"
}
}
Class One as MyCLass {
module doSomething {
.ThisOne=>WriteOnce$="Hello"
.ThisOne=>WriteOnce$="------"
.ThisOne=>Repeat 5
}
}
Class Two as MyCLass {
module doSomething {
.ThisOne=>WriteOnce$="ok"
.ThisOne=>WriteOnce.PrintOld
}
}
a=One() // a is a One() Group
b->Two() // b is a pointer to a new Two()
b=>doSomething
PrintMany coGroup // print 2
b=>doSomething
a.doSomething
b=>doSomething
PrintMany coGroup // print 13


Variation on third variation

We can make a fake coGroup type. Only the name of type is the same; Because interpreter check the types using the name (or names), we can pass the a as coGroup and now the a.NoUse can be read from PrintMany

Group Slave {
type: coGroup
Module PrintMany (a as coGroup) {
Print a.NoUse
}
}


We have to change the last lines for the example:

a=One() // a is a One() Group
b->Two() // b is a pointer to a new Two()
b=>doSomething
Slave.PrintMany coGroup // print 2
b=>doSomething
a.doSomething
b=>doSomething
Slave.PrintMany coGroup // print 13


Another variation:

Group Slave {
type: coGroup
Module PrintMany (a as *coGroup) {
Print a=>NoUse
}
}

and the last lines:

a=One() // a is a One() Group
b->Two() // b is a pointer to a new Two()
b=>doSomething
Slave.PrintMany pointer((coGroup)) // print 2
b=>doSomething
a.doSomething
b=>doSomething
Slave.PrintMany pointer((coGroup)) // print 13


and another variation using reference:

Group Slave {
type: coGroup
Module PrintMany (&a as coGroup) {
Print a.NoUse
}
}


and the last lines:

a=One() // a is a One() Group
b->Two() // b is a pointer to a new Two()
b=>doSomething
Slave.PrintMany &coGroup // print 2
b=>doSomething
a.doSomething
b=>doSomething
Slave.PrintMany &coGroup // print 13


There is a fault if we use this:

Group Slave {
type: coGroup
Module PrintMany (a as *coGroup) {
Print a=>NoUse
}
}

and make the call to Slave.PrintMany using this Slave.PrintMany pointer(coGroup) because pointer(coGroup) return a weak reference (same as &coGroup)  inside a pointer object. So when we use a=>NoUse the "a=>" part change with the weak reference so we can't read NoUse because it is private. All the other methods use either a copy of coGroup or a normal reference of coGroup (which is a new group with each field using a reference to the reference group's same named field.

If we use this:

Group Slave {
type: coGroup
Module PrintMany (&a as coGroup) {
Print a.NoUse
}
}

we can use pointer to group (as weak reference) like this:

a=One() // a is a One() Group
b->Two() // b is a pointer to a new Two()
b=>doSomething
Slave.PrintMany pointer(coGroup) // print 2
b=>doSomething
a.doSomething
b=>doSomething
cc->coGroup
Slave.PrintMany cc // print 13

This can be done because the not shown Read (which interpreter insert on module PrintMany) statement try to read &a and because expect a weak reference can extract this from the pointer we pass (which have a weak reference).




Δεν υπάρχουν σχόλια:

Δημοσίευση σχολίου

You can feel free to write any suggestion, or idea on the subject.