Έγραψα μια γλώσσα προγραμματισμού. Δείτε πώς μπορείτε επίσης.

Τους τελευταίους 6 μήνες, έχω εργαστεί σε μια γλώσσα προγραμματισμού που ονομάζεται Pinecone. Δεν θα το έλεγα ώριμο ακόμη, αλλά έχει ήδη αρκετά χαρακτηριστικά που μπορούν να χρησιμοποιηθούν, όπως:

  • μεταβλητές
  • λειτουργίες
  • καθορισμένες από τον χρήστη δομές

Εάν σας ενδιαφέρει, ρίξτε μια ματιά στη σελίδα προορισμού του Pinecone ή στο repo του GitHub.

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

Και όμως, έκανα ακόμα μια εντελώς νέα γλώσσα. Και λειτουργεί. Άρα πρέπει να κάνω κάτι σωστό.

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

Θα θίξω επίσης μερικές από τις αντισταθμίσεις που έχω κάνει και γιατί πήρα τις αποφάσεις που έκανα.

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

Ξεκινώντας

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

Μεταγλωττισμένο έναντι διερμηνείας

Υπάρχουν δύο βασικοί τύποι γλωσσών: συλλέγονται και ερμηνεύονται:

  • Ένας μεταγλωττιστής καταλαβαίνει όλα όσα θα κάνει ένα πρόγραμμα, το μετατρέπει σε «μηχάνημα κώδικα» (μια μορφή που ο υπολογιστής μπορεί να τρέξει πολύ γρήγορα) και μετά το αποθηκεύει για να εκτελεστεί αργότερα.
  • Ένας διερμηνέας περνά μέσα από τον πηγαίο κώδικα γραμμή προς γραμμή, καταλαβαίνοντας τι κάνει καθώς πηγαίνει.

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

Εκτιμώ ιδιαίτερα την απόδοση και είδα την έλλειψη γλωσσών προγραμματισμού που έχουν υψηλή απόδοση και προσανατολισμένη στην απλότητα, οπότε πήγα με το compile για το Pinecone.

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

Παρά το γεγονός ότι το Pinecone σχεδιάστηκε με γνώμονα τη μεταγλώττιση, έχει έναν πλήρως λειτουργικό διερμηνέα που ήταν ο μόνος τρόπος για να το τρέξει για λίγο. Υπάρχουν πολλοί λόγοι για αυτό, τους οποίους θα εξηγήσω αργότερα.

Επιλογή γλώσσας

Ξέρω ότι είναι λίγο meta, αλλά η γλώσσα προγραμματισμού είναι από μόνη της ένα πρόγραμμα και επομένως πρέπει να το γράψετε σε μια γλώσσα. Επέλεξα το C ++ λόγω της απόδοσής του και του μεγάλου σετ χαρακτηριστικών. Επίσης, πραγματικά μου αρέσει να δουλεύω στο C ++.

Εάν γράφετε μια ερμηνευμένη γλώσσα, είναι πολύ λογικό να το γράψετε σε μια μεταγλωττισμένη (όπως C, C ++ ή Swift) επειδή η απόδοση που χάθηκε στη γλώσσα του διερμηνέα σας και του διερμηνέα που ερμηνεύει τον διερμηνέα σας θα επιδεινωθεί.

Εάν σκοπεύετε να μεταγλωττίσετε, μια πιο αργή γλώσσα (όπως Python ή JavaScript) είναι πιο αποδεκτή. Ο χρόνος μεταγλώττισης μπορεί να είναι κακός, αλλά κατά τη γνώμη μου δεν είναι σχεδόν τόσο μεγάλη όσο ο κακός χρόνος εκτέλεσης.

Σχεδιασμός υψηλού επιπέδου

Μια γλώσσα προγραμματισμού είναι γενικά δομημένη ως αγωγός. Δηλαδή, έχει πολλά στάδια. Κάθε στάδιο έχει δεδομένα μορφοποιημένα με συγκεκριμένο, καλά καθορισμένο τρόπο. Έχει επίσης λειτουργίες για τη μετατροπή δεδομένων από κάθε στάδιο στο επόμενο.

Το πρώτο στάδιο είναι μια συμβολοσειρά που περιέχει ολόκληρο το αρχείο προέλευσης εισόδου. Το τελικό στάδιο είναι κάτι που μπορεί να εκτελεστεί. Όλα αυτά θα γίνουν σαφή καθώς περνάμε βήμα προς βήμα τον αγωγό Pinecone.

Λεξίνγκ

