Σάββατο 29 Αυγούστου 2020

OOP Programming M2000, Word Wrapping

 

Reading an article “Simulating Coroutines in Unmanaged C++”, I got the idea for this article. No we don’t use here Coroutines. We get only the example which tell for the text in the following frame need to formatted at screen, or any other output, to 44 characters as line length, using wrapping for words and excluding double or more spaces. The text supposed to be inserted from a file.


The Input File:



44
He [Sarek] was      delighted to discover   how very much like
him they    [computer people] were   ...  All they
cared about was the art of their
work, and doing            it right.  It was hard not
to admire such dedication and love of
computing for      its own   sake.
The programmers were the   first
Earth    people  he came   to   understand as being
really human.
Diane Duane.- "Spock's World."


The Output:



He [Sarek] was delighted to discover how
very much like him they [computer people]
were ... All they cared about was the art of
their work, and doing it right. It was hard
not to admire such dedication and love of
computing for its own sake. The programmers
were the first Earth people he came to
understand as being really human. Diane
Duane.- "Spock's World."



Our first solution has to be easy to implement. So we say that the input file is a string variable holding the text. Also we have to say that the output has to be performed to screen. The M2000 console can display text at a proportional or non proportional manner on character width. By default we use the non proportional (like in cmd.exe). We can use a FORM statement to make the line width above 44 characters, say FORM 60, 40 is good for this.


The First Program:



Module FirstProgram {
      a$={44
      He [Sarek] was      delighted to discover   how very much like
      him they    [computer people] were   ...  All they
      cared about was the art of their
      work, and doing            it right.  It was hard not
      to admire such dedication and love of
      computing for      its own   sake.
      The programmers were the   first
      Earth    people  he came   to   understand as being
      really human.
      Diane Duane.- "Spock's World."
      }
      word=false
      buff$=""
      line$=""
      \\ using "??" we get the integer value
      T=val(a$,"??",m) \\ m is the index of first char not included to value
      DRule$=""
      for i=0 to T div 10
            DRule$+=If$(i=0->string$(" ",10),string$(str$(i,""),10))
      next
      DRule$=Mid$(DRule$,2, T)
      Rule$=string$("1234567890", T div 10)+Mid$("1234567890",1, T mod 10)
      Print DRule$
      Print Rule$
      for i=m to len(a$)
            if chrcode(mid$(a$, i, 1))>32 then
                  if word then
                        Gosub SimpleSub
                        word=false
                        buff$=mid$(a$,i,1)
                  else
                        buff$+=mid$(a$,i,1)
                  end if
            else
                  word=true
                  
            end if
      next i
      Gosub SimpleSub
      If line$<>"" Then Print line$
      End
SimpleSub
:
            if len(buff$)>0 then
                  if len(line$)+1+len(buff$)>T then
                        if len(line$)>0 then Print line$
                        line$=""
                  end if
                  if len(line$)=0 then
                        line$=buff$
                  else
                        line$+=" "+buff$
                  end if
            end if      
      Return
}
FirstProgram



As we see from the FirstProgram module, we use some variables:

a$

String

The Input Text

word

Boolean

True means: Search for Word

False means: Word founded, look for other characters

buff$

String

A buffer for the word

line$

String

A buffer for line

T

Double

Hold the length for the proper output

DRule$

String

A computed string for first line of ruler

Rule$

String

A computed string for second line of ruler

m

Double

A helper which hold the index of not used char in Val().

i

Double

The index used in Mid$(a$, i, 1) to copy a character from a$


The ruler used for checking the output:


		
         11111111112222222222333333333344444
12345678901234567890123456789012345678901234


To check if a character has to stored in the word buffer we have to get the character code and if the code is bigger than 32 then this we say is a character for word. In our text we have 32 for Space, 13 and 10 for newline (two characters for a Crlf, or carriage return and line feed). So using the formula chrcode(mid$(a$, i, 1))>32 we check the code in index I on a$ string.


We need to use a boolean variable Word to determine if we have the first character for a word.


