Last update: 04/06/2017

Παράδειγμα εισόδου και tokens που θα αναγνωριστούν

Εισοδος:

23   5.5
myvariable
32s
"Hello\nExample"

Θα αναγνωριστούν τα εξής tokens:

line- numOfToken- Περιεχόμενο- Κατηγορία
1 #1 23 INTCONST
1 #2 5.5 REALCONST
2 #3 myvariable IDENT
3 #4 32 INTCONST
3 #5 s IDENT
4 #6 "Hello

Example" STRING

Με ενδιαφέρει σε αυτή τη φάση εάν ένα if statement είναι συντακτικά ορθό;

Όχι αυτο θα ελεγχθεί στην επόμενη φάση που είναι η συνακτική ανάλυση. Για αυτη τη φάση μαζεύετε τα Tokens χωρίς να ελένχετε τίποτα άλλο.

Απορίες σχετικά με escaped χαρακτήρες

  • Ποιοι είναι οι χαρακτήρες που πρέπει να χειριστούμε στα string με ειδικό τρόπο? πχ τα"\η, \t, //" είναι. ποια αλλα?

    Οι βασικοί είναι αυτοι που είπες, συν το \" για να τυπώσεις " μεσα σε string. Αν θέλεις μπορείς να βρείς όλους τους escape characters πχ για τη γλώσσα C εδω: https://en.wikipedia.org/wiki/Escape_sequences_in_C

  • Τι συμβαίνει με τους escaped χαρακτήρες μέσα στα strings; Μπορεί ένα string να περιέχει πραγματικούς ειδικούς χαρακτήρες πχ. newlines και tabs;

    Τα strings που περιέχουν escaped χαρακτήρες, θα πρέπει να τους αναγνωρίζουν ως ειδικούς χαρακτήρες, δηλαδή το string “hello world\n”, θα περιέχει τους χαρακτήρες h, e, l, l, ..., d, newline και όχι τους χαρακτήρες h, e, l, l, ..., d, \, n. Το ίδιο βέβαια ισχύει και για τους άλλους escaped χαρακτήρες. Από εκεί και πέρα ένα string μπορεί να περιέχει έναν πραγματικό χαρακτήρα tab αντί για τον escaped \t, αλλά η χρήση ενός πραγματικού χαρακτήρα newline είναι προαιρετική. Η χρήση ενός πραγματικού χαρακτήρα newline μέσα σε strings, επιτρέπει στους χρήστες να δημιουργούν μεμονωμένα strings που καταλαμβάνουν παραπάνω από μία γραμμές (multi-line strings) εξυπηρετώντας μόνο αισθητικούς “σκοπούς”.

  • Εφόσον αποθήκευουμε κάθε escaped χαρακτήρα σαν έναν, τι γίνεται με τον επόμενο χαρακτήρα (πχ. τo n στο \n).

    Πετάς και το \ και το n και βάζεις στη θέση τους τον χαρακτήρα '\n'. Αντικαθιστάς δηλαδή τους 2 χαρακτήρες με τον έναν που τους αντιστοιχεί.

  • Στην περιπτωση που εχουμε "\t\b\nabcdef", στην εξοδο πρεπει να εμφανιστει το παρακάτω ;

    "    \b
    abcdef"
    

    Το \ είναι ειδικός χαρακτήρας μέσα στο string. Σημαίνει, οτι θα το χειριστούμε παρέα με τον χαρακτήρα που ακολουθεί για να τυπώσουμε κάτι ειδικό - πχ newline, tab, " (χωρίς να κλείσει το string) κτλ... Σε περίπτωση που ακολουθεί διαφορετικός χαρακτήρας απο αυτούς που περιμένουμε, τότε πρέπει να το χειριστούμε με έναν απο τους παρακάτω τρόπους:

    1.το αφήνουμε μέσα στο string, όπως έκανες στο παράδειγμά σου, είτε με το \ είτε χωρίς, ΒΓΑΖΟΝΤΑΣ ΟΜΩΣ ενα μήνυμα warning ότι αυτό το string περιέχει τον invalid escape character b

    2.Πετάμε μήνυμα λάθους και δε δεχόμαστε καθόλου το string.

Πως τρέχω τον flex στα windows?

  1. Κανεις εγκατάσταση τον flex gia windows
  2. Βρίσκεις το path μέσα στο οποίο έχει γίνει η εγκατάσταση του flex (px: C:\GnuWin32\bin) και είτε μεταφέρεσαι mμε cd στον φάκελο του μέσα από το cmd είτε βάζεις τον flex σαν environmental variable.
  3. Ανοίγεις το cmd και δίνεις την εντολή flex.exe path_name_tou_arxeiou (για να είσαι σίγουρος ότι είναι σωστό το path_name_tou_arxeiou τότε πάρε cp το .l και βάλε το στον φάκελο του flex.exe για να είσαι σίγουρος ότι το path είναι οκ).

Αυτό που θα πρέπει να σου βγάλει σαν output file είναι το lex.yy.c που θα είναι ο λεξικογραφικός αναλυτής σου. Αν δεν σου βγάζει κάτι τότε κάτι κάνεις λάθος από τα παραπάνω βήματα, διότι το δοκιμάσαμε και δουλεύει, οπότε η ξαναπροσπάθησε, ή στην χειρότερη ανέβασε το στην περιοχή σου και κάνε αυτά που λέει το front1 για linux.

Τι γίνεται όταν συναντήσω undefined χαρακτήρα?

Αν κατά την λεξικογραφική ανάλυση συναντήσετε undefined chars η γενικότερα είσοδο που δεν κάνει match σε κάποιο από τους κανόνες σας, θα πρέπει να έχετε έναν επιπλέον κανόνα μέσα από τον οποίο το action που θα κάνετε είναι να πετάτε ένα σχετικό μήνυμα λάθους (undefined input x in line y).

Παράδειγμα

!asdasd  //asdasda

