Κυριακή 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.



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

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

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