Παρασκευή 29 Σεπτεμβρίου 2017

Using "=" vs "<=" in M2000.

Like in Basic, in M2000 we can declare a variable by assigning a value to a name. We use $ as last char for strings always. We use % as last char for integers, but integers in M2000 are double with no decimals. A statement A%=5.5 assign value 6. All computations in M2000work on a double accumulator. Strings can store very large content (16bit, UTF16LE) and are BSTR (used in COM objects).
When we declare names using equal sumbol "=" as a  name, the symbol  "=" and an expression then a local name inserted at the Variable List.  We can declate a variable in command line interpreter, or we say, using manual declaration: A=100 and now A is what we expected an A
Lets say that we make a module Alpha and in the code of Alpha we have A=100. Now a Alfa.A created. Every time, after local declaration, interpreter find local A and not A (from manual declaration). This is shadowing operation. A global variable has no hidden part, or module name, or function name, as part of the final name (lika Alpha.A for A in Alpha module).
If we wish to change value to global variable we have to use <=. When we use it, interpreter use name as is and search for it (using a hash function). If can't find then an error produced, because this "<=" is only for assigning a new value, not declaring a new variable.
The things get some peciuliar, if we think about groups of variables:
Group Example { X=10, Y=30}
Supposed that we make this group in Module Alpha. After declaration there are three variables: An Alpha.Example.X, an Alpha.Example.Y and Alpha.Example. The last variable is the object with a list of members. Each variable and member of Example group is local variable for Alpha.


See X and Y get initial values in Group declaration using "=". In Module Change, .X (dot  ex) which is Group member need a dot and <= to change value. If we place a X=1000 then that X became a Change.X as a local X in Charge. When we wish to change Y outside Example we use a simple "=".

Group Example {
      X=10, Y=30
      Module Change {
            .X<=1000
      }
}
Example.Y=500


All of that are true, if this code is in a module or function, and that module or function is either in a group module or in simple module (or in a group function or in simple function). There is an exception, and this post has to clarify:
Groups passed by value or by reference. By referencc the member list used to define menbers and a new list for these members, and for each member's name there is a pointer to point to original value. So when we altert a "referenced" value, actually we altered the original value. This not hold for "referenced" member list. We expand list, expand the referenced group, without altering the original member list. By value we simple make a new group, with no name. When a Read statement, with a simple name,  find in stack an anonymous group then make a new group using that simple name. Calling a module passing by value a group is the same as we Push a group in stack. Here we make a group from a class function, a constructor for a group (here using default constructor).
Class Alfa {
      X=10
      Y=5
}
Push Alfa()
Read Beta
List

With List command we can see the names of all variables. (with Module ? we can see the names of all modules/functions). Private members arn't displayed.
We can delcare Global Groups. And that is not "good" except we know why we do that. The reason for this ilustrated in the following example:

Class Alfa {
      X=10
      Y=5
}
Group Delta {
      Module Look {
           \\ Read Beta
            Read New Beta
            Beta2=Alfa()
            Group Beta3 {
                  X=10
                  Y=5
            }
            List
      }
}
Global Beta=Alfa()
Beta.X<=2000
Delta.Look Alfa()
Print Beta.X


If we use Read Beta in module Look in Delta, then because Beta is a global name, we get the a copy from group in stack, so X became 10. Using Read New we tell to Interpreter that Beta is a new name, so a new Beta (as Delta.Look.Beta) declared. Module's name is Delta.Look. 
Beta.X and Beta2.X because are made by aunamed groups (class return such a group) have no other parts, so when we wish to assign a value we have to use <= like these examples: Beta.X<=400 and Beta2.X<=400. But Group Beta3 has Beta3.X as Delta.Look.Beta3.X so need a simple =, so a Beta3.X=400 is ok.

If we change variables with properties then the same hold: If we declare a group from an anonymous group, and the name of that group is the same with a global one, then we write on the global one, except we use READ NEW.

