Πέμπτη 24 Φεβρουαρίου 2022

Αναθεώρηση 6, Έκδοση 11

 Έγινε μια προσπάθεια να συμμαζευτεί ο κώδικας. Υπήρχε η χρήση του & στη θέση του + για την συνένωση αλφαριθμητικών, στο κώδικα του περιβάλλοντος της Μ2000, πράγμα που δεν χρειάζονταν, παρά μόνο ελάχιστες φορές, επειδή το & κάνει μετατροπή σε αλφαριθμητικό όταν ακολουθεί αριθμητική παράσταση, έτσι το "α" & (1) ισοδυναμεί με το "α1". Η VB6 στη μετατροπή αυτή ακολουθεί το τρόπο εμφάνισης του συστήματος, έτσι αν έχουμε ελληνικά τότε θα δώσει σε δεκαδικό το κόμμα αντί για τελεία. Αν θέλουμε να πάρουμε τελεία σίγουρα θα πρέπει να κάνουμε αυτό "α" + ltrim$(str$(1.5)), όπου το Str$() δίνει μεν τελεία  για υποδιαστολή αλλά βάζει ένα διάστημα αν είναι μηδέν ή θετικός αριθμός, αλλιώς βάζει στο χώρο του διαστήματος μια παύλα (το πρόσημο του αρνητικού). Για το λόγο αυτό χρησιμοποιούμε το ltrim$() (που κάνει αποκοπή, trimming, μόνο από αριστερά, left),  Η γλώσσα Μ2000 δεν έχει τον τελεστή & για συνένωση αλφαριθμητικών, μόνο το +.  Για να βάλουμε ένα αριθμό χρησιμοποιούμε το Str$() ή Γραφή$(), Εδώ υπάρχει και δεύτερη παράμετρος (προαιρετική) που αν δοθεί ως κενό αλφαριθμητικό "" τότε αφαιρεί από τον αριθμό που επιστρέφει ως χαρακτήρες, το διάστημα. 

Εδώ τυπώνουμε το 12.123 βάσει του τοπικού 1032 (ελληνικά), δηλαδή ως 12,123.

Μπορούμε να αλλάξουμε το τοπικό (locale) και να δώσουμε τρόπο που θα εμφανιστεί ο αριθμός, αλλά τώρα το σημείο των δεκαδικών θα το πάρει από τον ορισμό που δώσαμε πριν με την Locale. Εδώ γίνεται και στρογγυλοποίηση. Το 1033 είναι για τα αγγλικά. Υπάρχει ξεχωριστό νούμερο για κάθε χώρα. Δείτε στον πίνακα εδώ (LCID (Locale ID))

? str$(12.123, 1032)

locale 1033
? str$(12.123,"#0.00")

Για να τυπώσουμε αριθμούς δεν χρειάζεται να τους κάνουμε αλφαριθμητικά. Η ? ή Τύπωσε τους εμφανίζει, με το κατάλληλο σημείο δεκαδικών (από το Locale).

Σε αυτήν την αναθεώρηση διορθώθηκε η χρήση στατικών μεταβλητών σε συναρτήσεις που δίνουμε σε αντικείμενα τύπου γεγονός. Παλιά δεν δούλευαν, αλλά άλλαξε αυτό στην 10η έκδοση. Τώρα σκέφτηκα μια αλλαγή, τις στατικές αυτών των συναρτήσεων τις κρατάει το αντικείμενο. Αν αντιγράψουμε το αντικείμενο τότε στο νέο θα ξεκινήσουν νέες στατικές. Όταν το αντικείμενο χαθεί (καταστραφεί) θα διαγραφούν και οι στατικές. Δείτε το πρώτο πρόγραμμα εδώ που είχε δημοσιευθεί το 2016 (έξι χρόνια πριν, στην έκδοση 8).

Στο πρόγραμμα δεν χρειάζεται η Καθαρό για να καθαρίσει τις στατικές, επειδή το αντικείμενο Γεγονός δημιουργείται με κενή λίστα στατικών. Για να μην χάσουμε την μπάλα, που λέμε, βάζουμε και τη Β, μια στατική που παίρνει ένα τυχαίο νούμερο. Έτσι κάθε φορά που καλούμε το γεγονός εκτελούνται όλες οι συναρτήσεις που περιέχει (που προσθέσαμε) και σε κάθε μια δίνει χώρο για στατικές, αν ζητηθεί.  Εδώ έχουμε μια συναρτηση τη Βηματική που την βάζουμε μερικές φορές, αλλά όχι ταυτόχρονα. Οπότε ότι βάλουμε πιο μετά θα ξεκινήσει με καθυστέρηση, το δικό της Α θα ξεκινήσει από το 1 ενώ οι άλλες μπορεί να είναι σε κάποιο άλλο σημείο (εδώ το βήμα ή η φάση, θα το λέγαμε).