To ! είναι ένας undefined char οπότε θα πρέπει να τυπώσετε μήνυμα λάθους και είτε συνεχίζετε με την αναγνώριση του υπόλοιπου input (asdasd //asdasda), είτε να σταματήσετε την συντακτική ανάλυση εκεί.

Τι είναι και πως χρησιμοποιείται η alpha_yylex?

Η alphayylex είναι στην ουσία η συνάρτηση μέσα από την οποία καλείται ο lex. Είναι το ίδιο πράγμα με την yylex απλά στην εκφώνηση έχει διαφορετική ονομασία με το prefix *"alpha"*. Στην ουσία είναι το όνομα της συνάρτησης που θα πεταχθεί απο τον lex και θα καλείται εσείς απο την main σας ώστε να γίνεται η λεξικογραφική ανάλυση. By default το όνομα της συνάρτησης που παράγει το εργαλείο είναι το yylex, για να αλλάξετε το όνομα αυτής αρκεί, όπως φαίνεται και στο φροντιστήριομ να κάνετε define το YY_DECL την υπογραφή της συνάρτησης πχ. int alpha_yylex (...)

Πως χρησιμοποιώ την παράμετρο που θα πέρνει ο aplha_yylex?

Θα πρέπει να ορίσετε εσείς ένα alpha_token_t το οποίο θα περιέχει τα στοιχεία που σας ζητάει να κρατάει η εκφώνηση και μπορεί να είναι κάπως έτσι (δεν υπάρχει περιορισμός μπορεί να είναι όπως θέλετε αρκεί να κρατάει ότι είναι απαραίτητο):

struct alpha_token_t {
  unsigned int     numline;
  unsigned int     numToken;
  char          *content;
  char          *type;
  struct alpha_token_t *alpha_yylex;

}
  • Είναι απαραίτητη η παράμετρος αυτή?

    Ο aplha_yylex παίρνει μια παράμετρο pointer στη μνήμη μίας μεταβλητής τύπου alpha_token_t, στην οποία και θα αποθηκεύετε τα χαρακτηριστικά γνωρίσματα του εκάστοτε αναγνωρισμένου token. Αρα ο alpha_yylex γεμίζει ένα token struct και έπειτα επιστρέφει. Αυτό το struct θα το βάζετε στη λίστα με τα Tokens. Επομένως η παράμετρος είναι απαραίτητη.

Απορίες σχετικά με σχόλια

  • Πως διαχειρίζομαι τέτοιου είδους σχόλια?

    /*aaaa
    /*aa*/
    
    • Ανοίγει το 1ο comment με το 1ο "/*"
    • Στην συνέχεια μετά τα "aaaa" συναντάμε το 2o "/*" οπότε ανοίγουν σχόλια μέσα σε σχόλια.
    • Μετά τα "aa" κλείνει το 2o σχόλιο (εμφωλευμένο) "*/" και ο συντακτικός αναλυτής θα συνεχίζει να τραβάει input έως ότου να βρει το κλείσιμο των σχολίων.

    Αν δεν βρεθεί κάτι τέτοιο έως το τέλος του αρχείου θα πρέπει να πετάξετε error για σχόλιο που δεν έχουν κλείσει.

  • Πως διαχειρίζομαι σχόλια με // αν έχουμε για παράδειγμα // assafa /dsadsa/?

    Το αγνοείτε κανονικά αφού το // είναι Line comment και οτιδήποτε υπάρχει στην γραμμή από το // και μετά ανήκει σε αυτό.

  • Πρέπει να εκτυπώνουμε τα σχόλια όταν είναι εμφωλευμένα?

    παράδειγμα

    /* mpla
        /*aaa*/
    */
    

    Δεν θα πρέπει να κρατάτε το περιεχόμενο των σχολίων, απλά μόνο το τι σχόλιο αναγνωρίσατε (καθώς και το πλήθος του), οπότε αυτό που τυπώνετε για το παράδειγμα είναι:

    1. Nested comment
    2. Comment
  • Συνδιασμοί one με multiline comments.Τι θα αναγνωριστεί στη περίπτωση που έχουμε το εξης:

    //kati edw/*kati allo
    
      pame katwme enter */
    

    Θα πρέπει να αναγνωριστεί ένα one line comment, 3 IDENT (pame, katwme και enter), ο OPERATOR * και ο OPERATOR /

    /*kati//kati allo*/
    

    Θα πρέπει να αναγνωριστεί ένα multiline comment.

Απορίες σχετικά με strings

  • Πως διαχειρίζομαι Strings που δεν κλείνουν ("..)?

    Ξεκινάει να αναγνωρίζει string, επομένως αν δεν κλείνει έως το τέλος του αρχείου θα πρέπει να πετάει σχετικό error.

  • Στην περίπτωση που η είσοδος χρήστη περιέχει enters ή tabs ή πολλά κενά τι κάνω?

    Όταν ένα string περιέχει enters ή tabs, ουσιαστικά περιέχει τους χαρακτήρες '\n' και '\t' αντίστοιχα.Απλά τους κρατάς ως έχουν στο δικό σου string.

Πώς γίνεται η αναζήτηση (lookup) ενός συμβόλου μέσα στο symbol table του μεταφραστή;

Προκειμένου να βρεθεί (να γίνει resolve) ένα σύμβολο που εμφανίζεται σε μία συνάρτηση, θα πρέπει η διαδικασία της αναζήτησης να περάσει από τα εξής στάδια (η διαδικασία σταματάει σε οποιοδήποτε στάδιο μόλις βρεθεί το σύμβολο):

  • Αναζήτηση του συμβόλου ανάμεσα στις τοπικές μεταβλητές, οποιουδήποτε τύπου, της συνάρτησης
  • Αναζήτηση του συμβόλου ανάμεσα στις παραμέτρους, οποιουδήποτε τύπου, της συνάρτησης
  • Αναζήτηση του συμβόλου σε όλα τα επίπεδα (scopes) που είναι μικρότερα του τρέχοντος και μεγαλύτερα του καθολικού (global) ανάμεσα μόνο στις συναρτήσεις που έχουν οριστεί σε αυτά
  • Αναζήτηση του συμβόλου ανάμεσα σε όλα τα σύμβολα, οποιουδήποτε τύπου, που έχουν οριστεί στο καθολικό επίπεδο. Εάν αποτύχουν όλα τα στάδια τότε το προς αναζήτηση σύμβολο δεν υπάρχει και θα πρέπει να εισαχθεί. Τέλος εάν το προς αναζήτηση σύμβολο ακολουθεί το token “::”, η αναζήτησή του θα πρέπει να γίνει αναγκαστικά στο καθολικό επίπεδο (βλέπε κανόνα 4).
  • Οσον αφορά τον Hash Table:

    Η υλοποίηση είναι δική σας επιλογή.

Συνεχίζεται η συντακτική ανάλυση σε περίπτωση που βρω error ?

Σε περιπτώσεις λάθους το ιδανικό είναι να βγάζετε κατάλληλο μήνυμα και να συνεχίζετε τη συντακτική ανάλυση στο υπόλοιπο κείμενο (όπως κάνουν δηλαδή οι πραγματικοί compilers). Πάντως στην περίπτωση που σας δυσκολεύει μια τέτοια συμπεριφορά, αρκεί να βγάλετε σχετικό μήνυμα λάθους και να τερματίζετε το compilation επιτόπου.

Στην περίπτωση όπου η funcdef αποτύχει πρεπει να αγνοήσω τα formal arguments και οτι υπάρχει μεσα στα blocks?

Παράδειγμα:

  f=1;

  function f(a,b)
  {
   x=5;
  } 

Θα μπορούσες να συνεχίσεις κανονικά με τη συντακτική ανάλυση και έτσι να αναλύσεις τα arguments και το body, απλά χωρίς να βάλεις τη συνάρτηση f στο symboltable (καθώς προκαλεί λάθος).

Παραδείγματα εμβέλιας(scope) μεταβλητών

  • Παράδειγμα με formal arguments

    x=9;
    function foo(x,y){} 
    

    Ακολουθώντας τους τους κανόνες στις τελευταίες διαφάνειες του φροντιστηρίου 3 προκύπτει:

    • x μεταβλητή scope 0,
    • foo συνάρτηση scope 0,
    • x, y μεταβλητές (formal arguments) σε scope 1
  • Θα πρεπει να πεταξει καποιο error στην κληση της x απο τη y ?

    { { function x(){} function y(){ x();} } }

    Με βάση τους κανόνες στις **τελευταίες διαφάνειες του φροντιστηρίου 3** το παραπάνω παράδειγμα είναι οκ.
    
  • Προσβασιμότητα μεταβλητών σε outer block απο το body εσωτερικής function

    Έστω οτι ειμαι στο scope 1 και εχει μια μεταβλητη y. Εχω μετά μια συνάρτηση μέσα στο ιδιο scope και βαζω και εκεί την μεταβλητή y ως lvalue.Τοτε πετάω error επειδη είναι εκτός της function το y ή φτιάχνω την μεταβλητη y μέσα στη function;

    Ακολουθήστε τις οδηγίες των τελευταίων 4 slides του φροντιστηρίου 3 όπου καλύπτονται όλες οι περιπτώσεις!

    Για το συγκεκριμένο παράδειγμα που αναφέρεις εξαρτάται από τον κώδικα:

    • αν είναι:
      { 
      y = 1;
      function ex (a) {
        y = 3; // error variable y in line 2 is not accessible in function ex
      }
      }
      
    • αν είναι:
      { 
      y = 1;
      function ex (a) {
       local y = 3; // define new local variable in scope 2 in line 4 
      }
      }
      
    • αν είναι:
      { 
      y = 1;
      function ex (a) {
        ::y = 3; // error, undefined global variable y in line 4 
      }
      }
      
  • Προσβασιμότητα global απο nested functions

    για να checkaroyme το x στην γραμμή 5 αν είναι valid, θα βρει το 3 πρώτα στην γραμμή 3, το οποιο είναι global από την γραμμή 1, σωστά?

    1 x = 5
    2 function foo{
    3    ::x = 3;
    4    function g(){
    5        x = 9;
    6    }
    7 }
    
    

    Όπως έχουμε πει αρκετές φορές δεν θα πρέπει να αντιμετωπίζουμε την κάθε περίπτωση ξεχωριστά. Οι περιπτώσεις έχουν ομαδοποιηθεί και είναι μαζεμένες στα 3-4 τελευταία slides του φροντ 3.

    Το συγκεκριμένο ανήκει στην τελευταία περίπτωση (slide 20) όπου το variable είναι "σκετο" δηλ δεν συνοδεύται από :: ή local. Σε αυτή την περίπτωση κάνουμε lookup πρώτα στο current scope και στην συνέχεια σε πιο έξω scope εως ότου φτάσουμε στο global scope. Εαν σε οποιοδήποτε scope το βρούμε τότε σταματάμε την αναζήτηση και αναφερόμαστε σε αυτό το σύμβολο. Αυτό που μένει τώρα να δούμε είναι αν έχει δικαιώματα πρόσβασης. Αν δεν αναφέρεται σε global scope ή στο current scope και είναι μεσολαβεί συνάρτηση τότε πρέπει να πετάτε error.

    Στο συγκεκριμένο παράδειγμα που αναφέρεις ψάχνει την μεταβλητή αρχικά στο current scope (i.e. 2) δεν τη βρίσκει. Στην συνέχεια ψάχνει την μεταβλητή στο αμέσως πιο πάνω scope (i.e. 1) δεν τη βρίσκει (::x δεν εισάγει κάποιο συμβολο στο symbol table απλά κάνουμε lookup στο scope 0). Τέλος, κάνουμε lookup στο πιο πάνω scope (i.e. 0) και εκεί αναφερόμαστε. Επειδή είμαστε σε global τότε η μεταβλητή είναι ορατή από στο scope 2 που προσπαθούμε να την κάνουμε access οπότε όλα καλά δεν υπάρχει κάποιο λάθος.

Που πρέπει να αποθηκεύουμε τα formal arguments ενος function ?

Οπωσδήποτε ως formals στο symboltable (ώστε να γίνεται σωστά το lookup μέσα στη συνάρτηση), και προαιρετικά ως extra πεδίο του struct function (μόνο και μόνο για να ξέρετε τα ορίσματα που παίρνει η εκάστοτε συνάρτηση).

Απορίες πάνω σε ορισμούς συμβόλων

  • Πότε ορίζεται το x ώς μεταβλητή και πότε συνάρτηση?

    Ακολουθόντας πάντα τους κανόνες στο τέλος του 3ου φροντιστηρίου . Αν δηλώνεις κάτι, τότε είναι ανάλογα με το τι δηλώνεις: το funcdef δηλώνει συνάρτηση, όλα τα υπόλοιπα δηλώνουν μεταβλητές. Όλες οι υπόλοιπες περιπτώσεις που απλά αναφέρεσαι σε κάτι (γιατι έκανες lookup και βρήκες κάτι) είναι ανάλογα με το τι βρήκες, δηλαδή μπορεί να είναι είτε μεταβλητή είτε συνάρτηση.

  • Στην περίπτωση something.id πως διαχειρίζομαι το id

    Σε αυτήν την περίπτωση το id δε μας ενδιαφέρει, δε μπαίνει στο symboltable, δεν το κάνουμε lookup, δεν ασχολούμαστε μαζί του καθόλου.

  • Υπάρχει πρόβλημα στην παρακάτω περίπτωση;

    f = 5; 
    f.x = 1;
    

    Σε αυτή τη φάση αυτό δε χρειάζεται να πετάξει λάθος.Στη γενική περίπτωση όμως, δε μπορούμε να το ξέρουμε αυτό στατικά αλλά μόνο κατά την εκτέλεση, οπότε η πιο απλή πρακτική είναι να το αφήσουμε ως έχει κατά το compilation και να διαγνώσουμε τέτοια τυχόν λάθη κατά την εκτέλεση.

  • [for loop]Είναι το παρακάτω έγκυρο?

    for(;;)
    

    Εφόσον για το for έχουμε τον κανόνα forstmt -> for (elist; expr; elist) stmt ουσιαστικά η ερώτηση ανάγεται στο αν επιτρέπεται κάποιο από τα elist και expr να είναι κενά. Βλέποντας και πάλι τη γραμματική, το expr δε μπορεί να είναι κενό συνεπώς το παράδειγμά δεν είναι συντακτικά ορθό.

  • [Πίνακες]Είναι τα παρακάτω ισοδύναμα?

    1)t = [{x:a},{y:b}] ; 
    2)t["x"]=a;
      t["y"]=b; 
    

    Δεν είναι ισοδύναμα. Τα x, y που έχεις στο πρώτο είναι μεταβλητές και όχι strings αρα δεν έχουν απαραίτητα τις τιμές "x" και "y" αντίστοιχα. Περίπου ισοδύναμα θα ήταν αν το πρώτο παράδειγμά σου ήταν: t = [ {"x" : a}, {"y": b} ]; Με αυτό το δεδομένο και στα δύο παραδείγματα θα μπούν μόνο τα t, a, b στο SymbolTable, όπως ακριβώς πρέπει. Ο λόγος που παραπάνω είπα "περίπου" ισοδύναμα, που έτσι κι αλλιώς δε μας ενδιαφέρει σε αυτή τη φάση, είναι οτι ακόμα και με την αλλαγμένη έκδοση, τα δύο κομμάτια κώδικα έχουν διαφορετική σημασιολογία. Το πρώτο (t = [...]) δημιουργεί ένα νέο δυναμικό πίνακα και τον εκχωρεί στη μεταβλητή t, ενώ το δεύτερο υποθέτει οτι η μεταβλητή t έχει τιμή πίνακα και του αναθέτει τιμές για κάποια indices. Σχετικά με τη χρήση πινάκων, οι δύο μορφές που είναι ισοδύναμες είναι οι t["x"] = a; και t.x = a; Στη δεύτερη περίπτωση το id x δε μας ενδιαφέρει και δεν μπαίνει στο SymbolTable, οπότε τα δύο παραπάνω αντιμετωπίζονται ακριβώς με τον ίδιο τρόπο.

  • [Funcdef] Αποθήκευση function χωρίς όνομα

    Στην περίπτωση που δηλώνουν τι συνάρτηση χωρίς όνομα, μας ενδιαφέρει να κρατάμε τις παραμέτρους τις ή απλά τις αγνοούμε (αφού δεν έχουμε τρόπο να την καλέσουμε υποθετικά) πχ function(a,b,c){}

    Οι συναρτήσεις χωρίς όνομα χρειάζονται τον ίδιο χειρισμό με τις συναρτήσεις που έχουν όνομα απλά θα πρέπει να τους δίνετε εσείς όνομα όπως αναφέρεται στο 3ο φροντ.

    Ένας από τους τρόπους που μπορεί να γίνει κλήση της συνάρτησης για την γλώσσα alpha ως εξής:

    example = function (a,b) { return a + b; }
    
    example (3, 4);
    

    Ένας άλλος τρόπος είναι:

    example2 = ( function (a,b) { return a + b; } ) (3, 4);
    

