Πέμπτη 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)