Δείτε ότι το αντικείμενο Γεγονός πρέπει να ορίζει "υπογραφή" δηλαδή το τι θα παίρνουν οι συναρτήσεις, εδώ είναι μια αριθμητική τιμή με πέρασμα με τιμή (αν είχαμε βάλει το &Χ θα ήταν πέρασμα με αναφορά, και στη Κάλεσε Γεγονός Α θα ακολουθούσε κόμμα και το &Ι). Επειδή δεν δίνουμε με αναφορά μπορούμε να διαβάζουμε τη τιμή από το σωρό τιμών της συνάρτησης με το Αριθμός. Αυτό δεν μπορεί να γίνει με το πέρασμα με αναφορά. Επειδή αυτό που παιρνάει είναι αλφαριθμητικό, η ισχνή αναφορά, και μια Διάβασε μετατρέπει την ισχνή σε πραγματική, εδώ το Διάβσαε &Μ παίρνει το αλφαριθμητικό, βλέπει ότι υπάρχει το &, βλέπει ότι ακολουθεί μια νέα μεταβλητή, την ορίζει αλλά μόνο ως όνομα, και βάζει τη διεύθυνση της τιμής της εκεί που δείχνει η "αναφερόμενη" μεταβλητή, αυτή του αλφαριθμητικού. Επίσης βάζει και μια "σημαία" που λέει ότι είναι αναφορά για τον απλό λόγο, να μην σβηστεί η τιμή της στο τέλος εκτέλεση της συνάρτησης. Ο διερμηνευτής όταν τερματίζει μια συνάρτηση, πάει από την τελευταία δημιουργημένη μεταβλητή πρός την πρώτη σε αυτή τη συνάρτηση, και βάζει "κενό" στην θέση μνήμης, Αυτό το κενό λέει στο διερμηνευτή την επόμενη φορά που θα δοθεί σε μεταβλητή ότι δεν έχει τιμή. Πχ αν ορίσουμε την Α=12/0 επειδή θα βγεί διαίρεση με το 0, η Α δεν θα έχει τιμή και εκτός από το λάθος "διαίρεση με το μηδέν" θα έχουμε και το λάθος "μη αρχικοποίηση της Α". Αν έχουμε μια εντολή πχ Τοπική Α χωρίς τιμή, το δέχεται ο διερμηνευτής και δίνει μηδέν με τύπο double. Μια τοπική μεταβλητή δεν μπορεί να αλλάξει τύπο αριθμητικό, μπορεί όμως να γίνει αντικείμενο - μετά δεν αλλάζει τύπο αντικειμένου..

Δείτε ότι η Α ξεκίνησε ως αριθμητική τύπου Double και έγινε αντικείμενο τύπου Ομάδα (Group). Οι κλάσεις δίνουν και τύπο το όνομα της κλάσης, εδώ το Κάτι, και το ελέγχουμε με το τελεστή με λέξεις, είναι τύπος. Η Τοπική φτιάχνει και πίνακες, που σίγουρα σκιάζουν οτιδήποτε έχει το ίδιο όνομα, είτε είναι γενικός είτε είναι τοπικός. Πχ Τοπική Μ(-5 έως 5)  ' θα έχει 11 στοιχεία στις θέσεις -5,-4,-3,-2,-1,0,1,2,3,4,5. Μπορούμε να αλλάξουμε μέγεθος χωρίς να σβήσουμε στοιχεία. Με την Πίνακας Μ(-5 έως 20) . τα στοιχεία που ήδη υπάρχουν ξεκινάνε από το κάτω όριο. Η Πίνακας φτιάχνει και πίνακες αν δεν υπάρχουν (όλες οι τιμές είναι κενές, αλλά στις πράξεις φαίνονται μηδενικές, εδώ δεν έχουμε ζήτημα αρχικοποίησης). Ένα τυπικό σφάλμα είναι να φτιάξουμε έναν πίνακα Μ() όπου θα υπάρχει ήδη ως γενικός, και στην ουσία αλλάζουμε τον γενικό! Για να το αποφύγουμε αυτό φτιάχνουμε το τοπικό με την Τοπικό. Αυτό θα το κάναμε σε μια "βιβλιοθήκη" που θα θέλαμε να χρησιμοποιήσουμε από διαφορετικά προγράμματα. Στις μεταβλητές δεν υπάρχει ζήτημα, γιατί το Α=10 θα φτιάξει μια τοπική. Δεν μπορούμε να έχουμε στατική και τοπική με το ίδιο όνομα. Όταν ορίζουμε στατική γίνεται έλεγχος αν το όνομα υπάρχει ως τοπική, και αν ναι βγαίνει σφάλμα (τερματίζει το πρόγραμμα).

Τμήμα Κάπα {
Τοπική Α
Τύπωσε Τύπος$(Α)="Double"
κλάση Κάτι {Χ=1234, Υ}
Α=Κάτι()
Τύπωσε Τύπος$(Α)="Group"
Τύπωσε Α.Χ
Τύπωσε Α είναι τύπος Κάτι = Αληθές
}
Κάπα


Και εδώ είναι το πρόγραμμα με το Γεγονός και τη χρήση περάσματος με τιμή:

Συνάρτηση Βηματική {
      Στατική Α=1, Β=τυχαίος(1000)
      Επίλεξε Με Α
      Με 1
      Τύπωσε "Ένα", Β,
      Με 2
      Τύπωσε "Δύο", Β,
      Με 3
      Τύπωσε "Τρία", Β,
      Αλλιώς
      {
            Τύπωσε "Τέσσερα", Β,
            Α=0
      }
      Τέλος Επιλογής
      Α++
      Τύπωσε Αριθμός
}
Γεγονός Α { Διάβασε X }
Γεγονός Α Νέο Βηματική()
Κάλεσε Γεγονός Α, 0
Κάλεσε Γεγονός Α, 0
Τύπωσε Υπό
Γεγονός Α Νέο Βηματική()
Κάλεσε Γεγονός Α, 0
Τύπωσε Υπό
Γεγονός Α Νέο Βηματική()
Για Ι=1 έως 3 {
      Κάλεσε Γεγονός Α, Ι
      Τύπωσε Υπό
}
Γεγονός Α Πέτα Βηματική()
Κάλεσε Γεγονός Α, 0


Και εδώ έχουμε πέρασμα με αναφορά αριθμητικής μεταβλητής:

Συνάρτηση Βηματική {
      Στατική Α=1, Β=τυχαίος(1000)
      Επίλεξε Με Α
      Με 1
      Τύπωσε "Ένα", Β,
      Με 2
      Τύπωσε "Δύο", Β,
      Με 3
      Τύπωσε "Τρία", Β,
      Αλλιώς
      {
            Τύπωσε "Τέσσερα", Β,
            Α=0
      }
      Τέλος Επιλογής
      Α++
      Διάβασε
      Τύπωσε Μ
}
ΣΣ=0
Γεγονός Α { Διάβασε &X }
Γεγονός Α Νέο Βηματική()
Κάλεσε Γεγονός Α, &ΣΣ
Κάλεσε Γεγονός Α, &ΣΣ
Τύπωσε Υπό
Γεγονός Α Νέο Βηματική()
Κάλεσε Γεγονός Α, &ΣΣ
Τύπωσε Υπό
Γεγονός Α Νέο Βηματική()
Για Ι=1 έως 3 {
      Κάλεσε Γεγονός Α,
      Τύπωσε Υπό
}
Γεγονός Α Πέτα Βηματική()
Κάλεσε Γεγονός Α, &ΣΣ



