Λογότυπο Zephyrnet

Ας δημιουργήσουμε μια μικροσκοπική γλώσσα προγραμματισμού

Ημερομηνία:

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

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

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

Μεταγλωττιστής

Ένας μεταγλωττιστής μετατρέπει τους κώδικες σε κώδικα μηχανής που μπορεί να εκτελέσει ο επεξεργαστής (π.χ. μεταγλωττιστής 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.

Διάγραμμα που δείχνει εντολή να περνά μέσα από ένα λεξικό.

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

  1. keyword
  2. 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, &quot;&quot;)
const magenta = new Magenta(codes)
magenta.run()

Πηγαίνετε να δημιουργήσετε μια γλώσσα προγραμματισμού!

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

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

[Ενσωματωμένο περιεχόμενο]
spot_img

Τελευταία Νοημοσύνη

spot_img