Class Alfa {
      Property X=10
      Property Y=5
      Module TestMe {
            \\  .[X] is a value
            \\ .X is a group which we can read or write, or read and write
            Print .[X], .[Y]
      }
}
Group Delta {
      Module Look {
           \\ Read Beta
            Read New Beta
            Beta2=Alfa()
            Group Beta3 {
                  Property X=10
                  Property Y=5
            }
            Beta.X<=500
            List
            Print Beta.X, Beta2.X, Beta3.X
            Beta.TestMe
      }
}
Global Beta=Alfa()
Beta.X<=2000
Delta.Look Alfa()
Print Beta.X



So now we can see a bigger example, using by reference too:


Module Global CheckRef {
      Read &group1
      Print group1.x, "!!!!!!!!!!!!!!"
}
Group A1 {
      x=10
      Property A$="ok"
}

Group Alfa {
      Module Beta {
            Read AA
            AA.x++
             \\ need (<=)  because is in a function in a group and produced by Read
             \\ all data members are like globals.
            AA.x<=300
            Print AA.x
            Group BB {
                  X=10
            }
           \\ we can't use  (<=) all members are like locals
            BB.X=30
            checkref &BB
            checkref &AA
\\            List
      }
      Function Beta {
           Read AA
            AA.x++
            \\ need (<=)  because this group is in a function in a group and produced by Read
            AA.x<=300
            Print AA.x
           Group BB {
                  X=10
            }
            \\ we can't use  (<=) all members are like locals
            BB.X=30
            checkref &BB
            checkref &AA
            =BB
      }
      Function CheckFuncByRef {
            Read &Func()
            =Func(Group)
      }
}

Alfa.Beta A1

Module Beta2 {
      Read AA
      AA.x++
      \\  can't use (<=)  because Beta2 Module isn't a group module
      AA.x=300
      Group BB {
            X=10
      }
      \\ we can't use  (<=) all members are like locals
      BB.X=30
      checkref &BB
      checkref &AA
      Print AA.x
\\      List
}
Function Beta2 {
      Read AA
      AA.x++
      \\  can't use (<=)  because Beta2() function isn't a group function
      AA.x=300
      Group BB {
            X=10
      }
      \\ we can't use  (<=) all members are like locals
      BB.X=30
      checkref &BB
      Print AA.x
      CheckRef &AA
      =AA
}
Call Beta2 A1
A2=Alfa.Beta(A1)
A3=Beta2(A1)
Print "Check By Refrenece"
A4=Alfa.CheckFuncByRef(&Alfa.Beta(), A1)
Print A4.x, "_No problem"
A5=Alfa.CheckFuncByRef(&Beta2(), A1)
Print A5.x, "_No problem"

Τετάρτη 27 Σεπτεμβρίου 2017

Class in a class in M2000


We have one class, with two lists, a list of students and a list of history. We have one class as factory for making students groups of data/code (objects in M2000). We can add students, we can get a student and alter credits and put back again, we can move a student to another list, we can rename student (adding a title perhaps).
There are three modes for students. A simple object with name$, id$ and credits, where name$ and id$ are readonly properties, and credits is a field, a public field. In students inventory we put


font "Tahoma"
Form 60, 32
Print $(4),   ' so now we get proportional printing
Bold False ' so now we get no bold font on screen
\\ Class is a factory function for groups
\\ a Class in a Module has global scope (until Module erased)
\\ a Class in a Class (a group defintion) is member of group as own factory
\\ groups holded by one pointer everytime. We can pass by reference (a weak type, not a pointer)
\\ but we can set a pointer to other variable.
\\ for setting pointer we have special objects, like Arrays, Inventories, Stacks, which use pointers only
\\ Here we use Inventories as pointer holders, which map to keys (here the id of student).
\\ Groups can be copied, and can be merged to other groups.
\\ Properties are Groups with set and value. or set, or value part. So a readonly Property has only value.
\\ For each property there is a private field, for name$ is [name]$, so we can write from inside code in group.