Δευτέρα 21 Φεβρουαρίου 2022

Αναθεώρηση 5, έκδοση 11 και ανάπτυξη του KarelIDE.

Σε αυτήν την αναθεώρηση διόρθωσα ένα Bug όπου κατά το resize της φόρμας χρήστη υπήρχε περίπτωση να δοθεί σε μια ιδιότητα της φόρμας το -1 όπου δεν το δέχονταν και έριχνε το πρόγραμμα, επειδή δεν υπήρχε σε εκείνο το σημείο μια On Error, στο κώδικα της Visual Basic 6, που είναι γραμμένο το περιβάλλον της Μ2000).

Κάποιοι θα αναρωτιούνται γιατί το περιβάλλον της Μ2000 γράφτηκε σε Visual Basic 6. Αρχικά είχε γραφτεί σε VB5, και μετά σε VB6. Όταν ξεκίνησε δηλαδή το 1999 δεν υπήρχε άλλη γλώσσα για το σκοπό αυτό, που να ήταν και εύκολη η διόρθωση κατά την εκτέλεση (δηλαδή στην εκτέλεση μπορεί κάποιος να προσθέσει κώδικα, και ένα μεγάλο μέρος γράφτηκε κατά την εκτέλεση). Στο τέλος η VB6 βγάζει κώδικα μηχανής και όχι εικονικής μηχανής, με βελτισοποιήσεις, μια εκ των οποίων είναι να αφαιρεί τον έλεγχο ορίων σε πίνακες. Αυτό το τελευταίο χαρακτηριστικό κάνει τον τελικό κώδικα εφάμιλλο της C (που και αυτή δεν έχει έλεγχο ορίων σε πίνακες). Αυτό δεν σημαίνει ότι στη γλώσσα Μ2000 δεν υπάρχει έλεγχος ορίων, αλλά δεν γίνεται δυο φόρες, μια στο επίπεδο του διερμηνευτή και μια στο επίπεδο του κώδικα της Visual Basic. Επιπλέον η Visual Basic 6 χρησιμοποιεί Variants, και προγραμματισμό χαμηλού επιπέδου (πχ στον διερμηνευτή φτιάχνονται αντικείμενα για να συνδεθούν γεγονότα από αντικείμενα COM κατά την εκτέλεση της Μ2000, δηλαδή ενώ ήδη τρέχει το περιβάλλον της Μ2000). Βεβαίως επειδή το περιβάλλον της Μ2000 είναι ανοικτού κώδικα θα μπορούσε κάποιος να φτιάξει μια έκδοση της Μ2000 με δικό του διερμηνευτή, και σε ένα εντελώς άλλο περιβάλλον. Κάποια στιγμή θα ολοκληρώσω και την ιδέα της εικονικής μηχανής, δηλαδή τα τμήματα της Μ2000 να είναι σε μια μορφή κώδικα, σαν ένα AST, αν και δεν μου αρέσει η δεντρική μορφή, προτιμώ να βάλω offsets, και να είναι ο κώδικας σε μια "ευθεία". Πιθανόν θα ξεκινήσω αυτή την αλλαγή το Μάρτιο, αν δεν προκύψει κάτι άλλο.

Σχετικά με τη γλώσσα Karel, σήμερα ολοκλήρωσα το πρώτο μέρος του IDE, που ήθελα να περιλαμβάνει σε μια φόρμα τέσσερα στοιχεία: Τον διορθωτή με χρωματισμό της γλώσσας (έγινε), μια λίστα επιλογών (έγινε), μια γραμμή κατάστασης-πληροφοριών (στο κάτω μέρος της φόρμας) και στο πάνω μέρος της φόρμας εμφάνιση του κόσμου του Karel (έγινε). Επίσης πρέπει να μπορούμε να αλλάζουμε το μέγεθος της φόρμας από κάτω αριστερά και να ακολουθούν την αλλαγή τα στοιχεία. Επίσης μεταξύ του διορθωτή και της λίστας επιλογών επίσης πρέπει να μεταφέρουμε το χώρισμα αριστερά ή δεξιά (έγινε). Επίσης πρέπει να μεταφέρουμε το χώρισμα πάνω από τον διορωθοτή/λίστα για να εμφανίζεται όλος ο κόσμος του Karel. Όλα αυτά έγιναν. Δεν χρησιμοποίησα το νέο στοιχείο Image, ο σχεδιασμός του κόσμου γίνεται στη φόρμα. Επίσης δεν χρησιμοποίησα λίστες ως διαχωριστικά για να δίνουμε την νέα θέση διαχωρισμού. Αυτό που κάνω εδώ είναι να εμφανίζω το κόσμο του Karel μέσα από ένα path (περιοχή που έξω από αυτή δεν φαίνονται γραφικά), και έτσι να μην χαλάω "πορτοκαλί" περιεοχές που δείχνουν τα οριζόντια και κάθετα διαχωριστικά καθώς και τις δυο κάθετες και την κάτω γραμμή του περιθωρίου της φόρμας (που ανοίκουν στο επίπεδο της φόρμας).

O σχεδιασμός του κόσμου πάντα στοιχίζεται κατά πλάτος και αλλάζει μέγεθος βάσει πλάτους, σε κάθε φάση που αλλάζουμε το μέγεθος της φόρμας, και συνάμα αλλάζει μέγεθος και το ρομπότ.


Τον κώδικα για το Karel Ide (γραμμένος με αγγλικές εντολές) θα τον βρείτε στο Git εδώ:

https://github.com/M2000Interpreter/KarelTheRobot

