Μέχρι τώρα, πιθανώς να είστε εξοικειωμένοι με μία ή περισσότερες γλώσσες προγραμματισμού. Αλλά έχετε αναρωτηθεί ποτέ πώς θα μπορούσατε να δημιουργήσετε τη δική σας γλώσσα προγραμματισμού; Και με αυτό εννοώ:
Γλώσσα προγραμματισμού είναι οποιοδήποτε σύνολο κανόνων που μετατρέπουν συμβολοσειρές σε διάφορα είδη εξόδου κώδικα μηχανής.
Εν ολίγοις, μια γλώσσα προγραμματισμού είναι απλώς ένα σύνολο προκαθορισμένων κανόνων. Και για να τα κάνετε χρήσιμα, χρειάζεστε κάτι που να κατανοεί αυτούς τους κανόνες. Και αυτά τα πράγματα είναι μεταγλωττιστές, διερμηνείς, κ.λπ. Μπορούμε απλά να ορίσουμε κάποιους κανόνες, και στη συνέχεια, για να λειτουργήσει, μπορούμε να χρησιμοποιήσουμε οποιαδήποτε υπάρχουσα γλώσσα προγραμματισμού για να φτιάξουμε ένα πρόγραμμα που μπορεί να κατανοήσει αυτούς τους κανόνες, ο οποίος θα είναι ο διερμηνέας μας.
Μεταγλωττιστής
Ένας μεταγλωττιστής μετατρέπει τους κώδικες σε κώδικα μηχανής που μπορεί να εκτελέσει ο επεξεργαστής (π.χ. μεταγλωττιστής C++).
Διερμηνέας
Ένας διερμηνέας περνά από το πρόγραμμα γραμμή προς γραμμή και εκτελεί κάθε εντολή.
Θέλετε να κάνετε μια δοκιμή; Ας δημιουργήσουμε μαζί μια εξαιρετικά απλή γλώσσα προγραμματισμού που θα βγάζει έξοδο χρώματος ματζέντα στην κονσόλα. Θα το ονομάσουμε Ματζέντα.
Ρύθμιση της γλώσσας προγραμματισμού μας
Θα χρησιμοποιήσω το Node.js, αλλά μπορείτε να χρησιμοποιήσετε οποιαδήποτε γλώσσα για να ακολουθήσετε, η ιδέα θα παραμείνει η ίδια. Επιτρέψτε μου να ξεκινήσω δημιουργώντας ένα index.js
αρχείο και ρυθμίστε τα πράγματα.
class Magenta {
constructor(codes) {
this.codes = codes
}
run() {
console.log(this.codes)
}
}
// For now, we are storing codes in a string variable called `codes`
// Later, we will read codes from a file
const codes =
`print "hello world"
print "hello again"`
const magenta = new Magenta(codes)
magenta.run()
Αυτό που κάνουμε εδώ είναι να δηλώσουμε μια κλάση που ονομάζεται Magenta
. Αυτή η κλάση ορίζει και εκκινεί ένα αντικείμενο που είναι υπεύθυνο για την καταγραφή κειμένου στην κονσόλα με οποιοδήποτε κείμενο του παρέχουμε μέσω codes
μεταβλητός. Και, προς το παρόν, το έχουμε ορίσει codes
μεταβλητή απευθείας στο αρχείο με μερικά μηνύματα "γεια".
Εντάξει, τώρα πρέπει να δημιουργήσουμε ένα αυτό που ονομάζεται Lexer.
Τι είναι το Lexer;
Εντάξει, ας μιλήσουμε για την αγγλική γλώσσα για λίγο. Πάρτε την ακόλουθη φράση:
Πώς είσαι;
Εδώ, το "How" είναι επίρρημα, το "είναι" είναι ρήμα και το "εσείς" είναι αντωνυμία. Έχουμε και ένα ερωτηματικό (“?”) στο τέλος. Μπορούμε να χωρίσουμε οποιαδήποτε πρόταση ή φράση όπως αυτή σε πολλά γραμματικά στοιχεία στο JavaScript. Ένας άλλος τρόπος με τον οποίο μπορούμε να διακρίνουμε αυτά τα μέρη είναι να τα χωρίσουμε σε μικρές μάρκες. Το πρόγραμμα που χωρίζει το κείμενο σε διακριτικά είναι το δικό μας Lexer.
Δεδομένου ότι η γλώσσα μας είναι πολύ μικρή, έχει μόνο δύο τύπους διακριτικών, το καθένα με μια τιμή:
keyword
string
Θα μπορούσαμε να χρησιμοποιήσουμε μια κανονική έκφραση για να εξαγάγουμε διακριτικά από το codes
string αλλά η απόδοση θα είναι πολύ αργή. Μια καλύτερη προσέγγιση είναι να περιηγηθείτε σε κάθε χαρακτήρα του code
κορδόνι και πιάσε μάρκες. Λοιπόν, ας δημιουργήσουμε ένα tokenize
μέθοδος σε μας Magenta
τάξη — που θα είναι το Lexer μας.
Πλήρης κωδικός
class Magenta {
constructor(codes) {
this.codes = codes
}
tokenize() {
const length = this.codes.length
// pos keeps track of current position/index
let pos = 0
let tokens = []
const BUILT_IN_KEYWORDS = ["print"]
// allowed characters for variable/keyword
const varChars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_'
while (pos < length) {
let currentChar = this.codes[pos]
// if current char is space or newline, continue
if (currentChar === " " || currentChar === "n") {
pos++
continue
} else if (currentChar === '"') {
// if current char is " then we have a string
let res = ""
pos++
// while next char is not " or n and we are not at the end of the code
while (this.codes[pos] !== '"' && this.codes[pos] !== 'n' && pos < length) {
// adding the char to the string
res += this.codes[pos]
pos++
}
// if the loop ended because of the end of the code and we didn't find the closing "
if (this.codes[pos] !== '"') {
return {
error: `Unterminated string`
}
}
pos++
// adding the string to the tokens
tokens.push({
type: "string",
value: res
})
} else if (varChars.includes(currentChar)) { arater
let res = currentChar
pos++
// while the next char is a valid variable/keyword charater
while (varChars.includes(this.codes[pos]) && pos < length) {
// adding the char to the string
res += this.codes[pos]
pos++
}
// if the keyword is not a built in keyword
if (!BUILT_IN_KEYWORDS.includes(res)) {
return {
error: `Unexpected token ${res}`
}
}
// adding the keyword to the tokens
tokens.push({
type: "keyword",
value: res
})
} else { // we have a invalid character in our code
return {
error: `Unexpected character ${this.codes[pos]}`
}
}
}
// returning the tokens
return {
error: false,
tokens
}
}
run() {
const {
tokens,
error
} = this.tokenize()
if (error) {
console.log(error)
return
}
console.log(tokens)
}
}
Αν το εκτελέσουμε σε ένα τερματικό με node index.js
, θα πρέπει να δούμε μια λίστα με διακριτικά τυπωμένα στην κονσόλα.
Καθορισμός κανόνων και συντακτικών
Θέλουμε να δούμε αν η σειρά των κωδίκων μας ταιριάζει με κάποιο είδος κανόνα ή σύνταξης. Αλλά πρώτα πρέπει να ορίσουμε ποιοι είναι αυτοί οι κανόνες και οι συντακτικές. Δεδομένου ότι η γλώσσα μας είναι τόσο μικροσκοπική, έχει μόνο μια απλή σύνταξη που είναι α print
λέξη-κλειδί ακολουθούμενη από μια συμβολοσειρά.
keyword:print string
Ας δημιουργήσουμε λοιπόν ένα parse
μέθοδος που κάνει βρόχο μέσα από τα διακριτικά μας και να δούμε αν έχουμε σχηματίσει μια έγκυρη σύνταξη. Εάν ναι, θα προβεί στις απαραίτητες ενέργειες.
class Magenta {
constructor(codes) {
this.codes = codes
}
tokenize(){
/* previous codes for tokenizer */
}
parse(tokens){
const len = tokens.length
let pos = 0
while(pos < len) {
const token = tokens[pos]
// if token is a print keyword
if(token.type === "keyword" && token.value === "print") {
// if the next token doesn't exist
if(!tokens[pos + 1]) {
return console.log("Unexpected end of line, expected string")
}
// check if the next token is a string
let isString = tokens[pos + 1].type === "string"
// if the next token is not a string
if(!isString) {
return console.log(`Unexpected token ${tokens[pos + 1].type}, expected string`)
}
// if we reach this point, we have valid syntax
// so we can print the string
console.log('x1b[35m%sx1b[0m', tokens[pos + 1].value)
// we add 2 because we also check the token after print keyword
pos += 2
} else{ // if we didn't match any rules
return console.log(`Unexpected token ${token.type}`)
}
}
}
run(){
const {tokens, error} = this.tokenize()
if(error){
console.log(error)
return
}
this.parse(tokens)
}
}
Και θα το κοιτάξετε αυτό — έχουμε ήδη μια γλώσσα εργασίας!
Εντάξει, αλλά το να έχεις κωδικούς σε μια μεταβλητή συμβολοσειράς δεν είναι τόσο διασκεδαστικό. Ας τα βάλουμε λοιπόν Ματζέντα κωδικούς σε ένα αρχείο που ονομάζεται code.m
. Με αυτόν τον τρόπο μπορούμε να κρατήσουμε τους ματζέντα κώδικες μας χωριστά από τη λογική του μεταγλωττιστή. Χρησιμοποιούμε .m
ως επέκταση αρχείου για να υποδείξει ότι αυτό το αρχείο περιέχει κώδικα για τη γλώσσα μας.
Ας διαβάσουμε τον κώδικα από αυτό το αρχείο:
// importing file system module
const fs = require('fs')
//importing path module for convenient path joining
const path = require('path')
class Magenta{
constructor(codes){
this.codes = codes
}
tokenize(){
/* previous codes for tokenizer */
}
parse(tokens){
/* previous codes for parse method */
}
run(){
/* previous codes for run method */
}
}
// Reading code.m file
// Some text editors use rn for new line instead of n, so we are removing r
const codes = fs.readFileSync(path.join(__dirname, 'code.m'), 'utf8').toString().replace(/r/g, "")
const magenta = new Magenta(codes)
magenta.run()
Πηγαίνετε να δημιουργήσετε μια γλώσσα προγραμματισμού!
Και με αυτό, δημιουργήσαμε με επιτυχία μια μικροσκοπική Γλώσσα Προγραμματισμού από την αρχή. Βλέπετε, μια γλώσσα προγραμματισμού μπορεί να είναι τόσο απλή όσο κάτι που επιτυγχάνει ένα συγκεκριμένο πράγμα. Σίγουρα, είναι απίθανο μια γλώσσα όπως το Magenta εδώ να είναι ποτέ αρκετά χρήσιμη ώστε να είναι μέρος ενός δημοφιλούς πλαισίου ή οτιδήποτε άλλο, αλλά τώρα βλέπετε τι χρειάζεται για να το φτιάξετε.
Ο ουρανός είναι πραγματικά το όριο. Αν θέλετε να βουτήξετε λίγο βαθύτερα, δοκιμάστε να ακολουθήσετε αυτό το βίντεο που έφτιαξα και σε ένα πιο προηγμένο παράδειγμα. Αυτό είναι το βίντεο που έχω δείξει επίσης πώς μπορείτε να προσθέσετε μεταβλητές στη γλώσσα σας επίσης.