Charcode > 32

Word

Result

False

False

Word=True

False

True

Word=True

True

False

Append character to buff$

True

True

First character for a Word / see more


The last row has an additional logic. Before we pass the first character to buff$ we have to see if the buff$ has a word. We use Len(buff$) to check if we have something, so the value is non zero. So if we have a previous word we have to check if we can add to line$. For easy calculation we say that a word never get the T length or more tan T length. So we have only to check the total length of line, using len(line$) plus one for a space plus the len$(buff$). If this value is more than T then we need to print the line$ and then make the line$ as the buff$ (so this is the word wrapping), so the new character (the First character) now assigned to buff$, and Word change to False.


When Charcode function return a value bigger than 32 and Word is False then we append character to buff$. When we get a value from Charcode function of 32 or less we just turn Word to False.


We use a simple subroutine, because the same code used after the scanning of input text (a$) when we have a buff$ (a word) of length>0 so we have to put it to current line or print the current line and put it to the last line (as new current one). Simple subroutines in M2000 used like in BASIC, with a label and the Return statement. A simple subroutine execute code like the code is in the place we call it, using the same module name space, without using local variables.


Look how to make the ruler, using the two variables, DRule$ and Rule$. We use the String$() with a string expression and a number expression as parameters. We get a string times number.



Another Try

Because we need something better, we looking for using a Group (an Object) to mimic a file. So now we have a second program, to do that.



The Second Program



Module SecondProgram {
      Group MyFile {
      Private:
            index=1
            a$={44
            He [Sarek] was      delighted to discover   how very much like
            him they    [computer people] were   ...  All they
            cared about was the art of their
            work, and doing            it right.  It was hard not
            to admire such dedication and love of
            computing for      its own   sake.
            The programmers were the   first
            Earth    people  he came   to   understand as being
            really human.
            Diane Duane.- "Spock's World."
            }
      Public:
            \\ = 0=1 return boolean false
            Property EOF{value}=0=1
            Function GetNumber {
                  m=0
                  =val(mid$(.a$, .index),"??",m)
                  .index+=m
            }
            Function GetWord$ {
                  while chrcode(mid$(.a$, .index, 1))<33
                        .index++
                        if .index>len(.a$) then .[EOF]<=True: Break
                  end while
                  m=.index
                  while chrcode(mid$(.a$, .index, 1))>32
                        .index++
                        if .index>len(.a$) then .[EOF]<=True: Exit
                  end while
                  =mid$(.a$, m, .index-m)
            }
      }
      buff$=""
      line$=""
      T=MyFile.GetNumber()
      DRule$=""
      for i=0 to T div 10
            DRule$+=If$(i=0->string$(" ",10),string$(str$(i,""),10))
      next
      DRule$=Mid$(DRule$,2, T)
      Rule$=string$("1234567890", T div 10)+Mid$("1234567890",1, T mod 10)
      Print DRule$
      Print Rule$
      Repeat
            buff$=MyFile.GetWord$()
            if MyFile.EOF then exit
            if len(line$)+1+len(buff$)>T then
                  if len(line$)>0 then Print line$
                  line$=""
            end if
            if len(line$)=0 then
                  line$=buff$
            else
                  line$+=" "+buff$
            end if
      Always
      If line$<>"" Then Print line$
}
SecondProgram



Now we have the MyFile group. This group has two private members, the a$ as the input text and the index member. Also this group has three public members, a property EOF (for End Of File), and two methods, GetNumber() and GetWord$().


The main code has many items from first program. The subroutine not used here, because the loop, Repeat Always, works fine and has the part of subroutine part of it.


The new code didn’t use a Word boolean variable, because the method GetWord$() return a word or nothing. Also this function change the hidden variable [EOF] which return by the read only property EOF, and for MyFile is the MyFile.EOF. And the m variable not used in the main code because now exist only in method GetNumber().


How the method GetWord$() works?


