Zephyrnet-logotyp

Låt oss skapa ett litet programmeringsspråk

Datum:

Vid det här laget är du förmodligen bekant med ett eller flera programmeringsspråk. Men har du någonsin undrat hur du kan skapa ditt eget programmeringsspråk? Och med det menar jag:

Ett programmeringsspråk är vilken uppsättning regler som helst som konverterar strängar till olika typer av maskinkod.

Kort sagt, ett programmeringsspråk är bara en uppsättning fördefinierade regler. Och för att göra dem användbara behöver du något som förstår dessa regler. Och det är de sakerna kompilatorer, tolkar, etc. Så vi kan helt enkelt definiera några regler, sedan, för att få det att fungera, kan vi använda vilket befintligt programmeringsspråk som helst för att skapa ett program som kan förstå dessa regler, vilket kommer att vara vår tolk.

Kompilator

En kompilator omvandlar koder till maskinkod som processorn kan exekvera (t.ex. C++ kompilator).

Tolk

En tolk går igenom programmet rad för rad och utför varje kommando.

Vill du ge det ett försök? Låt oss skapa ett superenkelt programmeringsspråk tillsammans som matar ut magentafärgad utdata i konsolen. Vi kallar det Magenta.

Vårt enkla programmeringsspråk skapar en kodvariabel som innehåller text som skrivs ut till konsolen... i magenta, förstås.

Konfigurera vårt programmeringsspråk

Jag kommer att använda Node.js men du kan använda vilket språk som helst för att följa med, konceptet kommer att förbli detsamma. Låt mig börja med att skapa en index.js fil och ställ in saker.

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()

Det vi gör här är att förklara en klass kallad Magenta. Den klassen definierar och initierar ett objekt som är ansvarigt för att logga text till konsolen med vilken text vi än tillhandahåller den via en codes variabel. Och för tillfället har vi definierat det codes variabel direkt i filen med ett par "hej"-meddelanden.

Skärmdump av terminalutgång.
Om vi ​​skulle köra den här koden skulle vi få texten lagrad i koder som loggas i konsolen.

OK, nu måste vi skapa en vad som kallas en Lexer.

Vad är en Lexer?

Okej, låt oss prata om det engelska språket för en sekund. Ta följande fras:

Hur mår du?

Här är "Hur" ett adverb, "är" är ett verb och "du" är ett pronomen. Vi har också ett frågetecken ("?") i slutet. Vi kan dela upp vilken mening eller fras som helst som denna i många grammatiska komponenter i JavaScript. Ett annat sätt vi kan särskilja dessa delar är att dela upp dem i små tokens. Programmet som delar upp texten i polletter är vårt lexer.

Diagram som visar kommandot som går genom en lexer.

Eftersom vårt språk är väldigt litet har det bara två typer av tokens, var och en med ett värde:

  1. keyword
  2. string

Vi kunde ha använt ett reguljärt uttryck för att extrahera tokes från codes sträng men framförandet kommer att vara mycket långsamt. Ett bättre tillvägagångssätt är att gå igenom varje karaktär i code snöre och grepppoletter. Så låt oss skapa en tokenize metod i vår Magenta klass — som kommer att bli vår Lexer.

Fullständig kod
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)
  }
}

Om vi ​​kör detta i en terminal med node index.js, bör vi se en lista över tokens utskrivna i konsolen.

Skärmdump av kod.
Bra saker!

Definiera regler och syntaxer

Vi vill se om ordningen på våra koder matchar någon sorts regel eller syntax. Men först måste vi definiera vad dessa regler och syntaxer är. Eftersom vårt språk är så litet har det bara en enkel syntax som är en print nyckelord följt av en sträng.

keyword:print string

Så låt oss skapa en parse metod som går igenom våra tokens och ser om vi har en giltig syntax bildad. Om så är fallet kommer den att vidta nödvändiga åtgärder.

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)
  }
}

Och skulle du titta på det — vi har redan ett arbetsspråk!

Skärmdump av terminalutgång.

Okej men att ha koder i en strängvariabel är inte så kul. Så låt oss sätta vår Magenta koder i en fil som heter code.m. På så sätt kan vi hålla våra magentafärgade koder åtskilda från kompilatorlogiken. Vi använder .m som filtillägg för att indikera att den här filen innehåller kod för vårt språk.

Låt oss läsa koden från den filen:

// 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()

Gå och skapa ett programmeringsspråk!

Och med det har vi framgångsrikt skapat ett litet programmeringsspråk från grunden. Se, ett programmeringsspråk kan vara så enkelt som något som åstadkommer en specifik sak. Visst, det är osannolikt att ett språk som Magenta här någonsin kommer att vara användbart nog för att vara en del av ett populärt ramverk eller något, men nu ser du vad som krävs för att göra ett.

Himlen är verkligen gränsen. Om du vill dyka in lite djupare, prova att följa den här videon som jag gjorde genom att gå igenom ett mer avancerat exempel. Det här är videon som jag också har visat hur du kan lägga till variabler till ditt språk också.

[Inbäddat innehåll]
plats_img

Senaste intelligens

plats_img

Chatta med oss

Hallå där! Hur kan jag hjälpa dig?