Class Student {
            property name$ {Value}
            property Id$ {Value}
            \\ just a value
            Credits=0
            Class:
            \\  String and String, or String and Group
            \\ if "", "" then give an empry student
            Module Student (name$) {
                  If match("G") then {
                        Read it
                        if trim$(name$)="" then {
                              .[name]$<=it.name$
                        } else .[name]$<=name$
                        .Credits<=it.Credits
                        .[id]$<=it.id$
                       
                  } else {
                        Read check$
                         If trim$(check$) ="" then {
                              If trim$(name$) ="" then break
                              Error "No ID found"
                         }
                        .[id]$<=check$
                        If trim$(name$) ="" then Error "No name found"
                        .[name]$<=name$
                  }
                 
            }
      }
Class Students {
      Private:
      \\ inventory Queue can take same keys, but in a search through exist() function get the last one
      Inventory Queue historyStudents
      Inventory iStudents
      Class Stamp {
            TimeStamp=Now+Today
            Status$
      Class:
            Module Stamp (.Status$) {}
      }
      Class Subclass {
      \\ hide private here
      \\ using \\
      \\ and unhide list ! in ExportById()
      Private:
            \\ this is a pointer to Inventory (a list/map type in M2000)
            \\ first time we get an empty Inventory
            \\ but in constructor we get a reference to other Inventory.
            inventory connectTo
      Public:
            Module PutBack {
            \\ here Student("",This) clear This from connectTo and Module PutBack
            Return .connectTo, .id$:=Student("", This)
        }
      Class:
      \\ Class part used once. so Class Subclass return a group with two members:
      \\ a private reference to an inventory and a module  PutBack
      \\ this class added to an existing group, with id$
      \\ also need to exist a Student Class.
            Module Subclass (.connectTo) {}
      }
      Function DispStamp$ (x) {
           =str$(x, "yyyy|mm|dd|hhnnss|")
      }      
      Public:
      Module InfromHistory (id$, status$) {
             If exist(.iStudents, id$) then {
                       simple_student=eval(.iStudents)
                       simple_student=.Stamp(status$)
                       Append .historyStudents, id$:=simple_student
             }
      }
      Module RenameStudent (id$, newName$){
            If Trim$(newName$)="" Then Error "Please place a name"
            If exist(.iStudents, id$) then {
                  simple_student=Student(newName$, eval(.iStudents))
                  Return .iStudents, id$:=simple_student
                  \\  not needed here:    Sort Ascending .iStudents as number
                  \\ merge a stamp part
                  simple_student=.Stamp("Rename")
                  Append .historyStudents, id$:=simple_student
            } else Error "Id not exist"
      }
      Module AddStudentByName (name$, id$) {
            If exist(.iStudents, id$) then {
                  Error "Id exist"
            } else {
                  simple_student=Student(name$, id$)
                  PutTimeToclass=simple_student
                  PutTimeToClass=.Stamp("Included")
                  Append .historyStudents, id$:=PutTimeToClass
                  Append .iStudents, id$:=simple_student
                  Sort Ascending .iStudents as number
            }
      }
      Function GetById (id$) {
            If exist(.iStudents, id$) Then {
                  \\ we get a copy
                  M=Eval(.iStudents)
                  M=.Subclass(.iStudents)
                  =M
            } Else Error "Id not exist"
             \\ List : k=Key$
      }
      Function ExportById (id$) {
            If exist(.iStudents, id$) Then {
                  toExport=Eval(.iStudents)
                  Delete .iStudents, id$
                  Sort Ascending .iStudents as number
                  PutTimeToclass= toExport
                  PutTimeToclass=.Stamp("Excluded")
                  Append .historyStudents, id$:=PutTimeToClass
                  =toExport
            }  Else Error "Id not exist"
      }
      Module AddStudent (student){
            If exist(.iStudents, student.id$) Then {
                  Error "Student exist"
            } Else {
                  PutTimeToclass=student
                  PutTimeToclass=.Stamp("Included")
                  Append .historyStudents, student.id$:=PutTimeToClass
                  Append .iStudents, student.id$:=student
                  Sort Ascending .iStudents as number
            }
      }
      Property friendlyname$ {Value}
      Module Students (.[friendlyname]$){}
      Module PrintData {
            Italic : Pen 12 {Print "List of "; .friendlyname$ } : Italic
            M=Each(.iStudents)
            While M {
                  For .iStudents(M^!) {
                        Print .name$, .id$, .credits
                  }
            }
      }
      Module DisplayHistory {
            Bold : Print "History of "; .friendlyname$ : Bold
            M=Each(.historyStudents)
            If len(.historyStudents)>0 then {
                 Pen 11 { Print Part  "TimeStamp", "Status", "Name", "Id", "Credits"}
                 Print Under
            }
            While M {
                  For .historyStudents(M^!) {
                        Print This.DispStamp$(.TimeStamp) , .status$, .name$, .id$, .credits
                  }
            }      
      }
}