Το πρώτο βήμα στις περισσότερες γλώσσες προγραμματισμού είναι το lexing ή το tokenizing. Το «Lex» είναι σύντομο για λεξική ανάλυση, μια πολύ φανταστική λέξη για τη διάσπαση ενός κειμένου σε διακριτικά. Η λέξη «tokenizer» έχει πολύ μεγαλύτερη σημασία, αλλά το «lexer» είναι πολύ διασκεδαστικό να το πω ότι το χρησιμοποιώ ούτως ή άλλως.

Διακριτικά

Το διακριτικό είναι μια μικρή ενότητα μιας γλώσσας. Ένα διακριτικό μπορεί να είναι ένα όνομα μεταβλητής ή συνάρτησης (AKA ένα αναγνωριστικό), ένας τελεστής ή ένας αριθμός.

Εργασία του Lexer

Το lexer υποτίθεται ότι παίρνει μια συμβολοσειρά που περιέχει ολόκληρα αρχεία αξίας πηγαίου κώδικα και έριξε μια λίστα που περιέχει κάθε διακριτικό.

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

Καλώδιο

Την ημέρα που ξεκίνησα τη γλώσσα, το πρώτο πράγμα που έγραψα ήταν ένα απλό lexer. Λίγο αργότερα, άρχισα να μαθαίνω για εργαλεία που υποτίθεται ότι θα κάνουν την lexing απλούστερη και λιγότερο με λάθη.

Το κυρίαρχο εργαλείο είναι το Flex, ένα πρόγραμμα που δημιουργεί lexers. Το δίνετε ένα αρχείο που έχει μια ειδική σύνταξη για να περιγράψει τη γραμματική της γλώσσας. Από αυτό δημιουργεί ένα πρόγραμμα C το οποίο μεταφέρει μια συμβολοσειρά και παράγει την επιθυμητή έξοδο.

Η απόφασή μου

Επέλεξα να κρατήσω το lexer που έγραψα προς το παρόν. Στο τέλος, δεν είδα σημαντικά οφέλη από τη χρήση του Flex, τουλάχιστον δεν αρκεί για να δικαιολογήσω την προσθήκη εξάρτησης και να περιπλέξει τη διαδικασία κατασκευής.

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

Τεχνολογία

Το δεύτερο στάδιο του αγωγού είναι ο αναλυτής. Ο αναλυτής μετατρέπει μια λίστα με διακριτικά σε ένα δέντρο κόμβων. Ένα δέντρο που χρησιμοποιείται για την αποθήκευση αυτού του τύπου δεδομένων είναι γνωστό ως Abstract Syntax Tree ή AST. Τουλάχιστον στο Pinecone, το AST δεν έχει πληροφορίες σχετικά με τύπους ή ποια αναγνωριστικά είναι ποια. Είναι απλά δομημένα διακριτικά.

Αναλυτικά καθήκοντα

Ο αναλυτής προσθέτει δομή στη λίστα ταξινομημένων κουπονιών που παράγει το lexer. Για να σταματήσει η ασάφεια, ο αναλυτής πρέπει να λάβει υπόψη την παρένθεση και τη σειρά των λειτουργιών. Η απλή ανάλυση των χειριστών δεν είναι εξαιρετικά δύσκολη, αλλά καθώς προστίθενται περισσότερες δομές γλώσσας, η ανάλυση μπορεί να γίνει πολύ περίπλοκη.

Βόνασος

Και πάλι, υπήρχε μια απόφαση να ληφθεί με τη συμμετοχή μιας βιβλιοθήκης τρίτων. Η κυρίαρχη βιβλιοθήκη ανάλυσης είναι το Bison. Ο Bison λειτουργεί πολύ σαν το Flex. Γράφετε ένα αρχείο σε προσαρμοσμένη μορφή που αποθηκεύει τις πληροφορίες γραμματικής, και στη συνέχεια το Bison το χρησιμοποιεί για να δημιουργήσει ένα πρόγραμμα C που θα κάνει την ανάλυση σας. Δεν επέλεξα να χρησιμοποιήσω το Bison.

Γιατί το Custom είναι καλύτερο

Με το lexer, η απόφαση να χρησιμοποιήσω τον δικό μου κωδικό ήταν αρκετά προφανής. Το lexer είναι ένα τόσο ασήμαντο πρόγραμμα που δεν γράφω τη δική μου αίσθηση σχεδόν τόσο ανόητη όσο δεν γράφω το δικό μου «αριστερό μαξιλάρι».