Απορίες πάνω στα break, continue, return

  • Μας ενδιαφέρει σε αυτην την φάση να τηρούνται τα break, continue μέσα σε επανάληψη και το return μέσα σε συνάρτηση?

    Θα τα δούμε στην επόμενη φάση, αν θέλετε πάντως μπορείτε να τα φτιάξετε και από τώρα, δεν υπάρχει πρόβλημα.

Τι ισχύει πάνω στα conflicts

Θα πρέπει να κάνετε όλες τις απαραίτητες αλλαγές στην γραμματική ώστε να μην έχετε conflicts (με εξαίρεση το conflict του ifelse) και δεν θα χαλάει την περιγραφή της γραμματικής της γλώσσας alpha που σας ζητάτε στην εκφώνηση.

Απορίες πάνω στη γραμματικής της γλώσσας alpha

  • [If statement] Κενό body segment

    Στην περίπτωση που έχουμε if(statement) semicolon , η έκφραση είναι αποδεκτη?

    πχ: if(5) ;

    Την απάντηση σε οποιαδήποτε τέτοιου είδους απορία την απάντηση μας την δίνει η γραμματική που περιγράφεται στην εκφώνηση.

    Συγκεκριμένα για το παράδειγμα που αναφέρεις αντιστοιχεί στον κανόνα του if:

    ifstmt -> if (expr ) stmt
    

    οπότε το θέμα είναι αν το 5 μπορεί να αναχθεί σε expr και το ; σε stmt,

    5 -> NUMBER -> const -> primary -> term -> expr   ΟΚ
    
    stmt -> ;   ΟΚ
    

    Οπότε ναι η έκφραση είναι αποδεκτή.