Τώρα μένει η επόμενη φάση, να φτιάξω τις επιλογές να λειτουργούν και ειδικότερα την εισαγωγή από αρχείο (που φτιάχνουμε με το πρόγραμμα δημιουργίας κόσμου, και αυτό υπάρχει τελειωμένο στο git) καθώς και την εκτέλεση του κώδικα (υπάρχει και ο AST interpreter τελειωμένος στο git), που θα εμφανίζει το αποτέλεσμα κατά την εκτέλεση (εδώ μάλλον θα φτιάξω μια φόρμα που θα εκτελεί βήμα προς βήμα το κώδικα, και θα βγαίνει πάνω από την φόρμα του IDE).


(Θα το συνεχίσω όταν τελειώσει ο πόλεμος στην Ουκρανία. Δεν μπορώ να δουλεύω ευχάριστα όταν δίπλα μας σκοτώνεται κόσμος)




Κυριακή 20 Φεβρουαρίου 2022

How M2000 demonstrate algorithms: CRC-32 (cyclic redundancy check)

This tutorial shows how M2000 environment, using M2000 language, code a known algorithm, the crc -32 or cyclic redundancy check.

Also we see how this can be done faster using Api call, the RtlComputCrc32 function of ntdll. This function is a _stdCall (if we wish to call a c functions with _cdecl call we use lib c "..."). Here all parameters are IN (input) and the return value is a 32byte which "converted" to long, signed. This is how internal system supposed to use the number. But it is an unsigned long, 32 bit, so we handle it like this using Uint() which return a Currency type (which is internal 64bit singed integer, rendering to string as a value/1000, showing the least significant 4 digits as decimals, so 15 digits left of decimal point and 4 right the decimal point). If we want to pass a Unsiged long we use sint(), so sint(0XFFFFFFFF) return -1 long type, or 0xFFFFFFFF& which return -1 (but 0xFFFFFFFF return the unsinged value 4294967295), and sint(0xFFFF,2) return -1 long type which easily converted to integer (2 bytes). Also 0xFFFF& is long type 65535, same as 0xFFFF, and 0xFFFF% is -1 integer type.

All 32bit functions like binary.xor() return currency unsigned value from 0x0 ro 0xFFFFFFFF.

So CheckApi, is real easy to understand. We place in a Declare line the name of function CRC32, the library which the actual function exist, and the parameter list which we say which are Long type, which means any 32 byte value, as long signed value, and this include pointers also). We see a$ as input parameter, and this is a long pointer to first character. We have to place the nubper of bytes (which is always 2*len(a$) for any a$, including those who have ansi encoding, and maybe they have odd number of bytes, so a 3.5 length means, 35 words or 7 bytes).

Module checkApi are defined two times. The second time change the first module's definition. So in the second definition we split the string to two parts a$ and b$ and call first for the a$ and the result goes to second call for b$. This demonstrate that we can produce a crc from something we read in parts, without loading all in the memory.