Με τον αναλυτή, είναι διαφορετικό θέμα. Το πρόγραμμα ανάλυσης Pinecone μου έχει επί του παρόντος 750 γραμμές και έχω γράψει τρεις από αυτές, επειδή οι δύο πρώτες ήταν σκουπίδια.

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

  • Ελαχιστοποίηση εναλλαγής περιβάλλοντος στη ροή εργασίας: η εναλλαγή περιβάλλοντος μεταξύ C ++ και Pinecone είναι αρκετά κακή χωρίς να ρίξει στη γραμματική της γραμματικής του Bison
  • Κρατήστε απλή την κατασκευή: κάθε φορά που αλλάζει η γραμματική ο Bison πρέπει να εκτελείται πριν από την έκδοση. Αυτό μπορεί να αυτοματοποιηθεί, αλλά γίνεται πόνο κατά την εναλλαγή μεταξύ συστημάτων κατασκευής.
  • Μου αρέσει να χτίζω ωραία σκατά: Δεν έκανα το Pinecone γιατί νόμιζα ότι θα ήταν εύκολο, γιατί γιατί θα αναθέσω έναν κεντρικό ρόλο όταν θα μπορούσα να το κάνω μόνος μου; Ένας προσαρμοσμένος αναλυτής μπορεί να μην είναι ασήμαντος, αλλά είναι απολύτως εφικτός.

Στην αρχή δεν ήμουν απόλυτα σίγουρος αν θα έπαιρνα μια βιώσιμη πορεία, αλλά μου δόθηκε αυτοπεποίθηση από όσα είπε ο Walter Bright (ένας προγραμματιστής σε μια πρώιμη έκδοση του C ++ και ο δημιουργός της γλώσσας D) θέμα:

«Κάπως πιο αμφιλεγόμενο, δεν θα ενοχλούσα να σπαταλάω χρόνο με γεννήτριες lexer ή parser και άλλους λεγόμενους« μεταγλωττιστές μεταγλωττιστών ». Είναι χάσιμο χρόνου. Το να γράφεις ένα lexer και parser είναι ένα μικρό ποσοστό της δουλειάς της σύνταξης ενός μεταγλωττιστή. Η χρήση μιας γεννήτριας θα διαρκέσει περίπου όσο χρόνο γράφοντας ένα με το χέρι και θα σας παντρευτεί με τη γεννήτρια (που έχει σημασία κατά τη μεταφορά του μεταγλωττιστή σε μια νέα πλατφόρμα). Και οι γεννήτριες έχουν επίσης την ατυχή φήμη ότι εκπέμπουν άσχημα μηνύματα σφάλματος. "

Δέντρο δράσης

Έχουμε αφήσει τώρα την περιοχή των κοινών, καθολικών όρων, ή τουλάχιστον δεν ξέρω ποιοι είναι πλέον οι όροι. Από την κατανόησή μου, αυτό που αποκαλώ «δέντρο δράσης» μοιάζει περισσότερο με το IR του LLVM (ενδιάμεση αναπαράσταση).

Υπάρχει μια λεπτή αλλά πολύ σημαντική διαφορά μεταξύ του δέντρου δράσης και του αφηρημένου δέντρου σύνταξης. Μου πήρε αρκετό καιρό για να καταλάβω ότι θα έπρεπε να υπάρχει και η διαφορά μεταξύ τους (η οποία συνέβαλε στην ανάγκη για επανεγγραφή του αναλυτή).

Δέντρο δράσης εναντίον AST

Με απλά λόγια, το δέντρο δράσης είναι το AST με περιβάλλον. Αυτό το πλαίσιο είναι πληροφορίες όπως ο τύπος που επιστρέφει μια συνάρτηση, ή ότι δύο μέρη στα οποία χρησιμοποιείται μια μεταβλητή χρησιμοποιούν στην πραγματικότητα την ίδια μεταβλητή. Επειδή πρέπει να καταλάβει και να θυμηθεί όλο αυτό το πλαίσιο, ο κώδικας που δημιουργεί το δέντρο δράσης χρειάζεται πολλούς πίνακες αναζήτησης χώρου ονομάτων και άλλα πράγματα.

Εκτέλεση του Δέντρου Δράσης

Μόλις έχουμε το δέντρο δράσης, η εκτέλεση του κώδικα είναι εύκολη. Κάθε κόμβος δράσης έχει μια συνάρτηση «εκτελέστε» που παίρνει κάποια είσοδο, κάνει ό, τι πρέπει να κάνει η ενέργεια (συμπεριλαμβανομένης ενδεχομένως κλήσης υπο δράσης) και επιστρέφει την έξοδο της δράσης. Αυτός είναι ο διερμηνέας σε δράση.

Επιλογές συλλογής

"Αλλά περίμενε!" Σας ακούω να λέτε, "δεν πρέπει να συντάξει το Pinecone;" Ναι είναι. Αλλά η κατάρτιση είναι πιο δύσκολη από την ερμηνεία. Υπάρχουν μερικές πιθανές προσεγγίσεις.

Δημιουργήστε το δικό μου μεταγλωττιστή

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