Στη σημασιολογία της γλώσσας αναφέρεται ένας reference counter για τα αντικείμενα τύπου πίνακα. Πρέπει να υλοποιηθεί σε αυτήν τη φάση;

Όχι. Δεν είναι δυνατόν να ξέρουμε το reference count ενός πίνακα at compile time. Αυτό θα γίνει at run-time.

Γινεται να γνωρίζω at compile time το αν ένας πίνακας είναι null?

Όχι. Δεν είναι δυνατόν να ξέρουμε at compile time αν ένας πίνακας είναι null. Αυτό θα γίνει at run-time.

Στην Alpha επιτρέπεται ή απαγορεύεται η έκφραση x % y, όταν το x ή y δεν είναι ακέραιος αριθμός; Πώς μπορούμε να υλοποιήσουμε την πράξη %, αφού ο gcc δεν μπορεί να εφαρμόσει τον τελεστή % σε doubles;

Το specification της γλώσσας λέει ότι ο τελεστής %, μπορεί να εφαρμοστεί σε αντικείμενα τύπου number. Επομένως θα μπορεί ο τελευταίος να εφαρμόζεται και σε μη ακέραιους αριθμούς, αρκεί αυτοί να μετατρέπονται πρώτα σε ακέραιους. Έτσι, για να υπολογιστεί το αποτέλεσμα της πράξης r = x % y, θα πρέπει να μετατραπούν πρώτα οι x, y σε ακέραιους και το τελικό αποτέλεσμα σε double, δηλαδή:

double r = ((int) x) % ((int) y);

Πού και πώς χρησιμοποιείται το πεδίο label του struct quad;

Στο πεδίο label του struct quad, αποθηκεύουμε μόνο το index (στον πίνακα των quads), στο οποίο θα κάνει (ενδεχομένως) jump μία εντολή διακλάδωσης. Σε όλες τις άλλου τύπου εντολές το πεδίο index δεν χρησιμοποιείται. Φυσικά το index μιας συγκεκριμένης εντολής ενδιάμεσου κώδικα, ορίζεται εμμέσως από την θέση της στον πίνακα των quads.

Γιατί υπάρχει ο pointer expr* next στο struct expr;

Ο pointer expr* next, υπάρχει στο struct expr, ώστε να μπορούμε να φτιάχνουμε άμεσα απλές λίστες από αντικείμενα τύπου expr. Αυτό είναι χρήσιμο γιατί μπορούμε να χρησιμοποιήσουμε τον ίδιο τύπο (expr) και για το μη τερματικό σύμβολο elist (expression list) που εμφανίζεται και χρησιμοποιείται στους κανόνες call και tablemake.

Γιατί χρειάζεται στον κανόνα του assignexpr (l-value '=' expr) και στους κανόνες προθεματικής αύξησης και μείωσης ενός l-value (++l-value, --l-value) να προστεθεί μία επιπλέον εντολή emit στο τέλος;

Αυτή η επιπλέον εντολή ανάθεσης τιμής στους παραπάνω κανόνες, ενώ Δεν φαίνεται εκ πρώτης όψεως να συμβάλλει στην λειτουργικότητα του προγράμματος, είναι απαραίτητη για την διατήρηση της ορθής σημασιολογίας της γλώσσας σε περίπτωση που ο προγραμματιστής χρησιμοποιήσει το ίδιο l-value, αλλάζοντάς το, σε δύο ή περισσότερα σημεία ενός expression list. πχ.

i = 0;
t = [ ++i, i = 1821, --i, i = 8 ];

έτσι ο πίνακας t, για να είναι “σωστός”, σύμφωνα πάντα με την σημασιολογία της γλώσσας, θα πρέπει να είναι ισοδύναμος με τον:

t_correct = [ 1, 1821, 1820, 8 ];

αφού σύμφωνα με τη γλώσσα, τα expression lists γίνονται evaluate από τα αριστερά προς τα δεξιά. Είναι προφανές ότι εάν Δεν υπήρχε αυτή η τελευταία εντολή ανάθεσης τιμής στους παραπάνω γραμματικούς κανόνες, ο ενδιάμεσος κώδικας που θα παραγόταν, θα ανάγκαζε τον t να αρχικοποιηθεί τελικά με τις τιμές:

t_wrong = [ 8, 8, 8, 8 ];

αφού, σύμφωνα με την syntax-directed παραγωγή ενδιάμεσου κώδικα, πρώτα γίνεται η αποτίμηση όλων των εκφράσεων στο expression list και μετά γίνεται η ανάθεσή τους με tablesetelem.

Η παραπάνω εντολή που γίνεται emit, διορθώνει το “πρόβλημα” της left-to-right αποτίμησης των expression lists για assignments και increments / decrements. Όμως τι γίνεται όταν ένας πίνακας αρχικοποιηθεί ως t = [ ++i, i, i = 8 ] ;

Όντος σε αυτήν την περίπτωση το δεύτερο στοιχείο του πίνακα θα αρχικοποιηθεί στην τιμή “8”, που σίγουρα δεν είναι κάτι που θα περίμενε ο προγραμματιστής σε αυτήν την περίπτωση. Όμως, θα μπορούσε κάποιος να διαφωνήσει σχετικά με το εάν σε ένα l-value που εμφανίζεται στο initialization list ενός πίνακα (ή των παραμέτρων μιας συνάρτησης) θα πρέπει να αποδίδεται η τελική του τιμή μετά από ολόκληρη την αποτίμηση των εκφράσεων ή η τιμή που έχει εκείνη τη στιγμή. Έτσι, δεδομένης της συγκεκριμένης ασάφειας και δεδομένου ότι είναι πιο “καθαρή” η λύση της ανάθεσης της τιμής ενός l-value όταν αποτιμηθούν όλες οι εκφράσεις που το περιέχουν, είναι αποδεκτές και οι δύο προσεγγίσεις στην υλοποίηση της γλώσσας.

Εκχώρηση τιμής στο $$. (Error: illegal cast από expr* σε char*)

Πρόβλημα: Δημιουργία expr από το struct expr και επιστρέφει ενός pointer σε αυτό. Στον parser, όταν γίνεται η εκχώρηση της τιμής του στο $$ πετάει illegal cast από expr σε char.

Λύση: Στο μη τερματικό κανόνα που είναι το $$, θα πρέπει να αλλάξετε απάνω τη δήλωση και αντί για strValue να έχει exprValue για αυτό το λόγο πετάει και το error.

Που αναφέρεται το πεδίο symbol* sym του struct exp:

Αναφέρεται στο SymbolTableEntry του SymTable.

Σύνταξη ASSIGN

Ερώτηση: σε πιο στοιχειο εκχωρείται η τιμή?

στο ASSIGN _t1 TRUE η τιμή TRUE περνιέται στο _t1 (Εκχωρεί στο πρώτο στοιχείο)

sto ASSIGN _t1 a η τιμή του _t1 περνιέται στο a(Εκχωρεί στο δεύτερο στοιχείο)

το κάθε opcode έχει μια σύνταξη και δεν μπορεί να συμβαίνει αυτό που έχει το παράδειγμα.

Απάντηση: Ναι είναι λάθος στη διαφάνεια το ένα από τα 2, εξαρτάται από τη τακτική την οποια θα ακολουθήσετε εσείς. Αν είναι με τη σειρά opcode result arg1 arg2, τότε είναι λάθος το δεύτερο assign, αν είναι με τη σειρά opcode arg1 arg2 result. Είναι ok να ακολουθήσετε μια από τις 2, αρκεί να είναι η ίδια σε όλες τις περιπτώσεις.

Από όσο θυμάμαι, στις διαλέξεις δεν είναι μονο εκεί με ανάποδη σειρά τα args-results των quads, πρέπει για πχ να έχει και στα tables. Σε κάθε περίπτωση να είστε προσεκτικοί ώστε η σειρά να είναι συνεπής στα quads τα οποια θα παράγεται.

Offset για Τemporary Μεταβλητές

Σε όποιο scopespace και να είστε (είτε μέσα σε συνάρτηση είτε σε global scope) οι tmp μεταβλητές μετράνε κανονικά στα offset.

