Κάθε μάθημα compilers διδάσκει το ίδιο pipeline: λεκτική ανάλυση, συντακτική ανάλυση, σημασιολογική ανάλυση, παραγωγή κώδικα. Το να το γράψεις από το μηδέν σε κάνει να συνειδητοποιήσεις πόσα κάνει σιωπηλά κάθε φάση.
Η γλώσσα
Η Alan είναι μια μικρή προστακτική γλώσσα διδασκαλίας. Έχει τύπους byte, int, reference και proc, συναρτήσεις, if/else, while, συμβολοσειρές και χαρακτήρες, και βιβλιοθήκη ρουτίνων. Αρκετά μικρή για να είναι διαχειρίσιμη, αρκετά μεγάλη ώστε τίποτα να μην είναι συντόμευση.
Τα τρία στάδια
Λεκτική ανάλυση. Η γραμματική JFlex (lexer.jflex) αναγνωρίζει λέξεις-κλειδιά, δεκαεξαδικά και συμβολοσειρές, και σχόλια --, με παρακολούθηση γραμμής και στήλης για διαγνωστικά. Η λεκτική ανάλυση φαίνεται απλή μέχρι να διαχειριστείς σωστά τα escapes και τα δεκαεξαδικά.
Συντακτική ανάλυση. Η γραμματική CUP (parser.cup) είναι LALR, με δηλώσεις προτεραιότητας τελεστών ώστε οι εκφράσεις να αναλύονται όπως ορίζει η γλώσσα. Η έξοδος είναι τυποποιημένο AST — Program, FuncDef, Expr, IfStmt, WhileStmt, Assignment — που σημαίνει ότι ο parser επαληθεύει και τη μορφή των δηλώσεων.
Σημασιολογία. Μια στοίβα SymbolTable/SymbolEntry ορίζει εμβέλειες δηλώσεων και ένας πλήρης ελεγκτής τύπων διασχίζει το AST, σηκώνοντας SemanticException/TypeException. Εδώ πιάνονται οι μη δηλωμένες μεταβλητές, οι λανθασμένοι τύποι και οι λάθος αριθμοί ορισμάτων — πριν παραχθεί οποιοσδήποτε κώδικας.
Το ενδιαφέρον κομμάτι: bytecode, όχι πηγαίος κώδικας
Η πιο σημαντική απόφαση ήταν να εκπεμφθεί απευθείας JVM bytecode με το tree API της ASM (ClassNode, MethodNode, InsnList) αντί να παραχθεί πηγαίος κώδικας Java και να τελειώσει τη δουλειά ο javac. Είναι ο έντιμος δρόμος: κάθε φάση του compiler πρέπει πραγματικά να λειτουργήσει.
Οι συνέπειες είναι άμεσες και αμείλικτες:
- Ο έλεγχος ροής (
if,while, βραχυκύκλωση) πρέπει να μεταφραστεί σε ρητές ετικέτες και εντολές άλματος. - Οι κλήσεις συναρτήσεων απαιτούν πειθαρχία στη στοίβα τελεστών — ορίσματα σε σειρά, λογαριασμός επιστροφών, σταθερό βάθος στοίβας σε κάθε ένωση διακλάδωσης.
- Οι συμβολοσειρές και η βιβλιοθήκη χρόνου εκτέλεσης χρειάζονται μοντέλο μνήμης: ένα
MemHeapδιαχειρίζεται τη διάταξη και ένα πακέτοlibrary/παρέχει ενσωματωμένες ρουτίνες όπωςReadInteger,WriteString,StrcmpκαιStringToArrayList. - Οι τοπικές μεταβλητές και η διάκριση
byte/intπρέπει να παρακολουθούνται μέσω του πίνακα συμβόλων.
Κάθε bug εκδηλώνεται ως VerifyError κατά την εκτέλεση — ή χειρότερα, ως σιωπηλά λάθος έξοδο. Δεν υπάρχει compiler που να πιάνει τα λάθη του compiler σου.
Τι παραδόθηκε
- Εργαλεία shell για συντακτικό έλεγχο, σημασιολογικό έλεγχο, μεταγλώττιση και εκτέλεση
- Build Maven με καθαρή δομή πακέτων
- Έτοιμα παραδείγματα:
hello_world, bubble sort, πύργοι του Ανόι, πρώτοι, αντιστροφή συμβολοσειράς
Γιατί είχε σημασία
Το γράψιμο αυτού του compiler ήταν απαιτητικό και ακριβές με τρόπο που η ανάπτυξη ιστοσελίδων σπάνια είναι. Η ανταμοιβή έρχεται στο τέλος: ένα πραγματικό πρόγραμμα σε πραγματική γλώσσα, μεταγλωττισμένο από λογισμικό που έγραψες εσύ, που τρέχει σωστά στο JVM. Ένα stack trace από bug στον δικό σου code generator διδάσκει περισσότερα για το JVM από κάθε tutorial.
Παραμένει το έργο που είμαι πιο περήφανος — και αυτό που απόλαυσα περισσότερο.