Πέμπτη 30 Δεκεμβρίου 2021

Making the Karel Ide (part two)

Now the Karel Ide has a list from we can choose a word to paste in editor. Multiple Undo and Redo works. When we move the mouse pointer on the left half side with holding the left mouse button down, we select item, and we see help in the status line. Also the same list at the end has IDE operations (Load Save etc). These are not ready yet.

Version 10, Revision 51 of M2000 Interpreter are ready to install.

Happy New Year.



Title "IDE"
const KAREL_INSTRUCTIONS$="|MOVE|TURNLEFT|PICKBEEPER|PUTBEEPER|TURNOFF"
const KAREL_COMMAND$ = "|BEGINNING-OF-PROGRAM|END-OF-PROGRAM|BEGINNING-OF-EXECUTION|END-OF-EXECUTION|BEGIN|END|DEFINE|IF|ELSE|ITERATE|WHILE"
const LASTPART$="|TIMES|AS|DO|THEN"
const FLAGS$ = "|FRONT-IS-CLEAR|FRONT-IS-BLOCKED|LEFT-IS-CLEAR|LEFT-IS-BLOCKED|RIGHT-IS-CLEAR|RIGHT-IS-BLOCKED|BACK-IS-CLEAR|BACK-IS-BLOCKED|NEXT-TO-A-BEEPER|NOT-NEXT-TO-A-BEEPER|ANY-BEEPERS-IN-BEEPER-BAG|NO-BEEPERS-IN-BEEPER-BAG|FACING-NORTH|NOT-FACING-NORTH|FACING-SOUTH|NOT-FACING-SOUTH|FACING-EAST|NOT-FACING-EAST|FACING-WEST|NOT-FACING-WEST"
const that$ = {`'"[]*+.,=-"}+"{}!@#$%^&/\<>?~"
const crlf$={
}
tab$=chr$(9)
listtext$="Commands"+Replace$("|", crlf$+tab$,KAREL_COMMAND$+crlf$+"After Command"+LASTPART$+crlf$+"Instructions"+KAREL_INSTRUCTIONS$+crlf$+"Flags"+FLAGS$+crlf$+"IDE Operations|Load|Save|New|Run|Run Again")
Blocked=list:="Commands", "After Command", "Flags", "IDE Operations", "Instructions"
Declare IDE_Karel Form
Declare Karel_Pad EditBox Form IDE_Karel
Declare Karel_List ListBox Form IDE_Karel
Declare Karel_Status ListBox Form IDE_Karel
With IDE_Karel,"UseIcon", True, "UseReverse", True, "Quit" as Quit
With IDE_Karel, "Title" as Caption$, "Visible" as Visible, "TitleHeight" as tHeight, "Sizable", True
Method IDE_Karel,"MakeStandardInfo", 1
Method IDE_Karel, "FontAttr", "Verdana", 18, true '  size=12, bold=true
Method Karel_List, "FontAttr", "Verdana", 14, true '  size=12, bold=true
Method Karel_Pad, "FontAttr", "Verdana", 14, true '  size=12, bold=true
Method Karel_Status, "FontAttr", "Verdana", 12, true
Method Karel_List, "SetBarStyle", color(50, 100, 200), 5, 15
Method Karel_Pad, "SetBarStyle", color(50, 100, 200), 5, 15
With Karel_Pad, "NoWrap", True, "SelLength" as SelLength, "HighlightParagraph", True
With Karel_List, "ShowAlways", true, "Text", listtext$, "transparent", false , "maychange", false, "SkipFirstClick", true
With Karel_Status, "ShowAlways", true,"Text" as status$, "transparent", true, "HideCaret", True, "displaylines", 1
Method Karel_List, "Colors", color(240,240,255), 1
With Karel_List, "List" As List$(), "listindex" as ListIndex, "title", "IDE Menu", "HideCaret", True
With Karel_Pad, "Text" as Pad.Text$, "NoColor", False, "ShowAlways", True, "SpaceIndent", 3
With Karel_Pad, "ColorCollection1", KAREL_COMMAND$+"|"
With Karel_Pad, "ColorCollection3", LASTPART$+"|"
With Karel_Pad, "ColorCollection4", FLAGS$+"|", "Default", true
With Karel_Pad, "Seltext" as SelText$, "SelStart" as SelStart, "SelLength" as SelLength, "InsertTextNoRender" as Insert$
Module InnerControl (listbox) {
// control is the real control on form
// which our listbox "subclass it"
method listbox, "control" as control
with control, "dcolor", -Color(255,40,0), "CapColor", -color(11)
with control, "backcolor", -Color(240,240,255), "forecolor", -color(1)
}
InnerControl Karel_List
'InnerControl Karel_Status
With Karel_Pad, "WordCharRightButIncluded", "-", "StartSymbols", "123456789"+that$
With Karel_Pad, "WordCharLeft"," ", "WordCharRight", " ", "DropSym", "-", "EXTRAFRONT", "FLRBNAEflrbnae"+that$, "LineComment1","", "LineComment2", "", "CommentSymbols","", "ComSymbolsWidth", 0, "CommentLineLight", false, "MultiLineComment1", "","MultiLineComment2", ""
'With Karel_Pad, "WordCharLeft"," ", "WordCharRight", " ", "EXTRAFRONT","FLRBNAEflrbnae"
Method  Karel_Pad, "UserColorSet",0,,,0,-color(5), -#005588
With Karel_Pad, "ColorSet", -1
Pad.Text$={BEGINNING-OF-PROGRAM
            DEFINE MOVE-IF-YOU-CAN AS
               BEGIN
                  IF FRONT-IS-CLEAR THEN
                        MOVE
               END
               BEGINNING-OF-EXECUTION
                  MOVE-IF-YOU-CAN
                  TURNOFF
               END-OF-EXECUTION
            END-OF-PROGRAM
            }
Function Karel_Pad.PopUp {
status$="Select something  with arrows from the Menu"
Method Karel_Status,"Refresh"
Method Karel_List,"GetFocus"
}
msg$="Line:{0}, Position:{1}"+tab$+"| Undo: Ctrl+Z | Redo: Ctrl+Y| Replace: Mark Text then press F5| Shift F10 or Right Mouse Click Select from List"
Function Karel_Pad.Inform {
Read New L, P
status$=format$(msg$, L,P)
Method Karel_Status,"Refresh"
Method Karel_Pad,"Show"
}
Function Info$(x) {
select case x
case 7
=" (DEFINE User_Instruction AS  one statement or BEGIN statement(s) END)"
case 8
=" (IF flag THEN  one statement or BEGIN statement(s) END | optional ELSE )"
case 9
=" (After complete IF we can add this ELSE  one statement or BEGIN statement(s) END)"
case 10
=" (ITERATE positiveNumber TIMES  one statement or BEGIN statement(s) END)"
case 11
=" (WHILE flag DO  one statement or BEGIN statement(s) END"
case 1 to 6
=" (command)"
case 13 to 16
=" (last part of command)"
case 18 to 22
=" (built in instruction)"
case 24 to 43
=" (flags used in IF flag THEN and WHILE flag DO)"
case 45
=" from file"
case 46
= " to file"
case 47
=" (Save before erase the program)"
case 48 to 49
=" (Using a Karel World)"
case else
=" not defined yet"
end select
}
Function Karel_List.dblClick {
Read New Who
local w$=list$(Who)
if left$(w$,1)<>tab$ then exit
if sellength>0 then
Insert$=mid$(w$,2)
else
Seltext$=mid$(w$,2)
Method Karel_pad, "RemoveUndo", mid$(w$,2)
sellength=0
SelStart=SelStart+len(mid$(w$,2))
end if
Method Karel_Pad,"GetFocus"
}
Function Karel_List.Scroll {
Read New Who
local w$=list$(Who-1)
if left$(w$,1)<>tab$ then exit
status$=mid$(w$,2)+info$(Who-1)
Method Karel_Status,"Refresh"
}
Function Karel_List.Click {
call local Karel_List.Scroll()
}
Function Karel_List.Color {
Read New &rgb
rgb=#0000FF
}
Function Karel_List.Sep {
Read New &D
if exist(Blocked, List$(d)) then d=-1
}
Function IDE_Karel.Resize {
Layer IDE_Karel { cls Color(255, 160, 0) , 0}
With IDE_Karel, "Width" as NP.Width, "Height" as NP.Height, "TitleHeight" as tHeight
tHeight1=theight*1
local nw=max.data(NP.Width/3, 2000), pw=max.data(NP.Width-nw, 2000)
If NP.height>tHeight1+theight Then {
Method Karel_Pad,"move", twipsX*3, tHeight1, pw-twipsX*6, NP.Height-tHeight1-tHeight
Method Karel_List,"move", twipsX*3+pw, tHeight1, nw-twipsX*6, NP.Height-tHeight1-tHeight
Method Karel_Status, "move",twipsX*3,NP.Height-tHeight1+twipsY*3,Np.Width-twipsX*6, tHeight1-twipsY*6
With Karel_Pad, "NoWrap" as NoWrap
Method Karel_list, "Refresh"
Method Karel_Pad,"Resize"
}
}
Call Local IDE_Karel.Resize()
status$="Welcome"
after 300 {
// SO A LOCKED CAN'T GET FOCUS
With Karel_Status, "Locked", true
}
Method IDE_Karel,"Show", 1
Declare Karel_Pad Nothing
Declare IDE_Karel Nothing





Τετάρτη 29 Δεκεμβρίου 2021

Revision 51, Version 10

This revision fix the problems found in revision 49. In revision 49 the execution of subroutines change to be more fast, but I forgot to fix the GOTO statement inside subs. So revision 51 execute code like revision 48, but faster.

This is the last revision with out a compiling stage. This interpreter execute code as is, using hash tables for searching identifiers. So each time interpreter find a literal number, convert it to binary format.  The compiling stage has to make this conversions once. All control structures can be hard coded in the final "code" before execution. My goal is to give at least 100 time faster execution, from original interpreter. The "old" interpreter can be exist with the new one, when the code is one line. So a Eval() instruction can be used without compiling the string inside Eval().

 



Τρίτη 28 Δεκεμβρίου 2021

Νέα για τον νέο διερμηνευτή της Μ2000 (ετοιμάζεται)

 Το έπιασα ζεστά το θέμα και μέχρι στιγμής έχω φτιάξει το σύστημα που σαρώνει τον κώδικα και βγάζει σε ένα αντικείμενο CodeBlock το κώδικα όπως φαίνεται παρακάτω. Θέλει δουλειά για να υποστηρίζω όλη τη πολύπλοκη δομή της Μ2000. Ο διαχωρισμός σε αναγνωριστικά, τιμές αριθμητικές, τιμές αλφαριθμητικές και σύμβολα είναι σημαντικός. Επίσης γράφονται και τα απαιτούμενα άλματα, καθώς και που κλείνει μια παρένθεση και που κλείνει μια αγκύλη. 

Module Alfa {
X=10
alfa:
if X>1 Then
x--
if x>2 and x<5 then print x
goto alfa
end if
print "ok"
}
Call Alfa


 0 1 171 Identifier: MODULE id (0)

 1 8 161 Identifier: ALFA id (1)

 2 14 140 START BRACKET (end bracket: 41)

 3 13 0

 4 17 161 Identifier: X id (2)

 5 18 14 = symbol id (2)

 6 19 100 10 Double numliteral (0)

 7 20 0

 8 24 20 Label alfa

 9 28 0

 10 32 171 Identifier: IF id (3)

 11 35 161 Identifier: X id (2)

 12 36 14 > symbol id (7)

 13 37 100 1 Double numliteral (2)

 14 39 10 Identifier: THEN skip to 36 id (4)

 15 42 0

 16 47 161 Identifier: X id (2)

 17 48 18 -- symbol id (124)

 18 49 0

 19 54 171 Identifier: IF id (3)

 20 57 161 Identifier: X id (2)

 21 58 14 > symbol id (7)

 22 59 100 2 Double numliteral (3)

 23 61 171 Identifier: AND id (5)

 24 65 161 Identifier: X id (2)

 25 66 14 < symbol id (4)

 26 67 100 5 Double numliteral (4)

 27 69 10 Identifier: THEN skip to 31 id (4)

 28 74 171 Identifier: PRINT id (6)

 29 80 161 Identifier: X id (2)

 30 80 0

 31 85 111 Identifier: GOTO id (7)

 32 90 161 Identifier: alfa id (8)

 33 93 0

 34 97 171 Identifier: END id (9)

 35 101 171 Identifier: IF id (3)

 36 102 0

 37 106 171 Identifier: PRINT id (6)

 38 112 15 "ok" strliteral (5)

 39 115 0

 40 118 141 END BRACKET (start  bracket: 3)

 41 118 0

 42 121 171 Identifier: CALL id (10)

 43 126 161 Identifier: ALFA id (1)

 44 129 0

Number Literals: 4

String Literals: 1

Identifiers: 11

Labels : 1



Η πρώτη στήλη είναι  η θέση στο πίνακα του κώδικα, ξεκινάει από το 0. Εδώ δεν έχουμε δένδρο τύπου AST, αλλά πίνακα με συνολικά 10 Bytes ανά θέση. Το δεύτερο νούμερο είναι η θέση στο κείμενο του προγράμματος. Το χρειαζόμαστε και για μηνύματα λάθους, και για να δείχνουμε το κώδικα όταν τρέχει σε φάση ελέγχου. Το τρίτο νούμερο είναι ο τύπος της εγγραφής-εντολής. Το τέταρτο νούμερο είναι είναι ένα βοgθητικό που αν χρειάζεται μας δείχνει το άλμα που μπορούμε να κάνουμε. Το πέμπτο νούμερο (ή τελευταίο τέταρτο σε κάποιες γραμμές, επειδή το προηγούμενο δεν χρησιμοποιείται) δείχνει την "μνήμη" που καταγράφεται, ή ένα όνομα (αναγνωριστικό) ή ένα αλφαριθμητικό ή ένας αριθμός. Οι αριθμοί σώνονται με το τύπο που δίνουμε.
Στο τέλος έχουμε μια λίστα αριθμών. Εδώ μας λέει ότι ο κώδικας είχε 4 αριθμούς, ένα
  αλφαριθμητικό, 11 αναγνωριστικά (γνωστά/άγνωστα) και μια ετικέτα.

Το κείμενο του κώδικα μπαίνει στην αρχή σε ένα αντικείμενο Document, και αυτό λειτουργεί όπως περίπου και στον χρωματιστή κώδικα στον διορθωτή της Μ2000, με τη διαφορά ότι εδώ δεν μας ενδιαφέρουν τα χρώματα αλλά ο τύπος. Γίνεται πάρα πολύ γρήγορα. Η Μ2000 χρωματίζει χιλιάδες γραμμές κώδικα σε κλάσμα δευτερολέπτου, με συνολικά πάνω από 1000 αναγνωριστικά - ελληνικά και αγγλικά, επειδή χρησιμοποιεί πίνακες κατακερματισμού με O(1) στην αναζήτηση. Εδώ όμως έχουμε και ένα δεύτερο πέρασμα στο χρωματισμένο κώδικα για να γραφτεί ο πίνακας. Για το σκοπό αυτό χρησιμοποιώ μια στοίβα που γράφω ορισμένα στοιχεία που πρέπει να ελέγχω. Έτσι αν στον παραπάνω κώδικα δεν βάλουμε το END IF, θα μείναι στη στοίβα το Then. Δείτε ότι υπάρχει ένα φωλιασμένο IF που έχει την Then αλλά δεν έχει END IF επειδή έχει εντολή μετά το Then. Επειδή δεν κοιτάει μπροστά το σύστημα, ο έλεγχος γίνεται στην αλλαγή γραμμής, και βλέπει τι είχε τελευταίο και τι απόσταση είχε από το τέλος γραμμής (δεν συμπεριλαμβάνονται οι σημειώσεις). Αν δει λοιπόν ότι είχε το THEN τότε θα το αφαιρέσει. Αυτό το κοίταγμα, έγινε και στο πρώτο Then, αλλά εκεί έκανε το σύστημα κάτι ωραίο, το σημάδεψε για να μην το ενοχλήσει ξανά (αφού βρήκε ότι ήταν ακριβώς μια εντολή πίσω από την αλλαγή γραμμής).

Το πρόγραμμα τρέχει στο "παλιό" ή "τωρινό" διερμηνευτή. Ακόμα δεν έχω φτιάξει την εκτέλεση του κώδικα από τον πίνακα. Έχει αρκετή δουλειά για να βρω όλες τις περιπτώσεις, να τεκμηριώσω ότι γράφονται στο πίνακα σωστά και μετά έχω ένα δεύτερο μεγάλο σκέλος, να φτιάξω όλες τις εντολές να δέχονται παραμέτρους από τον κώδικα στο πίνακα και όχι όπως τώρα που εκτελούν τον κώδικα απ' ευθείας από το κείμενο! Για εκπαιδευτικούς σκοπούς αυτή η "παλιά" πρακτική, δεν είναι πρόβλημα. Αλλά αν φτιαχτεί όπως σκέφτηκα, θα κερδίσω σε ταχύτητα, υπολογίζω πως θα κερδίσω σε αναλογία 1/200. Δηλαδή θα χρειάζεται 200 φορές λιγότερο χρόνο εκτέλεσης, από τη σημερινή υλοποίηση!

Αφού τελειώσω με το πίνακα, θα τον μαζέψω, δηλαδή θα αφαιρέσω από την αρχή τις γραμμές με το 0. Τώρα τις έχω για να καταλαβαίνω το κώδικα (διαφορετικά θα ήταν ένα μπέρδεμα...)

Δευτέρα 27 Δεκεμβρίου 2021

Making the Karel Ide (part one)

This is the first part, of my try to make a Karel Ide. This is the least program to display with syntax color, Karel programs. The Karel_Paf is an EditBox, one of M2000 controls for user forms. This control has a special part, for applying syntax color.

The first problem was from the karel commands (or statements) which utilize the hyphen as a character. So the word BEGINNING-OF-PROGRAM has to have one color. The control's state of the art routine, knows that hyphen is a symbol for expressions (in any language, except Karel). There is a property in the control named  WordCharRightButIncluded which we tell the hyphen included in names. Another property called DropSym and this is hyphen again. So in scanning process the hyphen dropped. Without this setting the END in get different color in END-OF-EXECUTION because END exist as command. So we want this: END-OF-EXECUTION full colored multi-word which included in KAREL_COMMAND$, which we insert in ColorCollection1 property.

There is another set of identifiers, the flags, which we have to place after specific statements, or for simplicity after any statement. So if we place one of them "alone" in a line we get different color. For Karel language there is no problem with  new lines because these included in the whitespace set. Also to make a new set with special color we have to use ColorCollection4 property with EXTRAFRONT property. So if a word has one of character in EXTRAFRONT then the system check the ColorCollection4 for a match. We have to provide upper and lower case for first letter, but no for the match (by default upper/lower case aren't significant).

In the image below we see the code in a form using indentation (spaces). Also with F9 we get a message (in the default language, which in my M2000 is Greek), about the number of words. The hyphen for this process is word boundary, so BEGINNING-OF-PROGRAM counts for three words. That is not bad, because anyway we can "measure" a program easy, using F9 to have an idea if something missed. 

For indentation use ctrl+tab and ctrl+shift+tab. You can mark many lines and apply to them the preferred indentation.

For the example the code has no indentation and the known identifiers are in a random case, so a bEGIN exist with another BEGIN.

This is the code as we copy to clipboard through control.  The color change to adapt the standard color scheme. See that FRONT-IS-BLOCKED are red like 4. Because these are "parameters". There is no -4, all numbers are from 1 positive. FRONT-IS-BLOCKED is a flag which reflect the situation in front of Karel Robot, but for the code is a parameter in a While or in an If structure, which have a meaning like to classifying the structure, as a kind of While or kind of If, as an abstraction.

With black color, in this colored scheme, we have instructions, the 5 standard of them (including move, turnleft, turnoff) plus any defined by us. So the color system not know for the 5 standard, so leave them at the same color as the new identifiers. If we place FRONT-IS-BLOCKED as first thing in a row, we get it in black color. The same for TIMES, AS, DO, THEΝ, if we place them as first thing in a row.  This isn't fault for syntax analyzer. This is for the student to place in a form which is good to him/her,. For example the statement Iterate 3 Times turnleft can be split in four lines, but this isn't nice. We can split it in two lines, keeping Iterate 3 Timers in the first line. Also there is another syntactic issue: After times can exist a final statement (black words), or a Begin End block, with any number of statements. So when we use two lines for Iterate 3 Times turnleft, we have to add indentation to turnleft maybe more, because there is no END. See the picture (where the black words here are magenta there).

So this is the first appear of Karel the Language, with a syntax color convention.

BEGINNING-OF-PROGRAM


DEFINE turnright AS
BEGIN
Iterate 4 times turnleft FINDPASS END
DEFINE FINDPASS AS
BEGIN WHILE FRONT-IS-BLOCKED DO turnleft
END
BEGINNING-OF-EXECUTION
IF RIGHT-IS-BLOCKED THEN bEGIN
ITERATE 3 TIMES BEGIN Begin begin turnright TURNLEFT End
begin MOVE end end WHILE FRONT-IS-BLOCKED DO FINDPASS
END END else BEGIN turnoff end
END-OF-EXECUTION


END-OF-PROGRAM


The form has an empty space at the bottom for a Status Line (I am thinking about it, nothing is finally). My intention will be to show in a list box all the identifiers to help the student to pick the right one, as a cheat sheet. Also a control box for compiling and running the program. The run as I think can be done without showing the Karel robot, and terminate with something like "Approved", or "Karel hit the wall in line 12 pos 1, program aborted". Another option will be to show Karel and to control each move, manually or selecting the speed of execution.

Also I have to load a Karel World (the Karel 's playground) as I defined it in a separate application.(this application exist). So in status line (bottom) I have to write the name of the world, and the starting conditions. Also a form which open above as toolbox, giving the Karel World as a drawing, for reference by the student.

Last will be the load/Save program, load Karel World, and a guide about Karel Language.




KAREL_COMMAND$="|BEGINNING-OF-PROGRAM|END-OF-PROGRAM|BEGINNING-OF-EXECUTION|END-OF-EXECUTION|BEGIN|END|DEFINE|IF|ELSE|ITERATE|WHILE|"
FLAGS$="|FRONT-IS-CLEAR|FRONT-IS-BLOCKED|LEFT-IS-CLEAR|LEFT-IS-BLOCKED|RIGHT-IS-CLEAR|RIGHT-IS-BLOCKED|BACK-IS-CLEAR|BACK-IS-BLOCKED|NEXT-TO-A-BEEPER|NOT-NEXT-TO-A-BEEPER|ANY-BEEPERS-IN-BEEPER-BAG|NO-BEEPERS-IN-BEEPER-BAG|FACING-NORTH|NOT-FACING-NORTH|FACING-SOUTH|NOT-FACING-SOUTH|FACING-EAST|NOT-FACING-EAST|FACING-WEST|NOT-FACING-WEST"


Declare IDE_Karel Form
Declare Karel_Pad EditBox Form IDE_Karel
With IDE_Karel,"UseIcon", True, "UseReverse", True, "Quit" as Quit
With IDE_Karel, "Title" as Caption$, "Visible" as Visible, "TitleHeight" as tHeight, "Sizable", True
Method IDE_Karel,"MakeStandardInfo", 1
Method Karel_Pad, "FontAttr", "Verdana", 12, true '  size=12, bold=true
Method Karel_Pad, "SetBarStyle", color(50, 100, 200), 5, 15
With Karel_Pad, "NoWrap", True, "SelLength" as SelLength, "HighlightParagraph", True
With Karel_Pad, "Text" as Pad.Text$, "NoColor", False, "ShowAlways", True, "SpaceIndent", 3
With Karel_Pad, "ColorCollection1", KAREL_COMMAND$
With Karel_Pad, "ColorCollection3","|TIMES|AS|DO|THEN|"
With Karel_Pad, "ColorCollection4", FLAGS$
With Karel_Pad, "WordCharRightButIncluded", "-", "StartSymbols", "123456789"
With Karel_Pad, "WordCharLeft"," ", "WordCharRight", " ", "DropSym", "-", "EXTRAFRONT","FLRBNAEflrbnae"
Method  Karel_Pad, "UserColorSet",0,,,0,-color(5), -#005588
With Karel_Pad, "ColorSet", -1
Pad.Text$={BEGINNING-OF-PROGRAM


DEFINE turnright AS
BEGIN
Iterate 4 times turnleft  FINDPASS END
DEFINE FINDPASS AS
BEGIN WHILE FRONT-IS-BLOCKED DO  turnleft
END
BEGINNING-OF-EXECUTION
IF RIGHT-IS-BLOCKED THEN bEGIN
ITERATE 3 TIMES BEGIN Begin begin turnright TURNLEFT End
begin MOVE end end WHILE FRONT-IS-BLOCKED DO FINDPASS
END END else BEGIN turnoff end
END-OF-EXECUTION


END-OF-PROGRAM


}
Function IDE_Karel.Resize {
      Layer IDE_Karel { Cls Color(255, 160, 0) ,0}
      With IDE_Karel, "Width" as NP.Width, "Height" as NP.Height, "TitleHeight" as tHeight
      tHeight1=theight*1
      If NP.height>tHeight1+theight Then {
            Method Karel_Pad,"move", twipsX*3, tHeight1, NP.Width-twipsX*6, NP.Height-tHeight1-tHeight
            With Karel_Pad, "NoWrap" as NoWrap
            Method Karel_Pad,"Resize"
      }
}
Call Local IDE_Karel.Resize()
Method IDE_Karel,"Show", 1
Declare Karel_Pad Nothing
Declare IDE_Karel Nothing


Σάββατο 18 Δεκεμβρίου 2021

Κάρελ το Ρομπότ (Δημιουργός Κόσμου - Μέρος Δεύτερο)

 Ολοκληρώθηκε το πρόγραμμα που φτιάχνει Κόσμους για το Κάρελ το Ρομπότ.


Πρόλογος

Στο πρόγραμμα αυτό εφαρμόζεται προγραμματισμός με γεγονότα, με ορισμό "ελαφριών" γεγονότων σε αντικείμενα που δημιουργούμε. Λέγονται "ελαφριά" στη Μ2000 για να ξεχωρίζουν από τα αντικείμενα τύπου Γεγονός. Τα αντικείμενα τύπου Γεγονός που δεν έχουμε σε αυτό το πρόγραμμα είναι λίστες συναρτήσεων με κοινή υπογραφή (με καθορισμένο αριθμό ορισμάτων και βασικό τύπο ορισμάτων - αριθμητικό ή αλφαριθμητικό, πίνακας ή απλή μεταβλητή, πέρασμα με ή χωρίς αναφορά). Τα αντικείμενα αυτά έχουν μια Κάλεσε Γεγονός που με μια κλήση καλούνται όλες οι συναρτήσεις (κάποιες από αυτές μπορεί να είναι αναφορές σε συναρτήσεις αντικειμένων, οπότε η κλήση οδηγείται σε αντικείμενα, κάποιες άλλες μπορεί να είναι "ψεύτικες" συναρτήσεις σε τμήματα όπου η κλήση είναι σαν κλήση γεγονότος ή "call back"). Τα ελαφριά γεγονότα έχουν τη διαφορά ότι δεν έχουν λίστα συναρτήσεων και ο διερμηνευτής κρατάει το που ορίσαμε το αντικείμενο ΜεΓεγονότα (είναι μια λέξη που βάζουμε κατά τον ορισμό τους), και όταν καλούμε ένα γεγονός, δεν εξετάζει καν πόσα ορίσματα και τι θα στείλουμε, απλά κοιτάει αν υπάρχει συνάρτηση εξυπηρέτησης εκεί που υποτίθεται θα υπάρχει, και αν όχι τότε απλά πετάει τα ορίσματα και συνεχίζει το πρόγραμμα, ενώ αν ναι τότε καλεί τη συνάρτηση εξυπηρέτησης σαν να ήταν κώδικας του τμήματος που βρίσκεται η συνάρτηση, δηλαδή με θέαση ίδια με αυτή του τμήματος (εκτός από τις στατικές ρουτίνες του τμήματος που δεν τις βλέπουν αυτές οι συναρτήσεις). Αυτές είναι οι κλήσεις τύπου "call back". Μια call back κλήση είναι η κλήση που γίνεται από αυτό που καλούμε προς το μέρος μας, πριν τερματίσει αυτό που καλούμε. Δεν θα είχε νόημα αυτή η κλήση αν η call back συνάρτηση ή ότι άλλο δεν είχε πρόσβαση σε μεταβλητές (δηλαδή να αλλάξει κατάσταση) στο χώρο που ξεκίνησε η αρχική κλήση. Η ΓΛΩΣΣΑ της ΑΕΠΠ δςεν θα μπορούσε να κάνει κάτι τέτοιο, γιατί δεν έχει τρόπο να έχει συναρτήσεις με θέαση στο περιβάλλον έξω από τη συνάρτηση. Σε αντίθεση οι C++ και C έχουν αυτή την δυνατότητα σε επίπεδο αρχείου, δηλαδή αν ένα πρόγραμμα είναι γραμμένο σε δυο ή περισσότερα αρχεία, ανά αρχείο υπάρχουν μεταβλητές θεατές στο αρχείο αυτό, και σε όλες τις συναρτήσεις σε αυτό. Στη Μ2000 στις πρώτες εκδόσεις μόνο γενικές μπορούσαν να θεαθούν μέσα σε συναρτήσεις και τμήματα. Αυτό συνεχίζει και τώρα αλλά έχουν μπει και άλλες μορφές κλήσεων που μεταθέτουν με τη κλήση και το όνομα χώρου για να υπάρχει η κοινή θέαση μεταβλητών. Χωρίς αυτό δεν θα δούλευαν και οι φόρμες χρήστη, αφού τα γεγονότα χρειάζονται εξυπηρέτηση από συναρτήσεις που πρέπει να έχουν επαφή με το περιβάλλον, εδώ ενός τμήματος, εκεί όπου θα "αλλάζουν" κατάσταση σε αυτό (αυτή είναι η έννοια εξυπηρέτησης γεγονότος, να επιφέρει αλλαγές στο έξω περιβάλλον, από το "γεγονός" που δημιουργείται στο αντικείμενο. Μπορούμε να έχουμε πέρασμα με αναφορά ώστε να έχουμε και ανάδραση, όπως για παράδειγμα το κλείσιμο μιας φόρμας χρήστη μπορεί να ακυρωθεί αν θέλουμε, οπότε το αντίστοιχο γεγονός δίνει "ανάδραση" κατάλληλη μέσω μεταβλητής που πέρασε με αναφορά.


Φτιάχνω το Κόσμο του Κάρελ του Ρομπότ

Το πρόγραμμα υπάρχει στο Git  και το αντιγράφετε ως έχει (ως gsb αρχείο, με Win Dir$ στη κονσόλα της Μ2000 ανοίγει ο Explorer στο φάκελο του χρήστη της Μ2000), ή κόβεται το κώδικα από το τμήμα Α και το βάζεται σε ένα δικό σας τμήμα στον διορθωτή της Μ2000 και το εκτελείτε άμεσα! 

Περιλαμβάνει και τον κόσμο που φαίνεται στην εικόνα. Ο κόσμος αυτός είναι στην αρχή του κώδικα, ως json αρχείο. Η εφαρμογή φτιάχνει json αρχείο και το αποθηκεύει με κατάληξη kword. Έχει ενσωματωθεί και η βοήθεια με υπερκείμενο.

Μετακινούμε το ρομποτ και σχεδιάζουμε το κόσμο, ασχολούμενοι με τα τοιχώματα και τα μπίμπερ. Για τα τοιχώματα, ο Κάρελ τα βάζει κάτω δεξιά (ή νοτιανατολικά). Το τι θα δούμε εξαρτάται από την "συνέχεια". Δηλαδή αν πάμε σε μια κενή φόρμα (επιλέγοντας διαστάσεις καθαρίζει ο κόσμος), θα πρέπει να βάλουμε Εμπόδιο δυο φορές συνεχόμενα για να δούμε γραμμή!

Μπορούμε να φτιάξουμε οποιοδήποτε σχέδιο, αρκεί να μετακινηθούμε αριστερά από εκεί που θέλουμε να αλλάξουμε κάτι. Ο Κάρελ για να φτιάξει τον κόσμο του μετακινείται ως "φάντασμα" δηλαδή διαπερνάει τους τοίχους. Αυτό γίνεται με τέσσερα πλήκτρα με τα λεκτικά Βορράς, Ανατολή, Νότος, Δύση. Αν θέλουμε να δούμε πως θα προχωρήσει με τις δικές του μοναδικές εντολές Εμπρός και Αριστερά υπάρχουν κουμπιά κάτω από το σχέδιο.

Το κουμπί έλεγχος κάνει παιχνίδι τον Κάρελ, αν και δεν "τερματίζει" αν δεν το τερματίσουμε με Esc ή διπλό πάτημα στο Έλεγχος.

Το επόμενο βήμα είναι να φτιαχτεί το πρόγραμμα που θα φορτώνει το κόσμο που θέλουμε (την άσκηση) και θα εκτελεί το πρόγραμμα του ασκούμενου, στη γλώσσα Κάρελ. Στο git έχω ανεβάσει τον AST-Διερμηνευτή, και πάνω σε αυτό θα βασιστώ για το τελικό πρόγραμμα.

Για να σώσουμε ένα κόσμο ανοίγει η φόρμα διαλόγου, για το όνομα αρχείου. Εδώ η Μ2000 έχει μια παραξενιά που μάλλον θα την βγάλω σε επόμενη έκδοση*. Δεν επιτρέπει να γράψουμε νέο όνομα αν ο φάκελος είναι read only δηλαδή μόνο για ανάγνωση. Χρειάστηκε στο φάκελο του χρήστη να πάω ένα επίπεδο παραπάνω, να μπω με cmd.exe (με δικαιώματα administrator) για να δώσω το attrib -r m2000 και να κάνω τον φάκελο για ανάγνωση και για εγγραφή. Εν τω μεταξύ για να σώσουμε πρόγραμμα ή για να γράψουμε αρχεία δεν είχε ζήτημα η Μ2000. Μόνο στο να "ανοίξει" ο διάλογος το πεδίο που δέχεται νέο όνομα, στη περίπτωση του read only φακέλου! Ο οποίος φάκελος δεν έγινε από μένα read only, και αν δεν το έβρισκα από το "σφάλμα" (που βασικά δεν είναι σφάλμα) του διαλόγου δεν θα το είχα καταλάβει! Το έψαξα λίγο και μάλλον κάτι έκανε ο Defenter των Windows, και για προστασία δήθεν "κλείδωσε" τους φακέλους. Στη πράξη το read only δεν κλειδώνει τίποτα στα Windows (έχει άλλες κλειδαριές...που δεν φαίνονται). Είναι απλός ένα "σινιάλο".  Οι περισσότεροι προγραμματιστές κάνουν κάτι απλό, δοκιμάζουν να δουν αν μπορούν να γράψουν ένα μικροσοπικό αρχείο! Αν γράφεται τότε ο φάκελος δεν είναι πραγματικά read-only.

*Ανέβηκε η αναθεώρηση 48 που αφαιρεί τον έλεγχο folder read only στη φόρμα διαλόγου που ανοίγει με την εντολή: Αποθήκευση.ως

Λίγα λόγια για το πρόγραμμα

https://github.com/M2000Interpreter/KarelTheRobot/blob/main/KarelTheRobot.gsb

Το πρόγραμμα είναι ένα αρχείο  gsb με ένα τμήμα το Α. Μέσα σε αυτό έχουμε 1500+ γραμμές. Αυτές αν και φαίνονται πολλές, δεν είναι! 

Το πρώτο μέρος έχει τη δημιουργία της Λιστ1$ μιας μεταβλητής αλφαριθμητικής στην οποία εκχωρούμε ένα json αντικείμενο που το έχουμε ως αλφαριθμητική τιμή σταθερή. Δηλαδή το αντικείμενο είναι στη λεγόμενη serialized μορφή.

Από τη γραμμή 44 φτιάχνουμε τη διεπαφή, δηλαδή στήνουμε τη φόρμα μας. Εδώ δεν χρησιμοποιούμε φόρμα χρήστη αλλά αλλάζουμε την κονσόλα. Δηλαδή όλο το πρόγραμμα θα τρέχει μόνο στη κονσόλα. Στην 51 γραμμή το Συγγραφή ! 3 κάνει το tab να πιάνει τρεις χαρακτήρες πλάτος. Αυτό με βόλευε κατά την δημιουργία του προγράμματος.

Στη 53 ξεκινάει η δημιουργία του συστήματος βοήθειας με υπερκείμενο. Υπάρχει τρόπος δηλαδή να ετοιμάσουμε κεφάλαια και αυτά να καλούν το ένα το άλλο με υπερσυνδέσμους σε τετράγωνες αγκύλες [ ] . Δείτε ότι ένας υπερσύνδεσμος αντί να ανοίξει νέο κεφάλαιο ανοίγει μια σελίδα στον τρέχοντα φυλλομετρητή ιστοσελίδων πχ στο Firefox ή στο Chrome.

Στην 114 γραμμή η Διαμέσου ΦόρτωσεΣυνάρτηση καλεί μια ρουτίνα απλή που γυρνάει με την εντολή Επιστροφή. Αυτές οι ρουτίνες δεν έχουν τοπικές, δηλαδή δεν ανοίγουν πρόσκαιρο χώρο για ορισμούς όπως κάνει μια ρουτίνα με όνομα με παρενθέσεις. Έτσι ότι φτιάχνει μένει. Εκεί λοιπόν έχω βάλει ορισμούς και άλλα σχετικά για το πρόγραμμα. Στην ουσία μετάθεσα το κομμάτι αυτό προς το τέλος.

Η Διαφυγή Όχι απενεργοποιεί το Esc, για να μην διακόπτεται το πρόγραμμα. Αλλά μπορούμε να το διακόψουμε με το CTRL+C. Θέλουμε το Esc για άλλες δουλειές!

Στην 144 υπάρχει μια μυστήρια εντολή: Θέση ! η οποία ακολουθεί μια Δρομέας 0,2 στην 143. Με την εντολή Δρομέας μετακινούμε νοητά την θέση που τυπώνουμε χαρακτήρες στη φόρμα μας (η Πλάτος μας δίνει το μέγιστο στη γραμμή χαρακτήρες, η Ύψος μας δίνει τον αριθμό γραμμών). Η Εντολή Θέση είναι η αντίστοιχη για τον δρομέα γραφικών, και κανονικά δέχεται ένα ή δυο αριθμητικά νούμερα. Πχ Θέση 6000, 3000 θα μετακινήσει το δρομέα γραφικών (νοητά, δεν το βλέπουμε) εκεί που η 6000twips οριζόντια συντεταγμένη συναντάει την 3000twips κάθετη συντεταγμένη (το 0,0 είναι πάνω αριστερά, και οι κάθετες συντεταγμένες είναι θετικές προς τα κάτω). Με τη Θέση ! μεταφέρουμε τη θέση του δρομέα χαρακτήρων (πάνω αριστερή γωνία θέσης χαρακτήρα) ως θέση δρομέα γραφικών. Ομοίως η Δρομέας ! κάνει το ανάποδο αλλά εκεί βρίσκει τη θέση χαρακτήρα και τη γραμμή που περιέχει την θέση του δρομέα γραφικών (έτσι μια Δρομέας ! και μετά Θέση ! ενδέχεται να αλλάξει τον δρομέα γραφικών). Αυτά τα κάνουμε για να πάρουμε πραγματικά νούμερα, επειδή κάναμε επιλογές για την εμφάνιση της κονσόλας:

Εντολή Παράθυρο 12, 0 στην 118 γραμμή λέει να μπουν 12pt χαρακτήρες στη τρέχουσα γραμματοσειρά, και να καλυφθεί όλη η οθόνη του μόνιτορ 0 (αν το 0 το κάνουμε 1 και υπάρχει δεύτερο μόνιτορ θα μεταφερθεί εκεί η κονσόλα). Παρακάτω όμως η εντολή Φόρμα με δυο νούμερα ζητάει στο χώρο της κοσνόλας να έχουμε ένα πλάτος χαρακτήρων με έναν αριθμό γραμμών (ύψος σε χαρακτήρες). Αυτό αλλάζει το μέγεθος της γραμματοσειράς και ενδεχομένως το διάκενο μεταξύ των γραμμών (το αυξάνει αν χρειάζεται για να καλύψουν οι γραμμές το χώρο της κονσόλας). Έτσι το που θα είναι η τρίτη γραμμή δεν το ξέρουμε πριν εκτελεστεί το πρόγραμμα! Να γιατί μας ενδιαφέρει να στοιχίσουμε οτιδήποτε στη φόρμα μας βάσει ενός προσχεδιασμένου πλάτους και ύψους φόρμας. Βέβαια αυτό το πρόγραμμα έχει γραφτεί έτσι ώστε αν θέλουμε να αυξήσουμε ή να μειώσουμε το πλάτος σε χαρακτήρες (ενώ κρατάμε σταθερό το ύψος). Η αλλαγή θα επιφέρει αλλαγή στο διάστιχο και στο μέγεθος χαρακτήρων με συνέπεια να έχουμε μικρότερα γράμματα στα πλήκτρα (στόχοι στη κονσόλα της Μ2000 κατά την ορολογία της Μ2000).

Οι στόχοι στη Μ2000 είναι πλαίσια που έχουν ιδιότητες και μπορούν με μια εντολή να αλλάξουμε κάποιες από αυτές και να δούμε τις αλλαγές στην οθόνη. Δεν είναι πραγματικά αντικείμενα όπως τα στοιχεία ελέγχου των φορμών χρήστη. Για να πάρουμε "ανάδραση" δηλαδή ότι επιλέξαμε κάποιο στόχο πρέπει να χρησιμοποιηθεί η Σάρωσε. Αυτή η εντολή παίρνει ένα αριθμό σε χιλιοστά του δευτερολέπτου (παίρνει και δεκαδικά) και στο χρόνο αυτό ή γυρνάει άπραγη ή έχει βρει σε ποιον στόχο κάναμε αριστερό κλικ και έχει κάνει ένα από τα δυο πράγματα ανάλογα με το τι ζητάμε από έναν στόχο: Ή να εκτελέσει κώδικα σαν να εκτελέστηκε στην κοσνόλα (δηλαδή με θέαση μόνο σε ότι είναι γενικό) ή να στείλει χαρακτήρες στο πληκτρολόγιο, και ειδικότερα στην μνήμη εντός της Μ2000 (όχι απευθείας στο πληκτρολόγιο, αυτό το τελευταίο το κάνουμε για το Esc πιο μετά για άλλο λόγο). Σημασία έχει ότι εδώ δεν χρησιμοποιούμε τους στόχους για να στείλουμε κάτι στο πληκτρολόγιο αλλά για να αλλάξουμε μια τιμή στην γενική αριθμητική μεταβλητή Κωδ. Αυτή η μεταβλητή ορίζεται στο τμήμα Α, θα σκιάσει τυχόν γενικές Κωδ που ήδη υπάρχουν και θα διαγραφεί στο πέρας εκτέλεσης της Α. Αν κάνουμε λάθος και γράψουμε μέσα στο τμήμα μια εντολή εκχώρησης Κώδ=0 τότε θα φτιαχτεί τοπική και ο διερμηνευτής θα δώσει προτεραιότητα σε αυτήν με συνέπεια οι αλλαγές της γενικής Κωδ στους στόχους να μην φαίνεται στο πρόγραμμα (εμείς θα διαβάζουμε τη τοπική). Για το λόγο αυτό όταν κάνουμε εκχώρηση σε γενική χρησιμοποιούμε ή το <= πχ Κωδ<=0 ή τη Στη, πχ Στη Κωδ=0 η οποία δεν δημιουργεί νέες μεταβλητές, αν βρει ότι υπάρχουν (η Στη είναι η Let στο αγγλικό λεξιλόγιο, η απλή = κοιτάει αν υπάρχει τοπική και αν όχι την φτιάχνει, δεν κοιτάει καθόλου αν υπάρχουν γενικές, όπως η Στη).


Οι στόχοι δεν μας δίνουν τα στοιχεία τους, μόνο αλλαγές μπορούμε να κάνουμε δίνοντας νέες τιμές. Επίσης μπορούμε να ενεργοποιήσουμε ή όχι κάποιον στόχο δίνοντας το νούμερό του και τιμή 1 ή 0.

Για να απλοποιήσω το πρόγραμμα έφτιαξα την Μνήμη ως μια Λίστα με κλειδιά/τιμές, όπου κλειδί είναι ένα όνομα πχ το ΕΜΠ είναι για το πλήκτρο Εμπόδιο, και τιμή ένας αυτόματος πίνακας (touple) με έξι στοιχεία (παίρνει και αριθμούς και αλφαριθμητικά, στον ίδιο πίνακα). Ο πίνακας καταχωρείται με δείκτη. Έτσι όταν αργότερα ζητήσουμε την τιμή στο κλειδί "ΕΜΠ" θα πάρουμε το δείκτη, σαν δεύτερο δείκτη και όχι αντιγραφή στοιχείων σε νέο πίνακα.

Στη γραμμή 201 είναι η εντολή Στόχος(2, !Μνήμη("ΕΜΠ")) η οποία είναι κλήση της ρουτίνας Στόχος(). Αυτή η ρουτίνα δέχεται αρκετές παραμέτρους, με πρώτη τον αριθμό που θα δίνει στην μεταβλητή Κωδ όταν ο στόχος ενεργοποιηθεί!. Αντί να δώσουμε έναν έναν τις επόμενες παραμέτρους λέμε στο διερμηνευτή ότι ακολουθεί πίνακας και να τις πάρει από εκεί και να τις βάλει στο σωρό τιμών για να διαβαστούν από την ρουτίνα. Έτσι τώρα αυτό που δίνουμε το έχουμε κρατημένο και στη Μνήμη. Η μεταβλητή Μνήμη έχει το δείκτη στη λίστα, αλλά τα Μνήμη() και Μνήμη$() διαβάζουν αριθμητικές (ή αντικείμενα) τιμές και αλφαριθμητές αντίστοιχα βάσει κλειδιού, αλφαριθμητικό ή αριθμός, ή αν έχουμε το ! στο τέλος ενός αριθμού βάσει τρέχουσας σειράς. Οι λίστες ενδέχεται να χάσουν την σειρά των κλειδιών αν διαγράψουμε κλειδί, γιατί η διαγραφή λειτουργεί ως εξής: Αναζήτηση σε πίνακα κατακερματισμού και αφαίρεση του κλειδιού, με αντιγραφή του τελευταίου στοιχείου στη θέση εκείνου που διαγράφτηκε. Η αντιγραφή είναι πάντα "σύντομη" γιατί γίνεται με αντιγραφή σταθερού μήκους μνήμης (όσο το μήκος μιας εγγραφής). Τα αλφαριθμητικά και τα αντικείμενα που "απασχολούν" μεγάλη μνήμη συνήθως, στην εγγραφή απασχολούν μόνο το μήκος ενός δείκτη. Αυτού του είδους λίστα χρησιμοποιεί ο διερμηνευτής για όλα τα Αναγνωριστικά, και τα βρίσκει σε O(1) σε σταθερό χρόνο, ανεξάρτητα από τον αριθμό Αναγνωριστικών!

Κάποιοι στόχοι στο πρόγραμμα έχουν φτιαχτεί μόνο για να εμφανίζουν ταμπέλες! Δηλαδή δεν είναι ενεργοί στόχοι. Πχ στη 294 διαβάζουμε:Κεφαλίδα(Μεγάλο, αρπ, ΓΡΑΜΜΗ+3, "Σκοπός του Κάρελ") όπου τα ορίσματα που δίνουμε λένε πόσο πλάτος θα έχει ο στόχος, που θα γραφτεί (το αρπ είναι η μεταβλητή αριστερό περιθώριο που υπολογίσαμε στην αρχή), και θα γραφτεί στη τρίτη γραμμή μετά την τρέχουσα που μας δίνει η μεταβλητή μόνο για ανάγνωση Γραμμή. Στο τέλος δίνουμε και ένα αλφαριθμητικό για το τι θέλουμε να γράφει ο στόχος!

Στην 327 γραμμή υπάρχει μια λάμδα συνάρτηση (θα μπορούσε να ήταν και κανονική) . Στην 358 γραμμή έχουμε αυτό Οθόνη : Αναφορά Μνήμη(ΘέσηΣτηΛίστα(Ποιός, Κωδ)!)#Τιμή$(5) δηλαδή δυο εντολές, η Οθόνη σβήνει το κάτω μέρος της φόρμας (το μέρος που μπορεί να ολισθαίνει) και το είχαμε ορίσει σε τρεις γραμμες ύψος, και η Αναφορά δίνει το πέμπτο στοιχείο, αλφαριθμητικό, από τον πίνακα  στην Μνήμη, αλλά δεν δίνουμε κλειδί αλλά θέση, τη θέση που μας επιστρέφει η ΘέσηΣτηΛίστα. Αυτή παίρνει την λίστα Ποιος και τον αριθμό Κωδ και δίνει τον αριθμό θέσης. Αυτό έγινε γιατί η Ποιος είναι μια Λίστα που δημιουργήσαμε στη γραμμή 188 με την Ποιός=Λίστα, και στη ρουτίνα Στόχος() (οι ρουτίνες βλέπουν τις μεταβλητές του τμήματος που εκτελούνται), υπάρχει η εντολή Προσθήκη Ποιός, οδηγ:=Μ όπου το οδηγ είναι ο αριθμός κωδ, και το Μ ο αριθμός στόχου. Έτσι το Ποιός(6) θα μας δώσει τον πραγματικό αριθμό στόχου σε εκείνον το στόχο που γυρνάει το 6 στην κωδ όταν ενεργοποιηθεί. Δείτε όμως ότι η λίστα Ποιος και η λίστα Μνήμη σε οποιαδήποτε Χ θέση έχουν στοιχεία για τον ίδιο στόχο, έτσι με την ΘέσηΣτηΛίστα() πάμε μέσω κωδ να βρούμε ποιος είναι ο στόχος ώστε από την Μνήμη() να δώσουμε τη θέση (να γιατί είναι το ! στο τέλος)  και να πάρουμε το πίνακα από όπου θα διαβάσουμε την 6η στην ουσία τιμή γιατί οι αυτόματοι πίνακες (touple) ξεκινούν από 0.

Κατά τη συγγραφή του προγράμματος η χρήση της αναζήτησης είναι πολύ χρήσιμη. Πχ. αν επιλέξουμε μια λέξη, με τα F2 πάμε στην ίδια παραπάνω και με το F3 πάμε στην ίδια παρακάτω. Με συνδυασμό Ctrl ή Shift, ανοίγει φόρμα και ζητάει όνομα, είτε για να το βρει μέσα σε λέξη είτε για να το βρει ως ολόκληρη λέξη. Επίσης το F5 κάνει αλλαγές στις λέξεις άμεσα (με undo Ctrl+Z και redo Ctrl+Y, δείτε και το σχετικό μενού με δεξί κλικ στο ποντίκι πάνω στον δρομέα, η Ctrl+F10 ή αριστερό κλικ στην επικεφαλίδα του διορθωτή). Αν θέλουμε αλλαγή μόνο σε πεζά κεφαλαία τότε αυτό το κάνει η F4, αλλά εδώ δεν υπάρχει undo. Το F4 κάνει επιλογή λέξης αν δεν έχει γίνει και αν έχει γίνει κάνει ομοιόμορφες όλες τις λέξεις. Αν θέλουμε κάποια λέξη να έχει τόνο τότε αλλαγές θα κάνουμε με το F5. To F5 έχει και αυτό παραλλαγή με το Shift, όπου οι αλλαγές γίνονται και σε μέρος λέξης. Το undo γίνεται όσο είμαστε μέσα στον διορθωτή. Αν θέλουμε να έχουμε πάντα το πρόγραμμα μέσα στον διορθωτή τότε ανοίγουμε τον meditor από το info αρχείο, που είναι ο παραθυρικός διορθωτής της Μ2000, και οι εκτελέσεις γίνονται σε νέο περιβάλλον (μέσα από το μενού επιλογών του παραθυρικού διορθωτή επιλέγουμε τι θα εκτελεστεί από το πρόγραμμα και πώς).

Στην γραμμή 352 ξεκινάει ένα μπλοκ Κάθε 1000/30  { } όπου το δευτερόλεπτο (1000ms) το χωρίζουμε σε 30 μέρη, άρα 30 φορές το δευτερόλεπτο ή 30Hz συχνότητα, εκτελείται η επανάληψη του προγράμματος για να διαβάζει τους στόχους και να εκτελεί ανάλογα.

Τα 30 μέρη μπορούν να λιγοστέψουν αν κάτι απαιτεί μεγαλύτερο χρόνο, και πράγματι σε μια επιλογή με την Έλεγχος μπαίνουμε  στο παιχνίδι() όπου εκεί υπάρχει άλλη Κάθε...και η προηγούμενη είναι σε αναμονή!

Αυτό είναι το πρόγραμμα ως σκελετός. Από και κει και πέρα φτιάχνουμε το τι θα κάνει ο κάθε στόχος.

Το  πρόγραμμα έχει δυο αντικείμενα το Κόσμος και το Ρομπότ. Και τα δύο τα έχουμε φτιάξει με γεγονότα. Ο λόγος που έγινε αυτό είναι για να βγάλουμε εκτός πράγματα που δεν ενδιαφέρουν τα αντικείμενα αυτά, όπως το πώς σχεδιάζεται ο Κόσμος και το πώς σχεδιάζεται το Ρομπότ. Αυτά που ενδιαφέρουν είναι τα στοιχεία πχ το Εμπόδιο είναι ένα καθορισμένο στοιχείο και παίζει ένα καθορισμένο ρόλο, ανεξάρτητα το πώς θα φτιαχτεί στην οθόνη. Θα μπορούσαμε να είχαμε μια 3D απεικόνιση χωρίς να αλλάξουμε τα αντιοκείμενα. Η μέθοδος εμφάνισε του αντκειμένου κόσμος στέλνει αιτήματα με την Κάλεσε Γεγονός. Αν δεν έχουμε συνδέσει συναρτήσεις εξυπηρέτησης τότε δεν θα έχουμε λάθος στον κώδικα της μεθόδου. Η μέθοδος δεν ενδιαφέρεται αν υπάρχει ή όχι εξυπηρέτηση! Αυτή είναι η λογική με το προγραμματισμό με γεγονότα, που αυτό το πρόγραμμα εφαρμόζει!

Στις συναρτήσεις εξυπηρέτησης γεγονότων, η θέαση είναι σχεδόν ίδια με αυτή των ρουτινών, αλλά δεν έχουμε κλήση σε ρουτίνες έξω από τη συνάρτηση. Όλες οι μεταβλητές του τμήματος είναι θεατές. Ο σωρός τιμών είναι ξεχωριστός από αυτό του τμήματος (ενώ οι ρουτίνες βλέπουν τον ίδιο). Αν έχουμε ονόματα που ήδη υπάρχουν στο τμήμα θα πρέπει είτε να δημιουργήσουμε με Διάβασε Νέο ή με Τοπική ή Τοπικές αν δίνουμε νέες τιμές απευθείας. Πχ στη 1080 η Συνάρτηση Ρομπότ_σχεδίασεΦόντο (Νέο Χ, Υ)  έχει το Νέο για να φτιαχτούν νέες Χ και Υ, αν βγάλετε το Νέο θα δείτε πρόβλημα στο πρόγραμμα! Αυτό που είναι στην παρένθεση θα πάει σε μια Διάβασε Νέο Χ, Υ.

Δείτε ότι οι συναρτήσεις εξυπηρέτησης για Ομάδες (τα αντικείμενα χρήστη που φτιάχνουμε στη Μ2000) έχουν τη κάτω παύλα (underscore) μεταξύ ονόματος αντικειμένου και ονόματος γεγονότος. 

Εκτός από τα αντικείμενα χρήστη (ομάδες) όπου ορίζουμε τα πάντα για αυτά, υπάρχουν έτοιμα όπως λίστες, πίνακες, σωρούς τιμών και άλλα τύπου COM όπως το ΛίσταJson (τύπος JsonObject), ΠίνακαςJson (τύπος JsonArray), ΣτοιχείαXML (τύπος XmlMono) και άλλα. Τα τύπου COM είναι για τοπική χρήση, ενώ οι πίνακες, λίστες και σωροί τιμών είναι "καταχωρητές" που καταχωρούνται οπουδήποτε (πχ μια λίστα μπορεί να έχει άλλες λίστες, άλλους πίνακες και άλλους σωρούς τιμών) και να δοθεί ως επιστροφή τιμής.


Δείτε στην 851 το Ιδιότητα ΠακέτοJson$  όπου φτιάχνουμε μια ιδιότητα του αντικειμένου Κόσμος, στην οποία δίνουμε το τι θα επιστρέφει και το τι θα παίρνει ως πρόγραμμα! Η Θέσε λέει τι θα παίρνει και η Αξία λέει τι θα δίνει.  Στη Αξία φτιάχνουμε σε Json μορφή τα στοχεία του αντικειμένου. Στην Θέσε διαβάζουμε αλφαριθμητικό, το μεταφράζουμε (parse) σε Json και από αυτό διαβάζουμε και ρυθμίζουμε το αντικείμενο (φορτώνουμε νέα κατάσταση - State). H ΠακέτοJson$ λειτουργεί σαν μεταβλητή. Τα Θέσε και Αξία είναι τα Setter Getter αντίστοιχα της Java. Υπάρχει μια [ΠακέτοJson]$ ιδιωτική που φτιάχνει ο διερμηνευτής αλλά δεν την χρησιμοποιούμε, αντί αυτού χρησιμοποιούμε το ΑΞΙΑ$ που μας φτιάχνει ο διερμηνευτής όταν καλείται η Θέσε (στην εκχώρηση τιμής) και η Αξία (στην ανάγνωση τιμής). Ο τύπος της ΠακέτοJson$ είναι αντικείμενο ομάδα (Group) και μπορούμε να ορίσουμε ότι θέλουμε αν παρακάτω δώσουμε στον ορισμό Ομάδα ΠακέτοJson { }. Επειδή όλες οι ομάδες που έχουν το όνομα  με $ (επειδή επιστρέφουν τιμή αλφαριθμητικό) έχουν και χωρίς το $ για να καλούμε μέλη. Πχ αν ένα μέλος ήταν το Α$ τότε η ΠακέτοJson.A$ θα δούλευε αλλά η ΠακέτοJson$.A$ δεν θα δούλευε (είναι κάτι άλλο για τον διερμηνευτή, το $. υποδηλώνει ότι υπάρχει δείκτης αλφαριθμητικός, πχ αν το A$="OK"  τότε το Α$.Χ++ θα αυξήσει την τιμή στο ΟΚ.Χ, είναι δηλαδή το ΟΚ.Χ++, είναι ένας τρόπος έμμεσης διευθυνσιοδότησης). Δοκιμάστε αυτό σε μια γραμμή: ΟΚ.Χ=10 : Α$="ΟΚ": Α$.Χ++:Τύπωσε Εκφρ(Α$.Χ) 

Καλά Χριστούγεννα!

ΓΚ











Τρίτη 14 Δεκεμβρίου 2021

Αναθεώρηση 47, Έκδοση 10 - Μεγάλα Αρχεία (>2GB)

Αναβαθμίστηκαν όλες οι εντολές αρχείων, που δουλεύουν με χειριστή αρχείου (file handler). Οι υπόλοιπες επειδή φέρνουν με μια εντολή στη μνήμη δεν μπορούν εκ των πραγμάτων να φορτώσουν πολύ την μνήμη (το μέγιστο είναι κοντά στα 2 GB). Όμως οι εντολές που αναβαθμίστηκαν μπορούν να διαβάζουν τμηματικά ένα αρχείο, μέσω χειριστή αρχείου και με χρήση του δρομέα που τώρα δεν έχει όριο τα 2GB. Πρακτικά δεν έχει όριο, μπορούμε να διαβάσουμε ένα αρχείο όσο μεγάλο και να είναι! Αν και φαινομενικά τα αρχεία που ανοίγουμε για Εισαγωγή, Εξαγωγή και Συμπλήρωση είναι Σειριακά, εντούτοις μπορούμε να αλλάζουμε κατά βούληση την θέση του δρομέα. Επίσης μπορούμε στο ίδιο αρχείο να ανοίξουμε και άλλους δρομείς (με νέους χειριστές αρχείων), ή αν θέλουμε να επιλέξουμε αποκλειστική "απασχόληση" και να μην επιτρέπουμε σε κανέναν να "ασχολείται" με το αρχείο μας.

Παλιότερα υπήρχε μέγιστος αριθμός 512 αρχεία, αλλά τώρα δεν υπάρχει αυτό, και υπάρχει εσωτερικά λίστα που ανακυκλώνει αριθμούς που "έπαιξαν" ή κατ' απαίτηση φτιάχνει νέο νούμερο. Ουσιαστικά σε αυτόν τον χώρο κρύβεται ο πραγματικός χειριστής αρχείου που δίνει το λειτουργικό σύστημα. Εμείς αντί να βλέπουμε τα παράξενα νούμερα του λειτουργικού, ξεκινάμε από το 1. 

Η εντολή Κλείσε (close) χωρίς παραμέτρους κλείνει όλα τα αρχεία άμεσα. Το ίδιο γίνεται και αν κλείσει η εφαρμογή ακόμα και μη αναμενώμενα (abnormal). Οπότε δεν "μένουν" κλειδωμένα αρχεία!

Η εντολή Άνοιξε (OPEN) μπορεί να ανοίξει αρχεία βάσει ANSI, ή WIDE (2bytes κάθε χαρακτήρας). Εξ ορισμού αν δεν δώσουμε το WIDE (ΕΥΡΕΙΑ) θα είναι το ANSI με το τρέχον τοπικό. Με τοπικό 1032 έχουμε ελληνικό ANSI (8bit ο χαρακτήρας). Όταν βάζουμε το ΕΛΛΗΝΙΚΑ τοπικό γυρνάει αυτόματα σε 1032. Με την εντολή Τοπικό 1032 το θέτουμε όταν θέλουμε. Η μεταβλητή ανάγνωσης Τοπικό δίνει τον τρέχον αριθμό.

Στο παράδειγμα παρακάτω, φτιάχνουμε μια Διάρθρωση μνήμης (BUFFER) με βασικό τύπο byte, με συνολικά 12MByte. Το alfa είναι δείκτης στη διάρθρωση, αν αλλάξουμε το δείκτη δίνοντας άλλη διάρθρωση η προηγούμενη χάνεται (δουλεύει με καταμέτρηση δεικτών, στο μηδέν διαγράφεται). Βέβαια μπορούμε να αλλάξουμε μέγεθος (όχι τύπο όμως). Η μνήμη που απασχολεί ενδέχεται να μετακινηθεί κατά την αλλαγή μεγέθους αλλά αυτό είναι πολύ σπάνιο. Για το χρήστη δεν έχει σημασία η μετακίνηση, όσο χρησιμοποιεί την μνήμη χωρίς να δίνει απόλυτες διευθύνσεις.

Η μεταβλητή R αλλάζει σε 1 ή 2 και έτσι επιλέγουμε "τυχαία" αν θα ανοίξουμε αρχείο κατά ANSI, ή κατά WIDE (οι βασικές εντολές γράφουν και διαβάζουν ως έχουν UTF16LE, αλλά στην ουσία δεν αποκωδικοποιούν, και αυτό που μετράει είναι ότι σημάδια όπως το κόμμα και η αλλαγή γραμμής είναι σε UTF16LE). Για το λόγο αυτό μπορούμε να γράψουμε ότι κωδικοποίηση θέλουμε, ειδικά αν βάζουμε και διαβάζουμε με διαρθρώσεις (αυτό σημαίνει διάβασμα/γράψιμο δυαδικών αρχείων,  και έτσι τα αρχεία της Μ2000 είναι και δυαδικά αν θέλουμε να είναι)

Στο παράδειγμα θα φτιάξουμε ένα αρχείο 12GB, και στο τέλος του θα γράψουμε κάτι που θα το διαβάσουμε αφού πρώτα κλείσουμε το αρχείο για εξαγωγή και το ανοίξουμε για εισαγωγή. Στο αρχείο τύπου Συμπλήρωση μπορούμε να εξάγουμε και να εισάγουμε μαζί! Υπάρχει και το αρχείο τύπου Πεδία, που φτιάχνουμε αρχεία με εγγραφές σταθερού πλάτους, και εκεί ο δρομέας λειτουργεί ανά εγγραφή, δηλαδή λέμε να γράψουμε στη 3η θέση εγγραφής. Το πλάτος έχει όριο τα 32768. Τα όρια θα μπορούσαν να μεγαλώσουν αλλά δεν έχουν νόημα να είναι μεγαλύτερα. Αν θέλουμε κάτι πολύπλοκο τότε μπορούμε να τα χειριστούμε με τα τρια πρώτα. Δηλαδή το αρχείο τύπου για Πεδία είναι απλά προσαρμοσμένο για ευκολία.

Στο πρόγραμμα έχει μπει ένα μπλοκ Δες ή Try που ελέγχει την περίπτωση σφάλματος και στην έξοδο κλείνει τα αρχεία με την Κλείσε (και να έχουν κλείσει ήδη όλα, δεν μας χαλάει μια Κλείσε).

Δείτε ότι ο χειριστής αρχείου έχει το # στην αρχή. Δεν το χρειάζεται σε όλες τις εντολές αλλά το βάζουμε, για να βλέπουμε τον κώδικα καλύτερα!

Εδώ έχουμε δώσει το exclusive δηλαδή το Αποκλειστικά, ώστε κανείς άλλος να μην πειράξει το αρχείο μας. Δεν είναι απαραίτητο, αλλά μπήκε για προβολή...

Δείτε ότι με την Seek() ή Μετάθεση() διαβάζουμε την μετάθεση (του δρομέα) από την αρχή. Ο πρώτος χαρακτήρας είναι στο 1.  Δεν υπάρχει πραγματικός δρομέας, αυτό το νούμερο είναι που χρησιμοποιεί το σύστημα αρχείων για τις εντολές που δίνουμε, πάνω στον χειριστή αρχείου, και το ενημερώνει. Δηλαδή αν γράψουμε δέκα bytes, θα τον αυξήσει κατά δέκα. Δεν γυρίζει πίσω αν δεν το "απαιτήσουμε" εμείς, με την Μετάθεση ή Seek (σαν εντολή και όχι σαν συνάρτηση). Για να γράψουμε στο τέλος του αρχείου, η Συμπλήρωση ξεκινάει από σημείο που δεν υπάρχει πραγματικό byte...αλλά θα το γράψουμε εμείς. Έτσι σε ένα κενό αρχείο ο δρομέας είναι στο 1. Ποτέ δεν είναι στο μηδέν (στο λειτουργικό είναι στο 0, αλλά στη Μ2000 το χειριζόμαστε από την μονάδα). Προσοχή, αν ανοίξουμε με Εξαγωγή ένα αρχείο και υπάρχει παλιό το σβήνουμε. 

Η εγγραφές() ή records() μας αναφέρει το μέγεθος του αρχείου. Το ίδιο και η Filelen() ή Αρχείου.Μήκος() αλλά η τελευταία δεν παίρνει χειριστή αρχείου αλλά παίρνει όνομα και ανοίγει το αρχείο μόνο για ανάγνωση χαρακτηριστικών (attributes). Η εγγραφές() αν έχουμε αρχείο για ΠΕΔΙΑ  (RANDOM) τότε δίνει τον αριθμό εγγραφών και όχι το μέγεθος σε bytes.

Γενικά οι εντολές/συναρτήσεις που επηρρεάστηκαν από την αλλαγή είναι οι παρακάτω:

  • OPEN, CLOSE, LINE INPUT, INPUT, PRINT, WRITE, SEEK, PUT, GET
  • SEEK(), RECORDS(), EOF()

Με την εντολή Βοήθεια διαβάζετε τι κάνει η κάθε μια.

Τέλος στην 47 έγιναν και μερικές βελτιώσεις (αθέατες, εκτός και αν διαβάζετε τον κώδικα στο GitHub).



buffer clear alfa as byte*12*1024*1024

const rJustNonProp=3
const columnwidth=14
// you can press Esc (automatic close all files, with last statement)
R=Random(1, 2)
Try ok {
If R=1 then
Open "giant1" for output exclusive as #f
else
Open "giant1" for wide output exclusive as #f
end if
For i=1 to 1024
m=Seek(#f)
put #f, alfa, m
Print Over $(rJustNonProp, columnwidth), m, str$(i/1024,"#.0%"), records(#f)
refresh
Next
Z=Seek(#f)
Print #f,"This is a big file"
Close #f
Print
Print filelen("giant1")
if R=1 then
Open "giant1" for input exclusive as #f
else
Open "giant1" for wide input exclusive as #f
end if
Print Records(#f)
seek #f, z
Line Input #f, what$
Close #f
Print what$
}
Close