Offset για Formal arguments

Παράδειγμα:

function f (a,b,c) { // τα offsets για τα formal args είναι 0,1,2
    function l (d,e,f) { // τα offsets για τα formal args είναι 0,1,2 αντίστοιχα

    }
    function (h, i, j) { // τα offsets για τα formal args είναι 0,1,2 αντίστοιχα
    }
}

Τα offsets για τα formal args είναι ίδια, αλλα αυτό είναι ok, διότι τα args είναι ξεχωριστά για τη κάθε συνάρτηση.

OFFSET και Scopespace

Παράδειγμα:

Το offset για τα program variables ΔΕΝ γίνεται reset. Το scopespace αλλάζει όταν ορίζεται νέα συνάρτηση και γίνεται reset το offset για τα formal args και τα local args. To offset για κάθε μεταβλητή βρίσκεται στα σχόλια

a;  //0
b;  //1
c;  //2
{
    d; //3
    e; //4
    {
        f;  //5
        g; //6
    }
    h; //7
    i;  //8
    while(true){
        j; //9
        k;//10    
    }
    l; //11
    for(m=0;n<2;o++){  //m=12 (_t0 = 13) n=14 (και άλλα temp)...
        p;  //15
        q;  //16
        if (r==false){  //17
            s;  //18
            t;  //19
        }
    }
    u; //20
    v;  //21

    function f(w,x,y){ //w=0 x=1 y=2
        z; //0
        while(true){
            aa; //1
    }

}
ab; //22

Εντολή: $funcprefix.iaddress=nextquadlabel();

(Στη διάλεξη 10 slide 5)

Η εντολή αυτή χρειάζεται για να κρατάμε τη διεύθυνση της κάθε συνάρτησης που ορίζεται, δηλαδή τον αριθμό στον οποιο antitoixei το quad της funcstart.

Ο λόγος που γίνεται αυτό είναι διότι, στη επομενη φάση (phase 4-5) στην στην εικονική μηχανή όταν θα θέλουμε να κάνουμε κλήση της κάθε συνάρτησης θα πρέπει να γνωρίζουμε σε ποια εντολή να πάμε έτσι ώστε να καλέσουμε τη συνάρτηση.

QUADS για ASSIGN

Σε κάθε assign πρέπει να παράγεται επιπλέον ένα assign σε tmp μεταβλητή, πχ. για το x = 1; θα παραχθούν τα quads:

assign 1 x

assign x _t0

Γιατι παράγονται 2 assign quads

Το 2o assign έχει χρησιμότητα σε περιπτώσεις όπως για παράδειγμα

f (x=1, x=2);

όπου αν απουσίαζε απο τα quads το 2ο assign τότε θα είχαμε ως αποτέλεσμα να περνάει σαν τιμές το f(2,2) αντί του f(1,2).

QUADS για table

Για το -> t.a.b = c.d.e=f.g.h παράγουμε:

0: TABLEGETELEM   t "a" _t0
1: TABLEGETELEM   c "d" _t1
2: TABLEGETELEM   f "g" _t2
3: TABLEGETELEM   _t2 "h" _t3
4: TABLESETELEM   _t1 "e" _t3
5: TABLEGETELEM   _t1 "e" _t4
6: TABLESETELEM   _t0 "b" _t4
7: TABLEGETELEM   _t0 "b" _t5

Περίπτωση στην οποία το lvalue είναι tableitem (διαλεξη 10 slide 22):

Στην περιπτωση που το lvalue ειναι tableitem η παραγωγη του ενδιαμεσου κωδικα δεν γινεται στην αναγωγη του lvalue. Πρεπει να "περιμενουμε" λιγο την αναλυση ωστε να δουμε με ποιο τροπο χρησιμοποιειτε το tableitem. Μια απο τις περιπτωσεις (slide 20-22 διάλεξη 10) ειναι το tableitem να χρησιμοποιειται ως rvalue κατι που το γνωρίζουμε μονο οταν φτασουμε στον κανονα primary<-lvalue.

QUADS για function

Για το -> function foo (a,b) { }

funcstart foo
funcend foo

Για το -> f(1,2);

PARAM 2
PARAM 1
CALL f

Για το -> f(a(1), b(2, 3));

0: PARAM 1 // Η παράμετρος της συνάρτησης a().
1: CALL a
2: GETRETVAL _r0
3: PARAM 3
4: PARAM 2
5: CALL b
6: GETRETVAL _r1
7: PARAM _r1
8: PARAM _r0
9: CALL f
10:GETRETVAL _r2

QUADS για κλήση συνάρτησης με τελεία

Παράδειγμα

test().x = 1;

print(y.z.x, "-");

QUADS:

Για το -> test().x = 1; τα quads είναι:

1: call test

2: getretval _t0

3: tablesetelem _t0 "x" 1

Για το -> print (y.z.x, "-"); τα quads είναι:

4: tablegetelem    y   "z" _t1

5: tablegetelem _t1   "x" _t2

6: param "-"

7: param _t2

8: call print

9: getreval _t3

QUADS για if_greater

Για το παράδειγμα:

if (1>2)
   x = 1;

θα παράχθούν τα εξής quads:

  1. if_greater 1 2 4 // first 4 quads evaluate logic expr 1>2 2 assign false _t1
  2. jump 5
  3. assign true _t1
  4. if_eq true _t1 7 // based on the result of logic expr (i.e. _t1), exec or not x = 1
  5. jump 9
  6. assign 1 x
  7. assign x _t2 9.

QUADS για while

Για το παράδειγμα:

while(1) { 
   x = 1; 
}

θα παράχθούν τα εξής quads:

  1. IF_EQ 1.000 true 2
  2. JUMP 5
  3. ASSIGN 1.000 x
  4. ASSIGN x _t0
  5. JUMP 0

Undefined Variables

Undefined ειναι οι μεταβλητές στις οποίες δεν εχουν γινει defined ο τυπος και η τιμη τους.

x=2; // x -> type: Number, value:2
x=y; // y δημιουργειτε αλλα δεν εχει ανατεθει σε αυτον τυπος και τιμη

Το αποτελεσμα ειναι τα x, y να ειναι undefined

Προσοχή: Πρόκειται για την σημασιολογία της γλώσσας και αφορά το runtime(i.e. Virtual Machine) και όχι το compile time!

Μερική Αποτίμηση Έκφρασης

Τα επιπλέον quads που θα πρέπει να παραχθούν φαίνεται από το φροντιστήριο στο slide 39 και 40 απλά είναι λίγο κρυμμένο :) Στην ουσία αν δείτε προσεκτικά για παράδειγμα στο slide 39:

Για το x = a < b or c<d and e < f;

Τα quads που παράγονται μέχρι και το 6 είναι αυτά που παράγονται για τα a<b, c<d και e<f. Την στιγμή που παράγονται τα labels μένουν incomplete..

Όταν βρισκόμαστε τον κανόνα and και or αντίστοιχα εκεί έχουμε την γνώση και κάνουμε backpatch για την falselist (όταν είμαστε στην or) και για την truelist όταν είμαστε στη and, τα υπόλοιπα μένουν incomplete και απλά τα περνάμε στους πιο "πάνω" κανόνες (slide 37). Στην συνεχεια για το συγκεκριμένο πχ πιο πάνω κανόνας είναι το assignexpr.

Εκεί θα πρέπει να παραχθούν τα quads 7-9, όπου αυτά είναι και τα extra quads που θα πρέπει να παραχθούν για τη μερική αποτίμηση.

Επομένως τα quads που θα πρέπει να παράγεται είναι:

label  QUAD  
N    : assign TRUE  _t0
N+1  : jump    N+3
N+2  : assign FALSE _t0

!!! Εκεί έχετε τη γνώση για τα labels και εκεί είναι είναι που θα γίνει και το backpatch για τις truelist - falselist Η truelist θα γίνει backpatch με το label Η και η falselist θα γίνει backpatch με το label Η+2.

Για το συγκεκριμένο παράδειγμα στο slide 39, η truelist θα γίνει backpatch με το label 7 και η falselist θα γίνει backpatch με το label 9.

Η περίπτωση της assignexpr είναι ένα παράδειγμα κανόνα που χρειάζεται αλλαγή.