Δυστυχώς, η σύνταξη ενός φορητού μεταγλωττιστή δεν είναι τόσο εύκολη όσο η σύνταξη κάποιου κώδικα μηχανής για κάθε στοιχείο γλώσσας. Λόγω του αριθμού των αρχιτεκτονικών και των λειτουργικών συστημάτων, δεν είναι πρακτικό για κάθε άτομο να γράφει ένα backend μεταγλωττιστή cross platform.

Ακόμη και οι ομάδες πίσω από τους Swift, Rust και Clang δεν θέλουν να ενοχληθούν από μόνες τους, έτσι αντ 'αυτού χρησιμοποιούν όλοι…

LLVM

Το LLVM είναι μια συλλογή εργαλείων μεταγλωττιστή. Είναι βασικά μια βιβλιοθήκη που θα μετατρέψει τη γλώσσα σας σε ένα μεταγλωττισμένο εκτελέσιμο δυαδικό αρχείο. Φαινόταν σαν η τέλεια επιλογή, γι 'αυτό πήδηξα μέσα. Δυστυχώς δεν έλεγξα πόσο βαθιά ήταν το νερό και αμέσως πνίγηκα.

Το LLVM, αν και δεν είναι σκληρή γλώσσα συναρμολόγησης, είναι μια τεράστια πολύπλοκη βιβλιοθήκη σκληρή. Δεν είναι αδύνατο να χρησιμοποιηθούν και έχουν καλά μαθήματα, αλλά συνειδητοποίησα ότι θα έπρεπε να ασκήσω κάποια πρακτική πριν ήμουν έτοιμος να εφαρμόσω πλήρως έναν μεταγλωττιστή Pinecone μαζί του.

Μεταβολή

Ήθελα ένα είδος μεταγλωττισμένου Pinecone και το ήθελα γρήγορα, οπότε γύρισα σε μια μέθοδο που ήξερα ότι θα μπορούσα να κάνω δουλειά: transpiling.

Έγραψα ένα Pinecone στο C ++ transpiler και πρόσθεσα τη δυνατότητα αυτόματης μεταγλώττισης της πηγής εξόδου με το GCC. Αυτή τη στιγμή λειτουργεί για σχεδόν όλα τα προγράμματα Pinecone (αν και υπάρχουν μερικές άκρες που το σπάζουν). Δεν είναι μια ιδιαίτερα φορητή ή επεκτάσιμη λύση, αλλά λειτουργεί προς το παρόν.

Μελλοντικός

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

Μέχρι τότε, ο διερμηνέας είναι ιδανικός για ασήμαντα προγράμματα και η μετάδοση C ++ λειτουργεί για τα περισσότερα πράγματα που χρειάζονται περισσότερη απόδοση.

συμπέρασμα

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

Εδώ είναι οι υψηλού επιπέδου συμβουλές μου για να ξεκινήσω (θυμηθείτε, δεν ξέρω πραγματικά τι κάνω, οπότε πάρτε το με έναν κόκκο αλατιού):

  • Εάν έχετε αμφιβολίες, ερμηνεύστε. Οι ερμηνευμένες γλώσσες είναι γενικά ευκολότερος σχεδιασμός, κατασκευή και εκμάθηση. Δεν σας αποθαρρύνω να γράψετε ένα μεταγλωττισμένο εάν ξέρετε ότι αυτό είναι που θέλετε να κάνετε, αλλά αν είστε στο φράχτη, θα ερμηνευόμουν.
  • Όταν πρόκειται για lexers και parsers, κάντε ό, τι θέλετε. Υπάρχουν έγκυρα επιχειρήματα υπέρ και κατά της γραφής των δικών σας. Στο τέλος, αν σκεφτείτε το σχέδιό σας και εφαρμόσετε τα πάντα με λογικό τρόπο, δεν έχει σημασία.
  • Μάθετε από τον αγωγό με τον οποίο κατέληξα. Πολλές δοκιμές και λάθη πήγαν στο σχεδιασμό του αγωγού που έχω τώρα. Προσπάθησα να εξαλείψω AST, ASTs που μετατρέπονται σε δέντρα δράσεων στη θέση τους και άλλες τρομερές ιδέες. Αυτός ο αγωγός λειτουργεί, οπότε μην το αλλάξετε εκτός εάν έχετε μια πολύ καλή ιδέα.
  • Εάν δεν έχετε το χρόνο ή το κίνητρο να εφαρμόσετε μια σύνθετη γλώσσα γενικού σκοπού, δοκιμάστε να εφαρμόσετε μια εσωτερική γλώσσα όπως το Brainfuck. Αυτοί οι διερμηνείς μπορεί να είναι τόσο σύντομοι όσο μερικές εκατοντάδες γραμμές.

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

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