students=Students("Students")
graduateStudents=Students("Graduate Students")
students.AddStudentByName "George","11217"
students.AddStudentByName "Bilis","11417"
\\ M is a copy of original student
M=students.getById("11217")
\\ name$ is a property. All properties are public. We can define If a property return value, or take value, or do either.
Pen 15 {Print "Pick One Student: 11217: ";M.name$, "credits: ";M.credits}
\\ credits is not a property, but a field, and we have access because it is public
M.credits+=100
\\ we can putback because we have a reference to inventory (not to students)
M.PutBack
students.InfromHistory M.Id$, "Add 100"
graduateStudents.AddStudentByName "Mark","11317"
students.PrintData
graduateStudents.PrintData
Pen 15 {Print "Now student George Graduate"}
graduateStudents.AddStudent students.ExportById("11217")
students.PrintData
graduateStudents.PrintData
graduateStudents.DisplayHistory
Pen 15 {Print "Now student George has a phD"}
graduateStudents.RenameStudent  "11217" , "Dr George"
graduateStudents.PrintData
graduateStudents.DisplayHistory
students.DisplayHistory













Κυριακή 24 Σεπτεμβρίου 2017

Link Objects in M2000




M2000 has two kind of objects when we think about pointers. An Inventory is an object, a list of keys, or keys and data (pairs). This kind of object is a reference type always. We can make as many variables, or array items, or inventory items, or stack items to point to one Inventory.
Another type of object is the Group Object. This object has only one pointer to hold it, and is internal, and not exposed to user. So we can hold in one place only.

There is one type of reference, a reference by name, or Weak reference. We can pass a Group in a module using a reference to "current" name of group. We use & before name and actually we pass a string with full name of Group (we see in source always a name which is a small part of current name, Interpreter expand names, and use expansion for scoping purposes).

(
Newer versions support pointers to groups, to variables, and with the capability to have an internal number of how many pointers point to object, so when the last pointer dropped, the object deleted
check this   26/2/2019

)

A group may consist of many things, as data and as modules/functions. A module in a group has access to group data using . or This. (it is the same, but sometimes . is not reference to This. so we have explicit declare it, as we can see in the following examples )

This is a Group and some functionality:

Group Alfa {
      Private:
            X=10, Y=3
      Public:
      Module SetXY ( .X, .Y) {}
      Module SetY ( .Y) {}
      Module SetX ( .X) {}
      Module GetInfo {
            Print .X, .Y
      }
}

Alfa.SetXY 20,30
Alfa.GetInfo
Alfa.SetX 40
Alfa.GetInfo
Alfa.SetY 20
Alfa.GetInfo