Hints - Βήματα για την υλοποίηση της μερικής αποτίμησης:

  1. Αλλαγή των γραμματικών κανόνων and, or (βλέπε slide 37)
    • Backpatch των falselist(για το or) και των truelist(για το and)
    • Merge των falselist(για το and) και truelist(για το or)
    • Πέρασμα των λιστών που δεν έγιναν backpatched (i.e. Expr2.truelist, Expr2.falselist) στο Expr (i.e. $$) ώστε να διατηρήσουμε την πληροφορία των incomplete labels έως ότου έχουμε την γνώση για το καθένα ώστε να γίνει το backpatch.
  2. Αλλαγή του γραμματικού κανόνα not (βλέπε slide 37)
    • Απλά αντιστρέφουμε τις λίστες truelist και falselist
  3. Αλλαγή των γραμματικών κανόνων RELOP (i.e. >,>=, etc.)
    • Παραγωγή των 2 quads που θα έχουν incomplete labels
      N    :  IF_RELOP   id1   id2   _
      N+1:  JUMP   _
      
    • Κρατάμε πληροφορία για τα incomplete labels των quads που μόλις δημιουργήσαμε (i.e. δημιουργούμε μια truelist με ένα στοιχείο τον αριθμό Ν, falselist με ένα στοιχείο τον αριθμό Ν+1)
  4. Προσθήκη επιπλέον quads (σε όποιον γραμματικό κανόνα χρειάζεται π.χ. στο assignexpr βλέπε παράδειγμα φροντ. 4 slide 39)
    N  :     ASSIGN  TRUE _t0   // κάνουμε backpatch την truelist, i.e. backpatch (truelist, N)
    N+1:     JUMP      N+3
    N+2:     ASSIGN  FALSE _t0   // κάνουμε backpatch την falselist, i.e.  backpatch (falselist, N+2)
    
  5. Προσθήκη επιπλέον quads όταν τα operands της λογικής έκφρασης δεν είναι Boolean (βλέπε φροντ. 4, slide) Π.χ. για την έκφραση x = a and b; Το a, b θα πρέπει να αναχθούν σε Boolean. Για να το καταφέρουμε αυτό θα πρέπει να προσθέσουμε 2 επιπλέον quads. Για το a:
    IF_EQ   a   TRUE   _
    JUMP   _
    

    Η προσθήκη των quads γίνεται όταν έχουμε την γνώση ότι βρισκόμαστε σε λογική έκφραση (i.e. and, or, not), όπου εξετάζουμε τα expr1, expr2 τι τύπου είναι και αν δεν πρόκειται για boolean operand τότε θα κάνουμε emit τα 2 αυτά quads. Προφανώς, από τη στιγμή που έχουμε 2 incomplete labels θα πρέπει να τα εισάγουμε στις λίστες των falselist και truelist αντίστοιχα.

Τύποι methodcall, normcall και callsuffix

Ο τύπος των methodcall, normcall και callsuffix δεν ορίζεται με κάποιο από τα υπάρχοντα struct που βρίσκονται στις διαλέξεις. Αντιθέτως, θα πρέπει να ορίσεται εκ νέου κάποιο struct..

Χειρισμός/μερική αποτίμηση έκφρασης not a < b

Καταρχήν μια τέτοια έκφραση για την σημασιολογία της Aplha είναι λάθος. Το not a θα έχει αποτέλεσμα bool ενώ επιτρέπονται μόνο arithexpressions. Μπορείτε προαιρετικά να το πετάτε ως error compile time, αλλιώς θα χτυπάει ως run-time error.

Τώρα για τα quads που θα παραχθούν έχει να κάνει με τη προτεραιότητα που έχουμε ορίσει από τη 2η φάση. Το not έχει πιο υψηλή προτεραιότητα από το < επομένως τα quads που παραχθούν είναι:

0: IF_EQ a true  4
1: JUMP    2
2: ASSIGN true  _t1
3: JUMP    5
4: ASSIGN false  _t1
5: IF_GREATER _t1 b  7
6: JUMP    9
7: ASSIGN true  _t2
8: JUMP    10
9: ASSIGN false  _t2

Η συσχέτιση των params (στην κλήση συνάρτησης) με τα formal args της συνάρτησης

Ερώτηση

Εστω το σενάριο

function foo(b,a){
    k=2*a + 3*b;
    return k;
}

foo(y,x);

τα quads που παραγονται ειναι

funcstart foo
//ενδιαμεσα quads εκφρασης της k=2*a + 3*b; και return
funcend foo 

param x
param y
call foo
getretval _t0

πως ξέρουμε εμείς μετά στο virtual machine να αντικαταστησουμε το b με y και το a με x εφοσων δεν γραφουμε σε κανένα quad αυτες τις 2(a,b) μεταβλητες.

Απάντηση

Έχει να κάνει με τα offsets που έχει κάθε σύμβολο. Το a,b που αναφέρεσαι σαν ονόματα δεν λένε τίποτα στην εικονική μηχανή. Η μηχανή γνωρίζει τα offsets του καθενός και τα ψάχνει μέσα στην στοίβα.

Η ανάθεση ενός πίνακα με μία τυχαία μεταβλητή, επηρεάζει το reference count του, έτσι υπάρχει περίπτωση ο πίνακας να μην καταστρέφεται όταν ο χρήστης θέσει όλα τα references του με nil;

Όντος, αυτό ισχύει και μάλιστα όταν ο πίνακας βρίσκεται στο global scope, υπάρχει περίπτωση να κρατηθεί ζωντανός σε όλη τη ζωή του προγράμματος (στο τέλος του προγράμματος θα γίνει σίγουρα collect) ακόμα κι αν ο χρήστης του θέσει ρητά την τιμή nil. Όμως αυτή η συμπεριφορά δεν αλλάζει καθόλου τη σημασιολογία του reference counting των πινάκων, αφού έτσι κι αλλιώς ο χρήστης (προγραμματιστής της γλώσσας), δεν μπορεί να βασίσει την ορθότητα του προγράμματος του στην γνώση καταστροφής ενός πίνακα σε συγκεκριμένη χρονική στιγμή. Έτσι αυτή η συμπεριφορά, είναι απόλυτα αποδεκτή. Ένας “νεκρός” -για τον χρήστη- πίνακας μπορεί να μην καταστραφεί από το runtime σύστημα άμεσα, όμως δεδομένου ότι

  • Υπάρχει μία υποτυπώδης επαναχρησιμοποίηση των προσωρινών μεταβλητών
  • Οι μεταβλητές που έχουν οριστεί μέσα στο σώμα μίας συνάρτησης μετά την κλήση της είναι σίγουρο ότι πεθαίνουν
  • Ένα απλό πέρασμα βελτιστοποίησης (optimization pass), μπορεί πολύ εύκολα να εντοπίσει και να απαλείψει “νεκρές” μεταβλητές είναι σίγουρο ότι σε γενικές γραμμές το ποσοστό της άχρηστης μνήμης που θα παραμένει δεσμευμένη ανά πάσα στιγμή θα είναι πολύ μικρό.

Αν έχει γίνει υλοποίηση μερικής αποτίμησης τι ισχύει με τις περιττές generate_and/or/not;

Kάποιες από τις συναρτήσεις δεν χρειάζονται εξαρτάται από το ρεπερτόριο των εντολών που έχετε ορίσει. Το ρεπερτόριο των εντολών θα πρέπει να είναι ένα προς ένα με των πίνακα των function addresses των generate για να δουλεύει σωστά o dispatcher, αυτός είναι και ο λόγος που φαίνονται τα generate functions. Επομένως, ανάλογα με την υλοποίηση σας θα πρέπει να κάνετε κατάλληλες τροποποιήσεις.

Στον τελικό κώδικα πρέπει να αποθηκευουμε τους πίνακες στους οποιους θα περιεχονται τα ονομα των μεταβλητων και οι σταθερες καθως και τα formals;

Ο τελικός κώδικας αναφέρεται στους πίνακες σταθερών τιμών. ΔΕΝ περιέχονται ονόματα μεταβλητών μέσα στους πίνακες. Οι πίνακες είναι strings, numbers, userfunctions και libfunctions που συναντήθηκαν μέσα στο alpha πρόγραμμα που γίνεται compile.