The CheckIt module has an inner function which make a table (an array) and a lambda function which hold the calculated array to lambda closure. So the final crc32 is a variable and a function. As variable can be returned as value (including the closure). As function crc32 can invoke from expressions or using Call, and the closure crct act as local variable. A lambda function can call itself, using either the name of function (which maybe change, so isn't good for recursive call)  or the lambda() or lambda$() depends of major type of return value, and for each call the closures are like global (but is local), the same for each inner call. Here we don't have recursion.

Buffer bur is memory defined in heap, where buf(0) is the real memory address. Real memory addresses needed for passing arrays to external libraries. Here we want only to place the string and just change the indexing to byte boundary, which means each index point to specific byte, starting from 0 (strings  characters starting from 1). The Len() function works for strings and other types, like buffers/  For buffers the return value is the same type as the basic type of buffer, here is the byte (can be byte, integer, long, single, double or a specific Structure which defined in previous steps)

Literal numbers such as 2, 1, -1 are double type (not long). OxFFFFFFFF is currency, 0x0 is currency too. See in crc32(0, a$, len(a$)*2) we pass double, string, double and interpreter convert them to three longs, the second one  a pointer to first string byte. Overflows through exceptions, but we can handle them in Try ok { } blocks, otherwise the module terminate execution and we get the error message (using shift f1 we open editor where the problem occur, most time that works immediately after the error happen)


Module CheckApi {
      Declare CRC32 LIB c "ntdll.RtlComputeCrc32" {Long Zero, a$, long s}
      a$=Str$("The quick brown fox jumps over the lazy dog")
      // a len of 3.5 is 7 bytes. Len is a double which return number of words
      // so a 0.5 word is a byte
      // because we get a long (singed) we convert it to unsigned 32 bit (using same bits)
      Hex Uint(CRC32(sint(0x0),a$,len(a$)*2))
}
CheckApi
Module CheckApi {
      Declare CRC32 LIB "ntdll.RtlComputeCrc32" {Long Zero, a$, long s}
      a$=Str$("The quick brown fox")
      b$=Str$(" jumps over the lazy dog")
      Hex Uint(CRC32(CRC32(0,a$,len(a$)*2),b$,len(b$)*2))
}
CheckApi
Module CheckIt {
Function PrepareTable {
Dim Base 0, table(256)
For i = 0 To 255
k = i
For j = 0 To 7
If binary.and(k,1)=1 Then
k =binary.Xor(binary.shift(k, -1) , 0xEDB88320)
Else
k=binary.shift(k, -1)
End If
Next
table(i) = k
Next
=table()
}
crc32= lambda ' we can break the lambda definition in parts
crct()=PrepareTable() ' this is a closure
(buf$) ' this is the input paremeter
-> { ' this is the function body of lambda
if len(buf$) Else exit
// we make a buffer of bytes and place string there
// Eval(buf, 0) read the first byte as unsigned number (0 to 255)
// buf and buf$ can coexist, they are different variables
buffer buf as byte*len(buf$)*2
return buf, 0:=buf$
crc =0xFFFFFFFF
For i = 0 To Len(buf) -1
crc = binary.xor(binary.shift(crc, -8), crct(binary.xor(binary.and(crc, 0xff), Eval(buf, i))))
Next
=0xFFFFFFFF-crc
}
// str$() convert Utf16LE to ANSI 8bit
// Hex is like Print but conver unsigned numbers to hex
Hex crc32(str$("The quick brown fox jumps over the lazy dog")) ' return 0x414fa339
}
CheckIt

Module CheckIt {

	Function PrepareTable {
Dim Base 0, table(256)
For i = 0 To 255
k = i
For j = 0 To 7
If binary.and(k,1)=1 Then
k =binary.Xor(binary.shift(k, -1) , 0xEDB88320)
Else
k=binary.shift(k, -1)
End If
Next
table(i) = k
Next
=table()
}
crc32= lambda ' we can break the lambda definition in parts
crct()=PrepareTable() ' this is a closure
(crc, buf$) ' this is the input paremeter
-> { ' this is the function body of lambda
if len(buf$) Else exit
// we make a buffer of bytes and place string there
// Eval(buf, 0) read the first byte as unsigned number (0 to 255)
// buf and buf$ can coexist, they are different variables
buffer buf as byte*len(buf$)*2
return buf, 0:=buf$
crc =0xFFFFFFFF-crc
For i = 0 To Len(buf) -1
crc = binary.xor(binary.shift(crc, -8), crct(binary.xor(binary.and(crc, 0xff), Eval(buf, i))))
Next
=0xFFFFFFFF-crc
}
// str$() convert Utf16LE to ANSI 8bit
// Hex is like Print but conver unsigned numbers to hex
Hex crc32(0@, Str$("The quick brown fox jumps over the lazy dog")) ' return 0x414fa339
Hex crc32(crc32(0@, str$("The quick brown fox")), str$(" jumps over the lazy dog")) ' return 0x414fa339
}
CheckIt

Πέμπτη 17 Φεβρουαρίου 2022

Revision 4, Version 11

For this revision: Fix logic for targets for Image Control. This is the FORMTARGET2 example in info.gsb (this file has many modules, you would find it in setup file)



// Now targets are in Image control (see also FormTarget module)
declare Form1 form
declare Image1 image form Form1
Method Form1, "move", 1000, 1000, 10000, 8000
Method Image1, "move", 1000, 1000, 6000, 4000
With Form1, "Title" as Form1.Title$
With Image1, "Visible" as Visible, "Locked" as Locked, "Enabled" as Enabled
Form1.Title$="Click on Form1"
Enabled=true
Layer Form1 {
Gradient 1, 5
move 0,0
draw to scale.x/2, scale.y/2
circle 1500
move 0,0
}
m=false
Module zz {
Form1.Title$="You Click on Top Target"
Layer {
print "ok", timecount
refresh
}
change A, text "ok"
}
Function form1.MouseDown {
Form1.Title$="You Click on Form1"
Stack
refresh
}
Function Image1.MouseDown {
Form1.Title$="You Click on Image1"
Stack
refresh
}
Function Image1.Target {
Print "target:", number, A
Refresh
Form1.Title$="You Click on Bottom Target"
Layer Image1 {
Target A, m
if m then
change A, back 2
else
change A, back 0
end if
}
m~
}
Layer Image1 {
window 16, 8000, 6000;
form 32, 20
cls #333333, 0
cursor 4, 4
target A, "call local zz", 10, 2, 2,15, 105, "press me"
cursor 4, 8
target B,"z", 10, 2, 5,15, 115, "press me too"
}
k=true
function form1.click {
if locked then locked=false
k~
if k then
method Image1, "CopyBack"
layer image1{
change a, border 15
change b, border 15
}
end if
visible=k
}
Thread {
layer Form1 {
// Refresh 1000
move random(3000), random(3000)
draw random(8000), random(8000), random(11,15)
method Image1, "CopyBack"
layer image1{
change a, border 15
change b, border 15
}
method form1, "RefreshAll"
// refresh
}
Layer {
print timecount
refresh
}
} as AA interval 100
locked=true
k=false
method Form1, "show", 1
Threads Erase
declare Form1 nothing


Τετάρτη 16 Φεβρουαρίου 2022

Compose Functions

There are two kind of functions compositions. One kind is the lambda composition, the other use Eval() to evaluate a string as number and Eval$() to evaluate a string as string expression.


For the first kind, we have to make lambda functions. Lambda functions are first citizens, like variables we can pass them like values. Also a lambda function may have closures, and a lambda function can be a closure too.

Compose = lambda (f, g) ->{
=lambda f, g (x) ->f(g(x))
}
Ln1=lambda (x)->Ln(x)
Exp=lambda (x)->round(2.7182818284590452**x)
ExpLn=compose(Exp, Ln1)
Print ExpLn(3)


Compose$=lambda$ (f$, g$) ->{
=lambda$ f$, g$ (x$) ->f$(g$(x$))
}
Uc$=lambda$ (x$) ->Ucase$(x$)
Lc$=lambda$ (x$) ->Lcase$(x$)
UcLc$=Compose$(Uc$, Lc$)
Print UcLc$("GOOD")

For the second kind, we make two classes, one for handling the numeric functions and one for handling string functions. Also user functions have to be global to be used anywhere (and inside a method of an object too).

First we make class comp. There we have common parts for the other two "final" classes: 

  1. The Compose class for using one numeric parameter and two numeric functions.
  2. The ComposeStr$ class for using string parameter and two string functions

The ComposeStr$ has two names, the other name is ComposeStr without $. We have to use the second one if we want to use properties. M2000 for evaluation purposes need the $ character as last character for an identifier which return string value (or object which return string value). For this reason there are lambda$ (we see it before) and the class definition for an object which return string.\

An object (of generic type Group) return value if Value part exist. If the name of class/group has $ as last character then the Value part has to return a string. We can specify one or more parameters.

A property part is an object too. The difference is that we use at Value part the Value = expression, because automatic interpreter make a private variable, here a [formula]$ and from that variable fill the Value, so if we want to change that Value, we have to do in the Value { } part. The Property part has a Value and a Set part. Here the Set part not implemented, so property is read only. The same happen for classes Compose and ComposeStr$, we can define objects but we can't change them. We have to make the Set part.  If we place after value part in class Comp this: Set {read This}  we can change the object with a new Compose or ComposeStr$.

class Comp {
private:
composition$
public:
Property formula$ {
value {
link parent composition$ to c$
Value$=c$
}
}
}
class Compose as Comp {
value (x){
=Eval(.composition$)
}
Class:
module compose(a$, b$) {
.composition$<=a$+"("+b$+"(x))"
}
}
class ComposeStr$ as Comp {
value (x$){
=Eval$(.composition$.)
}
Class:
module composeStr(a$, b$) {
.composition$<=a$+"("+b$+"(x$))"
}
}
// example
function Global Exp(x) {
=round(2.7182818284590452**x)
}
ExpLog=Compose("Exp", "Ln")
Print ExpLog(3)
Print ExpLog.formula$
UcaseLcase$=ComposeStr$("Ucase$", "Lcase$")
Print UcaseLcase$("GOOD")
Print UcaseLcase.formula$


The modules Compose and ComposeStr, of classes Compose and ComposeStr$ aren't part of final objects, but are part at construction. Anything after the Class: label until another label (Public: or Private:) are only at construction (when we call the class). If we make a Group using Class: label, we can use multiple time the parts inside there but if we get a copy of group these aren't copy. This is the way the Class (as function) do. The constructor is the class name as module (without $ if we have ait on the class name). This module called in a different way, so anything we do there, not erased (but if those thing isn't part of the object, at the end of the class execution we get the object and anything else erased.

This is an example where we make a "bare" class alfa with a constructor. In the constructor we decide upon a parameter value which definition for group N we take and then we upgrade This with the group N. Property zeta is readonly property (but we can change the value inside object using the [zeta] (including [ ] characters). We have to use <= to assign new value, so in another module of alfa a statement like this (see the dot before the name) .[zeta]<=500 change the property zeta to value 500. If we ommit the {value} it is like we use this {value, set} so we can write and read the value of zeta. We don't do that except we wish to have code for value and set. We can have a simple variable zeta as public, for read and write from anywhere (inside and outside of an object).

class alfa {
class:
module alfa (x) {
if x>5 then
group N {
property zeta {value}=100
}
else
group N {
property zeta {value}=10
}
end if
this=N
}
}


M=alfa(2)
Print M.zeta=10
M=alfa(20)
Print M.zeta=100


Τετάρτη 9 Φεβρουαρίου 2022

Revision 2 Version 11

Some fixes in this revision:

1.Change a variable from long to currency, to prevent overflow from timetotime Win32  function which return a big number in Windows 10. This number supposed to count milliseconds from the start of Windows, but sometimes get a big value. So a K1 variable which was long type change to Currency and now has no problem. The problem was on sprites module in info.gsb, where the handling of refresh rate was bad, due to overflow (also good handling  on error exceptions, the M2000 environment continue to work, but show flickering in the head line or information line). Just changing the type to Currency eliminate this problem.

We can check if the number of internal counter is big by using Timecount read only variable. This variable if we didn't use Profiler (to adjust an offset, so we get Timecount from that offset), return the actual time from the starting of windows. My computer show 2282833990.198 milliseconds, about 634 hours or 26.4 days. That is not normal, but happen with Windows 10, 64bit, 21H2  (I don't have 11 to check it).

2. XML3 example in info show the use of automatic conversion (only at reading) for numeric character entities:

// New method NumericCharactersEntities
// use this to convert numeric entitites when you read ATTR OR TEXT
// BUT NOT USED WHEN WE WRITE BACK.
// See the É as É (201 or 0xC9)
pen 14 : Cls 5,0 :Print $(4),
declare databank xmldata
method databank, "NumericCharactersEntities", true
with databank, "xml" as doc$, "beautify" as beautify, "status" as status$
doc$={<?xml?>
<Students>
<Student Name="April" Gender="F" DateOfBirth="1989-01-02" />
<Student Name="Bob" Gender="M"  DateOfBirth="1990-03-04" />
<Student Name="Chad" Gender="M"  DateOfBirth="1991-05-06" />
<Student Name="Dave" Gender="M"  DateOfBirth="1992-07-08">
<Pet Type="dog" Name="Rover" />
</Student>
<Student DateOfBirth="1993-09-10" Gender="F" Name="Émily" />
</Students>
}
beautify=-4
Report 3, doc$
With Databank, "Attr" as Attr$()
Print status$=""
Method databank, "GetListByTag", "Student", -1 as Result
Print type$(Result), len(Result)
c=1
If len(Result)>0 then
Stack Result {
Read fieldsNo : With fieldsNo, "Attr" as fieldsno.tag$()
}
Stack Result {
Print c, " "+fieldsno.tag$("Name")
c++
if empty else LOOP  // Loop raise a flag for this block, which interpreter read at the end of block, and then cleat it
Read fieldsNo
}
end if
// If you change value, this value not saved with escaped numeric character edities
// only the xml special characters escaped:
// quot "
// amp &
// apos '
// lt <
// gt >
rem 1:
fieldsno.tag$("Name")=@conv$("Émily")
beautify=-4
Report 3, doc$
declare databank Nothing


Function Conv$(a$)
if len(a$)=0 then exit function
local b$, c$, k
for i=1 to len(a$)
c$=mid$(a$, i,1)
k=uint(chrcode(c$))
Rem 2:if k>127 then b$+="&#"+str$(k,"")+";" else b$+=c$ // this place decimal value
if k>127 then b$+="&#x"+hex$(k,2)+";" else b$+=c$ // this place hexadecimal value
next
=b$
End Function


3. JsonObject now has a DeleteKey method:

function KeyList (json) {
flush
with json, "index" as json.index, "count" as json.count ,"KeyToStringPos" as keyName$()
if json.count=0 then =(,): exit
for i=0 to json.count-1:data keyname$(i):next
=array([])
}
declare Json JsonObject
json$={
{
    "description":"A person",
    "type":"object",
    "properties":
        {
            "name":
                {
                    "type":"string"
                },
            "age":
                {
                    "type":"integer",
                    "maximum":125
                }
        }
}
}
method json, "parser", json$ as json
with json, "json" as json.format$(), "item" as json(), "item" as json$()
with json, "itempath" as json.path(), "itempath" as json.path$()


Report json.format$(4)
Print json$("description")
Print json$("type")
Print json.path$("properties.name.type")
Print json.path$("properties|age|type", "|") ' define different seperator
Print json.path("properties.age.maximum")
method json, "assignpath", "properties.age.maximum", 80
Report json.format$(4)
Print json.format$(0)


list1=Keylist(json.path("properties"))
Print format$("Found {0} properties:", len(list1))
nl$={
}+" - "
Print #-2, " - "+list1#sort()#str$(nl$)
// New in revision 2 version 11 deletekey
Report json.format$(0)
//Method json, "deletekey", "type"
Method json, "deletekey", "properties"
Report json.format$(0)




Παρασκευή 28 Ιανουαρίου 2022

Version 11

1. Image Control like a console for input and output:

The Ver11 in Info file has this code:

declare form1 form
declare image1 Image form form1
METHOD image1,"MOVE", 1000,1000, 6000, 3300
With image1,"KeyEvent", true, "Enabled" as enabled, "tabstop", true, "showcaret" as caret, "default", true
enabled=true
caret=true
layer image1 {
form 20, 10
CLS 4,0 : PEN 15
DOUBLE
REPORT 2, "Example"
NORMAL
CLS #336633, 2
REFRESH 10
}
function form1.click {
enabled= not enabled
if enabled then method image1, "GetFocus"
}
bufall=stack
buf$=""
function getline$() {
if stack.size=0 then exit
shift 1,-stack.size
exp$=""
while not empty
exp$+=letter$
end while
=exp$
}
function image1.lostfocus {
layer image1 {
cursor 0, row:print over $(0), getline$(buf$, ! stack(bufall))
}
}
function image1.gotfocus {
layer image1 {
cursor 0, row:print over $(0), buf$;
}
}
function image1.keydown {
read new &k, &s
if s<>2 then exit
if k=86 then
local clip$=clipboard$
if clip$<>"" then keyboard clip$ : call local image1.keypress()
k=0:s=0
else.if k=67 then
local clip$=getline$(buf$, ! stack(bufall))
if clip$<>"" then clipboard clip$
k=0:s=0
end if
}
Def CheckSurrogateLead(w as integer)=uint(w)>0xD7FF and uint(w)<0xDC00
// HERE WE USE THE TAIL WHEN WE DELETE CHARS (SO WE CAN DELETE DOUBLE WORD CHARACTERS)
Def CheckSurrogateTrail(w as integer)=uint(w)>0xDBFF and uint(w)<0xE000
buf1$=""
function image1.keypress {
if not caret then caret=true
layer image1 {
LOCAL a$, onemore as boolean
a$=KEY$
DO
if a$=chr$(8) then
onemore=false
if len(buf$)>0 then
local w=chrcode(right$(buf$,1))
do
onemore=false
if CheckSurrogateTrail(w) then onemore=true
buf$=left$(buf$, len(buf$)-1)
until not onemore or len(buf$)=0
end if
if len(buf$)=0 and len(bufall)>0 then
stack bufall {read buf$}
if onemore then
buf$=left$(buf$, len(buf$)-1)
end if
cursor 0, row:print over $(0),buf$;
else
if pos>0 then cursor 0, row:print over $(0),buf$;
end if
else.if len.disp(a$)=0 and chrcode(a$)>32 or chrcode(a$)<0 then
// THIS IS FOR COMBINING_DIACRITICAL_MARKS LIKE CHRCODE$(0x301)
// COMBINING ACUTE ACCENT
// hold Alt press + 3 0 1 release Alt
buf$+=a$
cursor 0, row:print over $(0), buf$;
else.iF a$=CHR$(13) then
buf$=getline$(buf$, !bufall)
layer {print buf$:refresh}
cursor 0, row:print over $(0),buf$
print: buf$=""
else.if a$>=" " then
if len.disp(buf$)=width-1 then
stack bufall {push buf$}:buf$="": cursor 0, row:print over
end if
print a$; : buf$+=a$
end if
a$=KEY$
UNTIL a$=""
refresh
}
}
METHOD form1 "SHOW", 1


declare form1 NOTHING


The Image1.KeyPress is an event service function. The actual key retrieved using Key$ (which here not waiting for a key, as in pure M2000 console). We use a Do Until, loop to get all keys, before the end of service.

This is the first version, where we can write a big line and under the hood is a stack object, the bufall, which hold chunks of input line (those parts we didn't show). When we press Enter all chunks combined using the getline$() function. The statement: buf$=getline$(buf$, !bufall) call the getline$ and push on getline$() stack on top buf$ and under all items from bufall (so !bufall just send items from bufall which is a stack to functions stack, and this is fast, because each item in stack has one pointer to copy, the bufall get empty after that).

Some times we change the focus of Image1 so we get the lostFocus event and reset the line from the beginning (first character at the left of "console", or we say left justify). Because we have to prepare the final string, immediatetly, we use this getline$(buf$, ! stack(bufall)) where stack(bufall) return a copy of bufall, so the bufall stack remain intact. The ! operator used for arrays (tuple) and stacks, but for stacks this retrieve all items, and leave them empty.

About Stacks and Arrays.

We can make stacks from arrays, and arrays from stacks. The actual type of stack is mStiva (a com object) and for array (tuple) is mArray. 

a=stack((1,2,3))
? type$(a)="mStiva"
? a
? stackitem(a,2)=2
b=array(stack:=1,2,3)
? type$(b)="mArray"
? b
? array(b, 1)=2

Variables a and b are pointers to objects. There is another interface for arrays, which used with identifiers with parenthesis. These arrays are like values. We have to define them using Dim (or Local or Global), We can use up to 10 dimensions, and we can set bounds like this A(1 to 10), B(-4 to -3, 100 to 103) . We can use pointers to arrays like the c variable in the following example:

Dim A(10)=1, B()
B()=A()
B(3)+=10
c=B()
c++
? B(3)=12, B(0)=2, A(0)=1

So c++ add one in each numeric item of B(). Also pointers to arrays handle the array as one dimension. Arrays expand from the first dimension (preserving items). The k variable now is a pointer to k(), but read items as one dimension. Using array() we can use the true dimensions. We can change dimensions with another Dim, and if we place new items these will be of type Empty.

dim K(3,2)
k(0,0)=1,2,3,4,5,6
? K()
dim K(4,2)
k(3,0)=7, 8
? K()

k=k()
? k#val(7)=8

Print array(k, 0,0)


2. Splitter Control using a Listbox

We can make a splitter control using a listbox and proper configuration, through properties and methods, and use of proper events.

In the Info file (info.gsb) included in setup file the new mEditor which utilize the splitter as vertical splitter between the editor's editbox and the help editbox (you have to reload to user directory, using Dir AppDir$ : Load Info  and then press F1 to save it to M2000 user directory - also this change the current directory to user directory). Open the mEditor source using Edit mEditor statement in M2000 console.


We can make a fake splitter control using events from form, but the new splitter can show the new split without actually do the split (without resizing any controls we have to split), until we get the mouseup event. Also we can change the color at the moving stage, and restore it at the final mouseup event. The splitter configure to not included as a tabstop (so pressing TAB and Shift TAB we don't choose it). I am thinking to implement a keyboard control for splitter (so then the TAB Stop  is usefull)..


We can make three types of splitter, horizontal, vertical and both. Because a ListBox can be place on top of an Image control, we can use splitter to split controls on an Image (container).

A listbox can use the included logic for moving, either as HeaderOnly (so we use it as a simple frame, with a color and perhaps some characters as icons, for title), or we can with a list. We can configure to move the control or the container of control. The form's caption is on glis4.cls user control (the same under Listbox) which use this logic to move the form.

From mEditor source, this is the code to configure the Listbox, named Handle1 on form NotePad.
Declare Handle1 Listbox Form NotePad

Because the splitter we want to be visible only if HelpPad (the EditBox for help) is visible, we need to link property "Visible" to handle1.visible (dots can be used for variable names). The handle1.visible is not a boolean variable, so we cant do this handle1.visible~ to invert the value, because it is a PropReference object type (an object to hard link the property with an identifier). This object for this property has a boolean value. Any non zero number converted to True, and zero number converted to False. By default in M2000, True is not boolean, has value -1, and False hase value 0. Comparisons in M2000 return boolean type.

If we don't want a control to be visible when the form shown the first time, we have to lock it. The unlocked control's visible property change from false to true by the form at the show method of form. When a control is not visible tabstop not used. For Handle1 we want this happen always, so we place false in "tabstop" property.
For handling the title back color we link mcolor, and for the wanted return value we need the handle1.top (linked to "top" property, is a single float number),


With Handle1, "Visible" as handle1.visible, "locked" as handle1.locked, "headeronly", true, "tabstop", false, "TitleBackColor" as mcol, "top" as handle1.top
Method Handle1, "UseVerMove"
mcol=#FFA000
handle1.locked=true
handle1.visible=false

So we start using Locked=true, visible=false.

Before the opening of NotePad we have to call the resize event function by code, using this:

Call Local Notepad.Resize()

The Call Local place the same namespace as the current one, so from Notepad.Resize the interpreter think is running in module not in function, so the Call Local means call with same scope as the current. Although the scope is the same (as namespace), actually every new identifier erased at the return from call. Because events also called using a call like call local (except the stack of values is new), we have always use New identifier before we read values from stack, to make new identifiers (to shadow identifiers with same name in same scope, which prevent from altering values). Also if we want variables for local use, we can defined using Local statement. Also if we want a new link for a property, we can use  as new.

So this is the resize function (as event service function):

Function Notepad.Resize {
Local FromHelp=false
if match("N") then Read FromHelp
Layer NotePad { Cls Color(255, 160, 0) ,0}
local tHeight1=theight*2
Local free=NP.Height-tHeight1-twipsX*3
local oldhelpshow=helpshow
If NP.height>1800 Then {
If helpview Then
if oldhelpshow else
Method Handle1, "move",twipsX*3, tHeight1+free-free/3-twipsX*3, NP.Width-twipsX*6,twipsY*3
end if
if handle1.locked then handle1.locked=false
local third=handle1.top-tHeight1
if third<twipsX*10 then third=twipsX*10
if free<third then third=free/3
Method Pad,"move", twipsX*3, tHeight1, NP.Width-twipsX*6, third
third+=tHeight1
Method Handle1, "move",twipsX*3, third, NP.Width-twipsX*6,twipsX*3
Method HelpPad,"move", twipsX*3, third+twipsX*3, NP.Width-twipsX*6,free-third-twipsY*3+tHeight1
handle1.visible=true
helpshow=true
Method HelpPad, "resize"
Else
helpshow=false
handle1.visible=false
Method Pad,"move", twipsX*3, tHeight1, NP.Width-twipsX*6, NP.Height-tHeight1-twipsY*3
End If
Method Pad,"Resize"
}
}

Because we want the color of splitter control (the Handle1, a listbox used as splitter) we have to handle these events:

Function Handle1.ValidMove {
Drop
Read New &Y
mcol=7
if y<tHeight+1000 then y=tHeight+1000 : exit
if y>NP.Height-tHeight then y=NP.Height-tHeight
}
Function Handle1.MouseUp {
Call Local Notepad.Resize()
mcol=#FFA000
}

The ValidMove get two byref values, we drop one and we use only the Y (vertical move). So we can stop the move by handling this Y value, if Y get out of bounds we like.

When the move stopped releasing the mouse button we get the MouseUp event and we call the resize event service function of Notepad (the form), plus we restore the color. The #77A000 is the M2000 orange color.

The Handle1 also change Y when we resize the form Notepad, but not always. See line:

if free<third then third=free/3
in Notepad.resize function

Also for this Notepad, I add a new item in Edit menu, with F1 accelerator key for changing Wrap mode.