Each time we check a character at index, we add one to index and check if the index is bigger than length of a$. The first time we check the character code for less than 33. We want to skip all these characters with code less than 33. If we get an EOF we just make a Break (this break the code of method GetWord$() and return the last value which we place as return value, or the default value, the empty string for a string function. So if we pass the first loop without breaking it we have the first character of a word. We get in a second loop while we find a character code bigger than 32. Here we use Exit because we need to return the word. We didn’t use a buff$ variable for word, we just get two indexes and then we get a sub string from a$ using Mid$().


Looking the two programs we see that the second one has an outer loop which are not connected to index variable (like variable i in first program). The index is hidden in Group object.



So now lets see another version


Third Try

What if we have a second object for rendering the output?

Here we put another public method in MyFile object, the OpeFile, which prepare the index, to value one, an reset the EOF internal (hidden/private) variable [EOF]. Also we make another group, the Render group with three members, the T for the width for rendering, the Ruler() and the Screen() methods. We pass the MyFile object by reference to Render object. We can display or not the ruler, but we have to call Render.Ruler to get the proper T from file in MyFile.


We use Type: File to make the type of group MyFile as File (so we can check when get a group in Ruler and Screen methods). Classes in M2000 always insert a proper type directive, but for groups we define by using Group statement we have to put it if we want it, by using Type: (we can add one or more names)


The code inside Render isn’t new to us.


Third Program



Module ThirdProgram {
      Group MyFile {
      Type: File
      Private:
            index=100000
            a$={44
            He [Sarek] was      delighted to discover   how very much like
            him they    [computer people] were   ...  All they
            cared about was the art of their
            work, and doing            it right.  It was hard not
            to admire such dedication and love of
            computing for      its own   sake.
            The programmers were the   first
            Earth    people  he came   to   understand as being
            really human.
            Diane Duane.- "Spock's World."
            }
      Public:
            Property EOF{value}=1=1
            Module OpenFile {
                  .index<=1
                  .[EOF]<=False
            }
            Function GetNumber {
                  m=0
                  =val(mid$(.a$, .index),"??",m)
                  .index+=m
            }
            Function GetWord$ {
                  while chrcode(mid$(.a$, .index, 1))<33
                        .index++
                        if .index>len(.a$) then .[EOF]<=True: Break
                  end while
                  m=.index
                  while chrcode(mid$(.a$, .index, 1))>32
                        .index++
                        if .index>len(.a$) then .[EOF]=True: Exit
                  end while
                  =mid$(.a$, m, .index-m)
            }
      }
      Group Render {
            T=0
            Module Ruler (&File as File, ToScreen=False){
                  .T<=File.GetNumber()
                  If .T>89 then Error "Cant handle such width"
                  DRule$=""
                  for i=0 to .T div 10
                        DRule$+=If$(i=0->string$(" ",10),string$(str$(i,""),10))
                  next
                  If Not ToScreen Then Exit
                  Print Mid$(DRule$,2, .T)
                  Print string$("1234567890", .T div 10)+Mid$("1234567890",1, .T mod 10)
            }
            Module Screen (&File as File) {
                  line$=""
                  buff$=""
                  Repeat
                        buff$=File.GetWord$()
                        if File.EOF then exit
                        if len(line$)+1+len(buff$)>.T then
                              if len(line$)>0 then Print line$
                              line$=""
                        end if
                        if len(line$)=0 then
                              line$=buff$
                        else
                              line$+=" "+buff$
                        end if
                  Always
                  If line$<>"" Then Print line$
            }
      }
      Flush
      MyFile.OpenFile
      Render.Ruler &MyFile, %ToScreen=True
      Render.Screen &MyFile
      Print MyFile.EOF
}
ThirdProgram



Forth Try


So can we refine our program to print line by line, calling ScreenLine() method?

Now Render group get the buff$ and line$ as members, because ScreenLine method need to use them for each call.





Module ForthProgram {
      Group MyFile {
      Type: File
      Private:
            index=100000
            a$={44
            He [Sarek] was      delighted to discover   how very much like
            him they    [computer people] were   ...  All they
            cared about was the art of their
            work, and doing            it right.  It was hard not
            to admire such dedication and love of
            computing for      its own   sake.
            The programmers were the   first
            Earth    people  he came   to   understand as being
            really human.
            Diane Duane.- "Spock's World."
            }
      Public:
            Property EOF{value}=1=1
            Module OpenFile {
                  .index<=1
                  .[EOF]<=False
            }
            Function GetNumber {
                  m=0
                  =val(mid$(.a$, .index),"??",m)
                  .index+=m
            }
            Function GetWord$ {
                  while chrcode(mid$(.a$, .index, 1))<33
                        .index++
                        if .index>len(.a$) then .[EOF]<=True: Break
                  end while
                  m=.index
                  while chrcode(mid$(.a$, .index, 1))>32
                        .index++
                        if .index>len(.a$) then .[EOF]=True: Exit
                  end while            
                  =mid$(.a$, m, .index-m)
            }
      }
      Group Render {
            T, line$, buff$
            Module Ruler (&File as File, ToScreen=False){
                  .line$<=""
                  .buff$<=""
                  .T<=File.GetNumber()
                  If .T>89 then Error "Cant handle such width"
                  DRule$=""
                  for i=0 to .T div 10
                        DRule$+=If$(i=0->string$(" ",10),string$(str$(i,""),10))
                  next
                  If Not ToScreen Then Exit
                  Print Mid$(DRule$,2, .T)
                  Print string$("1234567890", .T div 10)+Mid$("1234567890",1, .T mod 10)
            }
            Function ScreenLine (&File as File) {
                  Repeat
                        if .buff$="" then .buff$<=File.GetWord$(): =True
                        if File.EOF then =False : exit
                        if len(.line$)+1+len(.buff$)>.T then exit
                        .line$+=If$(len(.line$)=0->"", " ")+.buff$
                        .buff$<=""
                  Always
                  if .line$<>"" then Print .line$ : .line$<=""
            }
      }
      Flush
      MyFile.OpenFile
      Render.Ruler &MyFile, %ToScreen=True
      While Render.ScreenLine(&MyFile)
            Wait 10
      End While
      Print MyFile.EOF
}
ForthProgram



Fifth Try

Lets say now that we want to change the loop to print word by word (not line by line as the forth program).

Now we use line$ only for calculations. So we print word as P$ (with or wthout a leading space) and a “;” at the end so we didn’t get a new line. We use Print with no parameters when we want to place a new line.


Fifth Program



Module FifthProgram {
      Group MyFile {
      Type: File
      Private:
            index=100000
            a$={44
            He [Sarek] was      delighted to discover   how very much like
            him they    [computer people] were   ...  All they
            cared about was the art of their
            work, and doing            it right.  It was hard not
            to admire such dedication and love of
            computing for      its own   sake.
            The programmers were the   first
            Earth    people  he came   to   understand as being
            really human.
            Diane Duane.- "Spock's World."
            }
      Public:
            Property EOF{value}=1=1
            Module OpenFile {
                  .index<=1
                  .[EOF]<=False
            }
            Function GetNumber {
                  m=0
                  =val(mid$(.a$, .index),"??",m)
                  .index+=m
            }
            Function GetWord$ {
                  while chrcode(mid$(.a$, .index, 1))<33
                        .index++
                        if .index>len(.a$) then .[EOF]<=True: Break
                  end while
                  m=.index
                  while chrcode(mid$(.a$, .index, 1))>32
                        .index++
                        if .index>len(.a$) then .[EOF]=True: Exit
                  end while            
                  =mid$(.a$, m, .index-m)
            }
      }
      Group Render {
            T, line$, buff$
            Module Ruler (&File as File, ToScreen=False){
                  .line$<=""
                  .buff$<=""
                  .T<=File.GetNumber()
                  If .T>89 then Error "Cant handle such width"
                  DRule$=""
                  for i=0 to .T div 10
                        DRule$+=If$(i=0->string$(" ",10),string$(str$(i,""),10))
                  next
                  If Not ToScreen Then Exit
                  Print Mid$(DRule$,2, .T)
                  Print string$("1234567890", .T div 10)+Mid$("1234567890",1, .T mod 10)
            }
            Function ScreenWord (&File as File) {
                  .buff$<=File.GetWord$(): =True
                  if File.EOF then =False : Print : exit
                  if len(.line$)+1+len(.buff$)>.T then Print : .line$<=""
                  P$=If$(len(.line$)=0->"", " ")+.buff$
                  Print P$;
                  .line$+=P$
            }
      }
      Flush
      MyFile.OpenFile
      Render.Ruler &MyFile, %ToScreen=True
      While Render.ScreenWord(&MyFile)
            Wait 2
      End While
      Print MyFile.EOF
}
FifthProgram



Sixth Try


So now its time to change MyFile group. We want to have a GetChar() member. Logic for End of File now moved from GetWord() method to GetChar() method.

Method GetWord$ change to not use the index variable, but the GetChar method.


The Z variable display the number of Words.


Sixth Program



Module SixthProgram {
      Group MyFile {
      Type: File
      Private:
            index=100000
            a$={44
            He [Sarek] was      delighted to discover   how very much like
            him they    [computer people] were   ...  All they
            cared about was the art of their
            work, and doing            it right.  It was hard not
            to admire such dedication and love of
            computing for      its own   sake.
            The programmers were the   first
            Earth    people  he came   to   understand as being
            really human.
            Diane Duane.- "Spock's World."
            }
      Public:
            Property EOF{value}=1=1
            Module OpenFile {
                  .index<=1
                  .[EOF]<=False
            }
            Function GetNumber {
                  =int(val(.GetWord$()))
            }
            Function GetChar(&C$) {
                  If Not .[EOF] Then
                        =True
                        C$=mid$(.a$, .index, 1)
                        .index++
                        if .index>len(.a$) then .[EOF]<=True
                  End If
            }
            Function GetWord$ {
                  W$=""
                  Word$=""
                  Do
                        If Not .GetChar(&W$) Then Break
                        if chrcode(W$)>32 Then Exit
                  Always
                  Word$+=W$
                  Do
                        If Not .GetChar(&W$) Then Exit
                        if chrcode(W$)<33 Then Exit
                        Word$+=W$
                  Always
                  =Word$
            }
      }
      Group Render {
            T, line$, buff$
            Module Ruler (&File as File, ToScreen=False){
                  (.T, .line$, .buff$)=(File.GetNumber(), "", "")
                  If .T>89 then Error "Cant handle such width"
                  DRule$=""
                  for i=0 to .T div 10
                        DRule$+=If$(i=0->string$(" ",10),string$(str$(i,""),10))
                  next
                  If Not ToScreen Then Exit
                  Print Mid$(DRule$,2, .T)
                  Print string$("1234567890", .T div 10)+Mid$("1234567890",1, .T mod 10)
            }
            Function ScreenWord (&File as File) {
                  \\ by default a arithmetic funcion return 0 (false)
                  .buff$<=File.GetWord$()
                  if File.EOF then Print : exit
                  if len(.line$)+1+len(.buff$)>.T then Print : .line$<=""
                  P$=If$(len(.line$)=0->"", " ")+.buff$
                  Print P$;
                  .line$+=P$
                  =True
            }
      }
      Flush
      MyFile.OpenFile
      Render.Ruler &MyFile, %ToScreen=True
      Z=0
      While Render.ScreenWord(&MyFile)
      Z++
      End While
      Print MyFile.EOF
      Print "Z=";Z
}
SixthProgram


Final Try

We skip some tries, to get the final try. We change MyFile Group and make the File Class. The File Class has two members, the F as the file handler, and the ANSI boolean variable to indicate if the file is ANSI or not (a UTF16LE encoding). Now the a$ not exist. We have a file, named spoke, in current directory (dir$ return this). Also we use a ANSI variable in Module FinalProgram to get a random number, and then we prepare a file as ANSI or UTF16LE.


Class File make MyFile object. We have new methods OpenFile, CloseFile and the constructor File (exist only when we make the object, but not to the final object because we have it after a Class: directive). The File constructor get the type of file.


So what we have to change to read from a file?

We change only the GetChar method. Files have the Eof() function so we prepare property EOF according to Eof() function. Also we have to Open the file, and at the end we have to close it.



Final Program



Module FinalProgram {
      Ansi=Random(-1, 0)
      Print "We make a file in dir:"
      Print Dir$
      \\ Locale used for converting properly internal UTF16LE to and from ANSI
      Locale 1033
      If Ansi Then
            Open "spoke" for output as #F
            Print "ANSI file - Using Locale=";Locale
      Else
            Open "spoke" for wide output as #F
            Print "UTF 16LE File"
      End if
      Print #F, {44
            He [Sarek] was      delighted to discover   how very much like
            him they    [computer people] were   ...  All they
            cared about was the art of their
            work, and doing            it right.  It was hard not
            to admire such dedication and love of
            computing for      its own   sake.
            The programmers were the   first
            Earth    people  he came   to   understand as being
            really human.
            Diane Duane.- "Spock's World."
            }
      Close #F
      Print "File Length in bytes:"; Filelen("spoke")
      Class File {
      Private:
            F=0
            Ansi=False
      Public:
            Property EOF{value}=1=1
            Module OpenFile(name$) {
                  If .F>0 then Error "Close current file"
                  If .Ansi Then
                        Open name$ for input as #.F
                  else
                        Open name$ for wide input as #.F
                  end if
            }
            Module CloseFile {
                  If .F>0 then Close #.F
            }
            Function GetNumber {
                  =int(val(.GetWord$()))
            }
            Function GetChar(&C$) {
                  .[EOF]<=Eof(#.F)
                  If Not .[EOF] Then
                        =True
                        C$=Input$(.F, If(.Ansi->2, 1))
                        .[EOF]<=Eof(#.F)
                  End If
            }
            Function GetWord$ {
                  W$=""
                  Word$=""
                  Do
                        If Not .GetChar(&W$) Then Break
                        if chrcode(W$)>32 Then Exit
                  Always
                  Word$+=W$
                  Do
                        If Not .GetChar(&W$) Then Exit
                        if chrcode(W$)<33 Then Exit
                        Word$+=W$
                  Always
                  =Word$
            }
      Class:
            Module File(Ftype) {
                  .Ansi<=Ftype
            }
      }
      Group Render {
            T, line$, buff$
            Module Ruler (&File as File, ToScreen=False){
                  (.T, .line$, .buff$)=(File.GetNumber(), "", "")
                  If .T>89 then Error "Cant handle such width"
                  DRule$=""
                  for i=0 to .T div 10
                        DRule$+=If$(i=0->string$(" ",10),string$(str$(i,""),10))
                  next
                  If Not ToScreen Then Exit
                  Print Mid$(DRule$,2, .T)
                  Print string$("1234567890", .T div 10)+Mid$("1234567890",1, .T mod 10)
            }
            Function ScreenWord (&File as File) {
                  \\ by default a arithmetic funcion return 0 (false)
                  .buff$<=File.GetWord$()
                  if File.EOF then Print : exit
                  if len(.line$)+1+len(.buff$)>.T then Print : .line$<=""
                  P$=If$(len(.line$)=0->"", " ")+.buff$
                  Print P$;
                  .line$+=P$
                  =True
            }
      }
      Flush
      Print "Process"
      Myfile=File(ANSI)
      MyFile.OpenFile "Spoke"
      Render.Ruler &MyFile, %ToScreen=True
      Z=0
      While Render.ScreenWord(&MyFile)
            Z++
      End While
      Print MyFile.EOF
      MyFile.CloseFile
      Print "Count **words**=";Z
}
FinalProgram



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

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

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