Group Rules
A group is a prototype object. If we execute Beta=Alfa we get a Beta as a Copy of Alfa (not a reference). If Beta isn't new variable the it has to be an object (with any members, or no members), else we get an error. So when Beta is an object, then statement Beta=Alfa make a merging to Beta, so Beta is an Alfa and anything other members with names that not exist in Alfa. Groups in Arrays, Inventories, Stacks, can be merged, we put always a new one so B(2)=Alfa make a copy to B(2), and any object in B(2) before the copy just dropped


We can make Classes as functions which produce Groups too:

Class Alfa {
      Private:
            X=10, Y=3
      Public:
      Module SetXY ( .X, .Y) {}
      Module SetY ( .Y) {}
      Module SetX ( .X) {}
      Module GetInfo {
            Print .X, .Y
      }
      Class:
      Module Alfa {
            If Match("NN") Then Read .X, .Y : Exit
            If Match("N") and Stack.Size=1 then Read .X: Exit
            If Match("?N") Then Read ? .X, .Y
      }     
}

Alfa1=Alfa()
Alfa2=Alfa(100)
Alfa3=Alfa(100, 40)
Alfa4=Alfa(, 40)
Alfa1.GetInfo
Alfa2.GetInfo
Alfa3.GetInfo
Alfa4.GetInfo

Newer version can use this (automatic handle of optional variables).:

Class Alfa {
      Private:
            X=10, Y=3
      Public:
      Module SetXY ( .X, .Y) {}
      Module SetY ( .Y) {}
      Module SetX ( .X) {}
      Module GetInfo {
            Print .X, .Y
}
      Class:
      Module Alfa (.X,.Y){
}
}

Alfa1=Alfa()
Alfa2=Alfa(100)
Alfa3=Alfa(100, 40)
Alfa4=Alfa(, 40)
Alfa1.GetInfo
Alfa2.GetInfo
Alfa3.GetInfo
Alfa4.GetInfo


As we see in example above, we can use optional parameters. All arguments pushed in as stack for values, and modules and functions read this stack. But read the rules:
Stack Rules
A module get parent stack, and is responsible to leave stack cleaned from passing values. A function get a new stack and can leave anything, because stack destroyed after function call.
In Class function we have a constructor as a Module but this is called when we use class name as function, so module receive stack from function, so we can leave parameters and at return from function these erased. Because we can use constructor (if we place it before Class: label), as a module call, then we have to do something with garbages..on stack. So Constructor normally exist after a Class: label, which Interpreter use as they are internal in Group, but not exported by class function. (Class function make a closed group, and then no class: type data and modules/functions can be copied).
This is an example to illustrate this idiom for M2000 (happen when we use optional parameters):
Class Alfa {
      Private:
            X=10, Y=3
      Public:
      Module SetXY ( .X, .Y) {}
      Module SetY ( .Y) {}
      Module SetX ( .X) {}
      Module SetAny {
            If Match("NN") Then Read .X, .Y : Exit
            If Match("N") and Stack.Size=1 then Read .X: Exit
            If Match("N?") or Match("?N") Then Read ? .X, .Y
      }
      Module GetInfo {
            Print .X, .Y
      }
      Class:
      Module Alfa {
            .SetAny
      }     
}

Alfa1=Alfa()
Alfa2=Alfa(100)
Alfa3=Alfa(100, 40)
Alfa4=Alfa(, 40)
Alfa1.GetInfo
Alfa2.GetInfo
Alfa3.GetInfo
Alfa4.GetInfo
Alfa4.SetAny 1,2
Alfa4.GetInfo

\\ not secure because maybe stack has some values
Alfa4.SetAny 100
Alfa4.GetInfo
Alfa4.SetAny ,40
Alfa4.GetInfo
\\ This is secure for modules, because we pass two items
\\ and module take two.
Alfa4.SetAny 50,?
Alfa4.GetInfo



