KXκωνσταντίνος.χαφής
2 λεπτά ανάγνωσης

Από τον lexer στο JVM bytecode: γράφοντας compiler για τη γλώσσα Alan

Μια ματιά μέσα σε πλήρη compiler — λεκτικός αναλυτής JFlex, συντακτικός αναλυτής CUP LALR, τυποποιημένο AST με πίνακες συμβόλων και άμεση εκπομπή JVM bytecode μέσω ASM. Το πιο δύσκολο, τεχνικό έργο που απόλαυσα περισσότερο.

#compilerjava#jvm#bytecode#asm#alan

Κάθε μάθημα 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 πρέπει πραγματικά να λειτουργήσει.

Οι συνέπειες είναι άμεσες και αμείλικτες:

Κάθε bug εκδηλώνεται ως VerifyError κατά την εκτέλεση — ή χειρότερα, ως σιωπηλά λάθος έξοδο. Δεν υπάρχει compiler που να πιάνει τα λάθη του compiler σου.

Τι παραδόθηκε

Γιατί είχε σημασία

Το γράψιμο αυτού του compiler ήταν απαιτητικό και ακριβές με τρόπο που η ανάπτυξη ιστοσελίδων σπάνια είναι. Η ανταμοιβή έρχεται στο τέλος: ένα πραγματικό πρόγραμμα σε πραγματική γλώσσα, μεταγλωττισμένο από λογισμικό που έγραψες εσύ, που τρέχει σωστά στο JVM. Ένα stack trace από bug στον δικό σου code generator διδάσκει περισσότερα για το JVM από κάθε tutorial.

Παραμένει το έργο που είμαι πιο περήφανος — και αυτό που απόλαυσα περισσότερο.