Τι θα πρέπει να περιέχει το binary file για το VM και πως το κατασκευάζουμε; Είναι υποχρεωτική η δημιουργία του;

Στο binary file καταγράφετε τους πίνακες και τα instruction (τελικός κώδικας) που αντιστοιχούν στο πρόγραμμα που γίνεται compile. Η VM κάνει load το binary file με τους ΄πίνακες και τα instructions και τα εκτελεί.

Επίσης είστε ελεύθεροι να περνάτε την πληροφορία με το τρόπο που θέλετε, δεν είναι υποχρεωτική η μορφή που σας δόθηκε απλά είναι προτεινόμενη και να έχετε ότι κατάληξη θέλετε στο binary file.

Δεν χρειάζεται να μεταφράσετε σε 0 και 1 τις πληροφορίες. Δείτε πως γράφουμε-διαβάζουμε binary files σε C/C++. Σε περίπτωση που δυσκολεύεστε είναι οκ να κάνετε ένα απλό read και write. Σε κάθε περίπτωση έτσι και αλλιώς για λόγους debug βολεύει να τα γράφετε σε αρχεία στην αρχή ώστε να δείτε ότι είναι αυτά που θέλετε να γράψετε.

Η κατασκευή ενός binary file για να προχωρήσετε στην 5η φάση είναι υποχρεωτική.

Στον τελικο κωδικα πως χειριζομαστε την Funcstart;

Χρειάζεται να προσθέσετε μια επιπλέον εντολή jump στην αρχή της Funcstart έτσι ώστε να κάνει skip τον τελικό κώδικά που αντιστοιχεί σε κάθε συνάρτηση.

Μετατροπές ενδιάμεσου σε τελικό κώδικα

funcstart:

jump labelNextFuncend
funcenter

return result:

assign result
jump labelFuncend

and, or, not:

Βλέπε διάλεξη 14 διαφάνειες 19-21

uminus:

mul -1

Αντιμετώπιση ορισμάτων που δεν υποστηρίζονται

Σε οποιοδήποτε περίπτωση έχετε ορίσματα που δεν υποστηρίζονται βγάζετε runtime error με κατάλληλο διαγνωστικό μήνυμα.

Για τα strings συγκεκριμένα, θα είχε νόημα οι τελεστές σύγκρισης να κάνουν λεξικογραφική σύγκριση και το συν string concatenation, αλλά δεν είναι υποχρεωτικό.

Τι γίνεται όταν ο χρήστης καλέσει μία συνάρτηση με περισσότερες παραμέτρους από όσες έχει ορίσει στην υλοποίησή της;

Οι επιπλέον παράμετροι που περνάει ο χρήστης, απλώς αγνοούνται. Το runtime σύστημα, αντιστοιχίζει πάντα τις πραγματικές παραμέτρους της συνάρτησης στις αντίστοιχες πρώτες παραμέτρους που περνάει ο χρήστης και έτσι οι υπόλοιπες αγνοούνται με ασφάλεια.

Τι γίνεται όταν ο χρήστης καλέσει μία συνάρτηση με λιγότερες παραμέτρους από όσες έχει ορίσει στην υλοποίησή της;

Τότε υπάρχει πρόβλημα. Δεδομένου του τρόπου αντιστοίχησης, από το runtime σύστημα, των πραγματικών παραμέτρων μιας συνάρτησης με τις παραμέτρους που περνάει ο χρήστης, υπάρχει το ενδεχόμενο είτε μία παράμετρος να περιέχει "σκουπίδια", είτε να γίνει corrupt το runtime stack εάν ο χρήστης προσπαθήσει να αναθέσει τιμή σε μία "ανύπαρκτη" μεταβλητή. Όμως προκειμένου, αφενός να είναι πιο εύκολη η υλοποίηση και αφετέρου να είναι πιο γρήγορη η λειτουργία της εικονικής μηχανής, η υλοποίηση της γλώσσας δεν χρειάζεται να "προστατεύει" τον χρήστη (προγραμματιστή της γλώσσας) από αυτήν την "παγίδα".

Ποιες είναι οι συναρτήσεις βιβλιοθήκης και ποια η συμπεριφορά της κάθε μίας;