Student-Course Example
So we learn something about Groups and here is a way to Link a group to another group using a weak reference (as a string)
\\ Student
Class Student {
Private:
      StudentName$
      CourseWeak$
Public:
      Module Info {
            Print "StudentName "; .StudentName$
            If .CourseWeak$<>"" Then {
                  Print "Course:"
                  Rem "One Way": Print "Course:"; Eval$(.CourseWeak$.GetName$())
                  For .CourseWeak$ {
                        Print "","Name:";.GetName$()
                        Print "","ID:";.GetID$()
                  }
            }
      }
      Module GetCourse {
            Read .CourseWeak$
      }
Class:
      Module Student {
            Read .StudentName$
      }
}

Class Course {
Private:
      Name$
      Id$
Public:
      Function GetName$ {
            =.Name$
      }
      Function GetID$ {
            =.ID$
      }
Class:
      Module Course {
            Read .Name$, .ID$
            If .Name$="" then Error "Undefined name for Course"
            If Len(.ID$)<4 Then Error "Course's ID need at least 4 digits"
            If not .ID$ ~ "[1-9]"+String$("[0-9]", Len(.ID$)-1) then Error "Invalid chars  for Course's ID"
      }
}

S1=Student("Babis")
C1=Course("Java","1214")
S1.GetCourse &C1
S1.Info
Dim Courses(20)
Courses(1)=Course("M2000","1417")
S1.GetCourse Weak$(Courses(1))
S1.Info
As we see, we can pass reference to array item (using Weak$() function). So now we thing that this isn't a good way to link two objects;
We Think that using an array pointer is a better way. A variable which point to an array is an idea
Rules for Arrays and Pointers to Arrays
In M2000 the statement A=(1,2,3) make an array with 3 items and link to A. A statement Link A to A() give to A() a link to A so we can use Print A(1) to get 2. A statement: Dim B() and another: B()=A() get a copy of A() to B(), so if at the left hand of assignment we have this kind of array A() we get a copy. So A is a pointer to array, B() is an array and we can export a pointer: A=B(). Also we can copy new values B()=((1,2),(3,4)), and we get Print B(0)(0)=1 , but we have to Dim B() first or make a Link to B().



\\ Student
Class Student {
Private:
      StudentName$
      CourseHolder=(,)
      CourseRef=1
Public:
      Module Info {
            Print "StudentName "; .StudentName$
            If Len(.CourseHolder)>.CourseRef-1 Then {
                  link .CourseHolder to Courses()
                  Print "Course:"
                  For Courses(.CourseRef) {
                        Print "","Name:";.GetName$()
                        Print "","ID:";.GetID$()
                  }
            }
      }
      Module GetCourse {
            Read .CourseHolder, .CourseRef
      }
Class:
      Module Student {
            Read .StudentName$
      }
}

Class Course {
Private:
      Name$
      Id$
Public:
      Function GetName$ {
            =.Name$
      }
      Function GetID$ {
            =.ID$
      }
Class:
      Module Course {
            Read .Name$, .ID$
            If .Name$="" then Error "Undefined name for Course"
            If Len(.ID$)<4 Then Error "Course's ID need at least 4 digits"
            If not .ID$ ~ "[1-9]"+String$("[0-9]", Len(.ID$)-1) then Error "Invalid chars  for Course's ID"
      }
}

Dim Courses(20)
Courses(1)=Course("M2000","1417")
M=Courses()
S1=Student("Babis")
S1.GetCourse M, 1
S1.Info





So now lets drop arrays and do a work with Inventory. An Inventory can hold keys, or keys and data. There are two kinds. The normal one, where key can be feed as numeric or as string (but is string internal), and must be unique. The other one is the Inventory Queue, a special one, when we can but same keys, but always we get last one (except we take all keys with an iteration process). This kind of inventory can't delete any key, but only "drop" from the end of list (where we put the last keys). This kind of inventory ha same functionality (but isn't the same) as M2000 interpreter's list for variables.



\\ Student
Class Student {
Private:
      StudentName$
      Inventory CourseHolder
      CourseID$
  
Public:
      Module Info {
            Print "StudentName "; .StudentName$
            If .CourseID$<>"" Then {
                  Print "Course:"
                  For .CourseHolder(.CourseID$) {
                        Print "","Name:";.GetName$()
                        Print "","ID:";.GetID$()
                  }
            }
      }
      Module GetCourse {
            Read .CourseID$
      }
Class:
      Module Student {
            Read .CourseHolder, .StudentName$
      }
}
Class Course {
Private:
      Name$
      Id$
Public:
      Function GetName$ {
            =.Name$
      }
      Function GetID$ {
            =.ID$
      }
Class:
      Module Course {
            Read .Name$, .ID$
            If .Name$="" then Error "Undefined name for Course"
            If Len(.ID$)<4 Then Error "Course's ID need at least 4 digits"
            If not .ID$ ~ "[1-9]"+String$("[0-9]", Len(.ID$)-1) then Error "Invalid chars  for Course's ID"
      }
}

Inventory Courses
Append Courses "1417":=Course("M2000","1417")
S1=Student(Courses, "Babis")
S1.GetCourse "1417"
S1.Info

So now we want to make second Inventory for Students:



\\ Student
Class Student {
Private:
      StudentName$
      StudentID$="9999"
      Inventory CourseHolder
      CourseID$
  
Public:
      Module Info {
            Print "StudentName "; .StudentName$
            If .CourseID$<>"" Then {
                  Print "Course:"
                  For .CourseHolder(.CourseID$) {
                        Print "","Name:";.GetName$()
                        Print "","ID:";.GetID$()
                  }
            }
      }
      Module GetCourse {
            Read .CourseID$
      }
Class:
      Module Student {
            Read .CourseHolder, .StudentName$
            Read ? .StudentID$
      }
}
Class Course {
Private:
      Name$
      Id$
Public:
      Function GetName$ {
            =.Name$
      }
      Function GetID$ {
            =.ID$
      }
Class:
      Module Course {
            Read .Name$, .ID$
            If .Name$="" then Error "Undefined name for Course"
            If Len(.ID$)<4 Then Error "Course's ID need at least 4 digits"
            If not .ID$ ~ "[1-9]"+String$("[0-9]", Len(.ID$)-1) then Error "Invalid chars  for Course's ID"
      }
}

Inventory Courses, Students
Append Courses "1417":=Course("M2000","1417")
Append Students "1234":=Student(Courses, "Babis", "1234")
For Students("1234") {
      .GetCourse "1417"
      .Info
}



But here is a better code, we can place more than one course in a student

\\ Student
Class Student {
Private:
      StudentName$
      StudentID$="9999"
      Inventory CourseHolder, CourseIDs
  
Public:
      Module Info {
            Print "StudentName "; .StudentName$
            If Len(.CourseIDs)>0 Then {
                  Print "Course:"
                  CourseID=Each(.CourseIDs)
                  While CourseID {
                        For .CourseHolder(Eval$(CourseID)) {
                              Print "","Name:";.GetName$()
                              Print "","ID:";.GetID$()
                        }
                  }
            }
      }
      Function GetCourse {
            While not empty {
                  Read CourseID$
                  If Not exist(.CourseIDs, CourseID$ ) Then {
                        Append .CourseIDs, CourseID$
                        =true \\ this in not an exit
                  }
            }
      }
Class:
      Module Student {
            Read .CourseHolder, .StudentName$
            Read ? .StudentID$
      }
}
Class Course {
Private:
      Name$
      Id$
Public:
      Function GetName$ {
            =.Name$
      }
      Function GetID$ {
            =.ID$
      }
Class:
      Module Course {
            Read .Name$, .ID$
            If .Name$="" then Error "Undefined name for Course"
            If Len(.ID$)<4 Then Error "Course's ID need at least 4 digits"
            If not .ID$ ~ "[1-9]"+String$("[0-9]", Len(.ID$)-1) then Error "Invalid chars  for Course's ID"
      }
}