Οι συναρτήσεις βιβλιοθήκης που θα πρέπει να υλοποιηθούν, είναι οι εξής:

  • print – Παίρνει οποιονδήποτε αριθμό παραμέτρων και τους τυπώνει με τη σειρά (από τα αριστερά προς τα δεξιά) στην έξοδο του τερματικού. Η συνάρτηση αυτή, θα πρέπει να αναγνωρίζει και να εκτυπώνει τους εξής τύπους:
    • Boolean – θα εκτυπώνεται true ή false ανάλογα με την τιμή του ορίσματος
    • Number – θα εκτυπώνει τον αριθμό με τα 3 σημαντικότερα δεκαδικά του ψηφία
    • String – θα εκτυπώνει το αλφαριθμητικό αναλλοίωτο
    • nil – θα εκτυπώνει nil
    • User Function – θα εκτυπώνει την ένδειξη "user function", μαζί με τη διεύθυνση της συνάρτησης
    • Library Function – θα εκτυπώνει την ένδειξη "library function", μαζί με το όνομα της συνάρτησης
    • Table – θα εκτυπώνει όλα τα στοιχεία της δομής, περικλείοντάς τα με τα σύμβολα '[' και ']' παράδειγμα:
        value = false
        foo = nil
        t = [ { "foo" : 32 }, { "lala" : [ "inner", value, foo, 3.14 ] } ];
        print("the contents of t are - ", t);
        >> the contents of t are - [ { foo : 32 }, { lala : [ inner, false, nil, 3.140 ] }
      
  • input – διαβάζει από την προκαθορισμένη είσοδο μία ακολουθία συμβόλων και επιστρέφει:
    • Ένα number (πραγματικό αριθμό) που αναπαριστάται από την ακολουθία χαρακτήρων που δίνει ο χρήστης (στο δεκαδικό σύστημα), εάν αυτή η ακολουθία μπορεί να να μετατραπεί σε αριθμό
    • Ένα boolean με την τιμή true εάν η είσοδος περιέχει την ακολουθία χαρακτήρων true και false εάν η είσοδος περιέχει την ακολουθία χαρακτήρων false
    • Την τιμή nil εάν η είσοδος περιέχει την ακολουθία χαρακτήρων nil
    • Ένα string που περιέχει την ακολουθία χαρακτήρων εισόδου αναλλοίωτη, εάν είτε αυτή η ακολουθία περικλείεται ανάμεσα σε εισαγωγικά ("), είτε η ακολουθία αυτή ΔΕΝ ικανοποιεί τις προδιαγραφές των παραπάνω κανόνων για επιστροφή ενός άλλου τύπου παράδειγμα:
      a = input();
      << 3.1415
      >> 3.1415 // number
      << "3.1415"
      >> "3.1415" // string
      << 4.4a
      >> "4.4a" // string
      << true
      >> true // boolean
      
  • objectmemberkeys – παίρνει ως παράμετρο έναν πίνακα, και επιστρέφει έναν καινούριο integer-indexed πίνακα με όλα τα κλειδιά του πίνακα παράδειγμα:
      t = [ { "foo" : 34 }, { "lala" : [ 3, 4, 5 ] } ];
      objectmemberkeys(t);
      >> [ "foo", "lala" ]
    
  • objecttotalmembers – παίρνει ως παράμετρο έναν πίνακα και επιστρέφει τον αριθμό των στοιχείων που περιέχει παράδειγμα:
      t = [ 1, 2, 3, 4, 5 ];
      objecttotalmembers(t);
      >> 5
    
  • objectcopy – παίρνει ως παράμετρο έναν πίνακα, φτιάχνει ένα ρηχό αντίγραφο του και το επιστρέφει. Το ότι το αντίγραφο που φτιάχνει ο πίνακας είναι ρηχό, σημαίνει πρακτικά ότι εάν κάποιο στοιχείο του πίνακα που δίνουμε ως παράμετρο είναι και αυτό πίνακας, τότε το αντίστοιχο στοιχείο του αντιγράφου θα είναι μία αναφορά σε αυτόν τον πίνακα και όχι αντίγραφό του
  • totalarguments – επιστρέφει τον αριθμό των παραμέτρων που έχει δώσει ο χρήστης στην τρέχουσα συνάρτηση. Εάν η συνάρτηση αυτή κληθεί σε global scope, επιστρέφει nil
  • argument – παίρνει ως παράμετρο έναν ακέραιο i και επιστρέφει την i-οστή παράμετρο της τρέχουσας συνάρτησης. Εάν η συνάρτηση αυτή κληθεί σε global scope, επιστρέφει nil. Η αρίθμηση των παραμέτρων, ξεκινάει από το μηδέν και έτσι η πρώτη παράμετρος της συνάρτησης μπορεί να ανακτηθεί με την κλήση argument(0), ενώ η τελευταία παράμετρος μπορεί να ανακτηθεί με την κλήση argument(totalarguments()-1)
  • typeof – παίρνει ως παράμετρο μία οποιαδήποτε μεταβλητή ή σταθερά και επιστρέφει τον τύπο της ως string. Έτσι τα δυνατά strings που μπορεί να επιστρέψει είναι: "boolean", "number", "string", "nil", "userfunction", "libraryfunction", "table"
  • strtonum – παίρνει ως παράμετρο ένα αλφαριθμητικό (string) και επιστρέφει έναν πραγματικό αριθμό (number) που αναπαριστάται από το συγκεκριμένο αλφαριθμητικό. Εάν το αλφαριθμητικό δεν μπορεί να μετατραπεί σε αριθμό, η συνάρτηση αυτή επιστρέφει nil παράδειγμα:
      strtonum("1821.22");
      >> 1821.220
      strtonum("foo");
      >> nil
    
  • sqrt – παίρνει ως παράμετρο έναν πραγματικό αριθμό και επιστρέφει την τετραγωνική του ρίζα. Εάν η τετραγωνική ρίζα του δοσμένου αριθμού δεν ορίζεται, επιστρέφει nil παράδειγμα:
      sqrt(-1);
      >> nil
    
  • cos – παίρνει ως παράμετρο έναν πραγματικό αριθμό που συμβολίζει μία γωνία σε μοίρες και επιστρέφει το συνημίτονο της γωνίας που αναπαριστά ο αριθμός παράδειγμα:
      cos(90);
      >> 0
    
  • sin – παίρνει ως παράμετρο έναν πραγματικό αριθμό που συμβολίζει μία γωνία σε μοίρες και επιστρέφει το ημίτονο της γωνίας που αναπαριστά ο αριθμός παράδειγμα:
      sin(270);
      >> -1
    

Αντιμετώπιση κλήσης συνάρτησης βιβλιοθήκης - Εισαγωγή σε πίνακες σταθερών τιμών

Ερώτηση:

Κάθε φορά που έχουμε κλήση μιας συνάρτησης βιβλιοθήκης θα πρέπει να εισάγεται στον αντίστοιχο πίνακα σταθερών τιμών ανεξαρτήτως αν έχει κληθεί ξανά πιο πριν ή πρέπει να γίνεται lookup μέσα στον πίνακα και αν υπάρχει να μην την εκχωρούμε;

Απάντηση:

Στους πίνακες σταθερών (όχι μόνο στις συναρτήσεις αλλά σε όλους) οι τιμές θα πρέπει να είναι μοναδικές. Όταν πάτε να κάνετε insert, κάνετε πρώτα lookup και αν βρείτε το entry επιστρέφετε το αντίστοιχο index του, αλλιώς το εισάγετε και επιστρέφετε το index αυτού που μόλις βάλατε.

Πρεπει να περνάμε τα ονόματα των formals στο vm;

Δεν περνάτε ονόματα formals. Καταρχάς στον τελικό κώδικα δεν υπάρχουν ονόματα, περνάτε τύπο και offset. Οπότε τύπος όταν έχουμε formals είναι formal + το offset είναι ο τρόπος που θα το βρούμε μέσα στη στοίβα - περιβάλλον εκτέλεσης.

Garbage Collection

  • Τα strings του πίνακα σταθερών τιμών δε χρειάζεται να τα απελευθερώσετε ακόμα κι αν δεν αναφέρεται πλέον κανείς σε αυτά. Το string value που μπορεί να έχει το κάθε memcell απελευθερώνεται όταν χρειάζεται με τη avm_memcellclear.

  • Όσον αφορά πίνακες, το garbage collection αφορά τον πίνακα και όχι τις μεταβλητές που αναφέρονται σε αυτόν. Όταν το refcount γίνει 0 άσχετα με το αν το τελευταίο ref ήταν global, formal ή local, τότε κάνετε collect.

Ερωτήσεις πάνω σε πίνακες

Πίνακας με όρισμα πίνακα

  • παράδειγμα:
    t=[];
    t[t]=3; 
    

    Οι πίνακες είναι hashtables που θεωρητικά μπορούν να υποστηρίξουν κλειδιά οποιοδήποτε τύπου. Σημασιολογικά το t[t] = 3; σημαίνει οτι στον πίνακα t στο κλειδί t βάζουμε την τιμή 3. Εφόσον κατά το runtime ο πίνακας t είναι ένας δυναμικός πίνακα με μοναδική διευθυνση στη μνήμη, μπορούμε κάλλιστα να χρησιμοποιήσουμε τη διεύθυνσή του για να κάνουμε ως index στο hashing. Ως index δεν επιτρέπεται ούτε το nil ούτε το undefined, άρα θα βγάζετε κατάλληλο μήνυμα λάθους. Πάντως για το project υποχρεωτικά είναι μόνο τα αριθμητικά και string κλειδιά.

Refcounter σε πίνακα

  • παράδειγμα:
    x=[];
    0: NEWTABLE_V (global)1
    1: ASSIGN (global)1  (global)0
    2: ASSIGN (global)0  (global)2 
    

    Στην παραπάνω περίπτωση το refcounter είναι 3 (3 αναφορές).

Undefined και nil τιμή σε πίνακα

  • Ως value, to undefined λειτουργεί ως οποιαδήποτε άλλη τιμή, δηλαδή αποθηκεύεται και επιστρέφεται κανονικά από τον πίνακα,
    • π.χ.
      t = [];
      t[0] = x; //x is undefined, so t[0] becomes undefined
      print(t[0]); //prints undefined
      
  • Για το nil ως value ισχύουν τα εξής:
    • Όταν κάνεις get από ένα πίνακα ένα index που δεν υπάρχει, η επιστρεφόμενη τιμή είναι nil. π.χ.
      t = [];
      print(t[0]); //prints nil
      
    • Όταν κάνεις set το nil σε ένα index ενός πίνακα τότε διαγράφεις τελείως το entry που αντιστοιχεί στο index αυτό.
      t = ["an entry"];
      t[0] = nil;
      print(t[0]); //prints nil
      print(objecttotalmembers(t)); //prints 0 
      

Ερωτήσεις πάνω στο top και topsp

Αρχικοποίηση top και topsp:

Στην κορυφή μπαίνουν τα globals, οπότε αρχικοποιείτε τους top και topsp τόσες θέσεις κάτω από την κορυφή. Από την 3η φάση είπαμε οτι οι temp μεταβλητές είναι κανονικές μεταβλητές που παίρνουν offset και μετράνε κανονικά στο εκάστοτε scope space. Άρα τις μετράμε και για το χώρο των globals, και των locals στις συναρτήσεις.

Γενικά συγκεκριμένο νούμερο για τα globals είναι δύσκολο να πεις καθώς εξαρτάται από το πόσα temps παράγονται που με τη σειρά του εξαρτάται από τον ακριβή κώδικα (π.χ. μερική vs ολική) καθώς και το αν κάνετε temp reuse.

  • Για το παράδειγμα:
    x = 10;
    y = "hello";
    function f(x,y){return x+y;}
    z = "word";
    w = cos(3.14);
    print(x,y,z,w); 
    

    Χωρίς temp reuse, έχουμε:

    • x, y, z, w μεταβλητές = 4 ,
    • 4 assignments που θέλουν ένα extra temp για να κρατήσουν το lvalue = 4
    • 2 function calls που θέλουν 1 temp για το retval = 2
    • Σύνολο 10 globals.