Inventory Courses, Students
Append Courses "1417":=Course("M2000","1417"), "1517":=Course("Java","1517")
Append Students "1234":=Student(Courses, "Babis", "1234")
For Students("1234") {
      If .GetCourse( "1417", "1517") Then .Info
}

Finally we make Courses to hold inventories with students too
\\ Student
Class Student {
Private:
      StudentName$
      StudentID$="9999"
      Inventory CourseHolder, CourseIDs
  
Public:
      Module SimpleInfo {
            Print .StudentID$;": "; .StudentName$
      }
      Module Info {
            Print .StudentID$;" : "; .StudentName$
            If Len(.CourseIDs)>0 Then {
                  Print "Course:"
                  CourseID=Each(.CourseIDs)
                  \\ CourseID is an object above CourseIDs, which add a cursor
                  \\ we can read cursor as CourseID^
                  While CourseID {
                        \\ normaly we use Eval$(CourseID!)  (look !)
                        \\ but here inventory CourseIDs has keys as data too.
                        \\ we can use .CourseHolder(CourseID^!)
\\                           For .CourseHolder(Eval$(CourseID))
\\                          For .CourseHolder(CourseID^!)
                        For .CourseHolder(Eval$(CourseID!)) {
                              Print "","Name:";.GetName$()
                              Print "","ID:";.GetID$()
                        }
                  }
            }
      }
      Function GetCourse {
            While not empty {
                  Read CourseID$
                  if Exist( .CourseHolder, CourseID$) Then {
                        If Not exist(.CourseIDs, CourseID$ ) Then {
                              Append .CourseIDs, CourseID$
                              For .CourseHolder(CourseID$) {
                                   .PlaceStudent This.StudentID$
                              }
                              =true \\ this in not an exit                      
                        }
                }  Else {
                      =false
                      Break
                }
            }
      }
Class:
      Module Student {
            Read .CourseHolder, .StudentName$
            Read ? .StudentID$
      }
}
Class Course {
Private:
      Name$
      Id$
      Inventory StudentHolder
      Inventory StudentList
Public:
      Module PlaceStudent {
            read StudentID$
            If Not Exist(.StudentList, StudentID$) Then Append .StudentList, StudentID$
      }
      Module Info {
      Print .Id$;" Students in Course ";.Name$
            M=Each(.StudentList)
            While M {
                  For .StudentHolder(Eval$(M)) {
                        .SimpleInfo
                  }
            }
      }
      Function GetName$ {
            =.Name$
      }
      Function GetID$ {
            =.ID$
      }
Class:
      Module Course {
            Read .Name$, .StudentHolder, .ID$
            If .Name$="" then Error "Undefined name for Course"
            If Len(.ID$)<4 Then Error "Course's ID need at least 4 digits"
            If not .ID$ ~ "[1-9]"+String$("[0-9]", Len(.ID$)-1) then Error "Invalid chars  for Course's ID"
      }
}
Inventory Students, Courses
Append Courses "1417":=Course("M2000",Students, "1417"), "1517":=Course("Java",Students, "1517")
Append Students "1234":=Student(Courses, "Babis", "1234")
Append Students "1235":=Student(Courses, "Fotis", "1235")
For Students("1234") {
      If .GetCourse( "1417", "1517") Then .Info
}
For Students("1235") {
      If .GetCourse( "1517") Then .Info
}
M=Each(Courses)
While M {
      \\ M^ is the counter and ! is a mark to place in Inventory counter and not key
      \\ We can use Eval$(M!) which return the current key
      For Courses(M^!) {
            .Info
      }
}

As we can see we get a double linked objects, from arrays of objects (Garbage collection is automatic in M2000), without use of a "second" reference to any object of type Group. Garbage collector works only for objects like Arrays, Inventories and Stacks.





Five years later and version 12 execute same code without problem.