, The Great Wave off Kanagawa Katsushika Hokusai As someone who writes code, you undoubtedly do so using one or multiple programming languages. You probably enjoy writing code in some programming languages because of their elegance, expressive power or any other reason and you also have probably kept your distance from other programming languages because of, maybe, some of their features that you think are badly implemented. Have you, however, thought about how those programming languages we love and hate came to be? How those particular features we like (or do not like) are designed and implemented and why? How those magic black boxes that are compilers and interpreters work? How code written in JavaScript, Ruby, Python, etc, turns into an executable program? Or have you ever thought about building your own programming language? If your answer to any of the above questions is yes, welcome to this series of articles about . building a programming language This series will take you from 0 to 1 in building a functional interpreter for a programming language. At the end of the series, you will have an interpreter, that you built from scratch, run programs written in a programming language that we would have designed together. Why? Why learn how to implement a programming language? Compilers and interpreters encompass interesting data structures and algorithms, the knowledge of which is applicable and useful to other domains. You can apply this knowledge to writing parsers for different file formats, building domain specific languages, for example a database query language, …. You will also better understand how and when to optimize code, be better equipped to choose the best programming language for a particular task and you will finally make sense of some of those weird error messages your compiler/interpreter throws sometimes :). Building a compiler or interpreter will make you a better programmer. . Many interesting programming languages are open source and welcome new contributors but often, the knowledge necessary to contribute is a barrier to entry for most people who never took a CS compiler course. If you care about your preferred programming language and/or have ever thought about contributing to its evolution, you’ll be better equipped to do so after having built a toy compiler or interpreter. Contribute to the development of your favorite programming language . :) It’s a lot of fun How? This article is the introduction to a series of which each article will introduce the concepts and knowledge necessary to complete one specific step in implementing a programming language. Each article ends with a challenge (it wouldn’t be fun if there was no challenge, right?). The challenge will prompt you to implement a well-defined component of your interpreter. There will be some test files to download at the end of each article and you complete the challenge by writing the code to make all the tests pass. By the time you complete all the challenges, you will have a full working interpreter that can run code written in our programming language. Although any programming language can be used to complete the challenges, for this series, our implementation language will be , ( more precisely) mostly so that the series will be accessible to a wider audience. JavaScript ES6 What? If all the above sounds good, let’s start by describing the programming language we will be implementing. Our programming language, specifically designed for the purpose of this series, is called . It’s an interpreted, object-oriented and statically typed programming language inspired by , and . Blink Swift Kotlin Scala A tour of Blink Hello, Blink! The typical is written as follows. “Hello, World” Console.println("Hello, Blink!") Data types Blink supports values of familiar types like , , and . Additionally, there is a special type to express the absence of value (similar to in Java) and all types in Blink inherit from a supertype . Int Double String Bool Unit void Object Comments A comment in Blink starts with . Anything from the symbol to the end of line will be ignored by the Blink interpreter. // // // This is a comment. Declaring variables Variables are declared using a . let expression message: = "Hello, Blink!" {Console.println(message)} let String in A expression comprises 2 parts: let the (before the keyword) where the variable is declared and possibly initialized. A variable declaration is made of the name of the variable followed by a colon , followed by the type of the variable . To initialize the variable at its declaration, append equal and the value of the variable . declaration in : message: String = message: String = "Hello, Blink!" the (after the keyword) where the variable can be accessed. Variables declared with are only accessible in the associated body. body in let Blink supports meaning that if a variable is initialized at its declaration then its type can be omitted and Blink will infer the correct type of the variable based on its value. The previous example could have been written like this: Omitting the type of the variable. type inference message = "Hello, World!" {Console.println(message)} let in To declare multiple variables, separate them with commas in the declaration part of the . Declaring multiple variables at once. , let expression a = 42**,** b = 12**,** c = 24 {Console.println(a + b + c)} let in Each variable can access all the variables declared before it in its initialization expression. So something like the following is perfectly possible. a = 42, b = , c = {Console.println(a)Console.println(b)Console.println(c)} let a * 2 b * 3 in The above will print 4284252 A final note on the is that if the body is made of only one , the curly braces can be omitted. The first example could be written more succinctly as follows. Omitting the curly braces. let expression expression message = "Hello, Blink!" Console.println(message) let in Conditions Like in most programming languages, conditions are expressed using an . if-else expression (<condition>) {<do something>} {<do something>} if else The condition must evaluate to a value of type . The block can be omitted if needed and the curly braces can be omitted for the or block if the block is made of only one . Bool else if else expression (answer == 42) {Console.println("That's the answer of life!")} {Console.println("That's not the answer of life.")} if else Looping A is used to execute one or more as long as a condition holds true. while expression expressions (<condition>) {<do something>} while Again, the curly braces can be omitted if the body of the is made of only one while expression. Defining functions Functions are defined using the keyword. func sum(a: , b: ) {a + b} func Int Int : Int = in Blink are separated by commas and enclosed in parenthesis . Each parameter is defined by its name followed by a colon , followed by its type . Function parameters , (a: Int, b: Int) : a: Int The return type is defined by adding a colon followed by the type of the value returned by the function after the parameter list. If the function does not return a value, the return type must be . Return type. : Unit greet() = {Console.println("Hello, Blink!")} func : Unit However, as a syntactic sugar, if a function does not declare any return type, Blink will automatically assume that the return type is . So the previous example could be written as follows. Unit greet() = {Console.println("Hello, Blink!")} func After the return type, comes the equal operator and then the body of the function enclosed in curly braces. There is in Blink, the value of the last in the body of the function the value returned by the function. Function body. = no return keyword expression is If the body is made of only one , the curly braces can be omitted. Our first example could be rewritten like this: expression sum(a: Int, b: Int) a + b func = Defining classes Classes are defined using the keyword. class Person {} class This defines a simple (albeit useless) class . Objects of the class can now be created using the keyword. Person Person new p = { } let new Person() in A class can have one or more properties declared with the keyword. Properties. var Person { firstname: = "Klaus" lastname: age: } class var String var String var Int A property can be optionally initialized at its declaration. If a property is initialized, the initialization will be evaluated when the object is being created. expression . . Getters and setters (which are just functions) can be created to access properties outside of the class. Properties in Blink are private They can not be made public A class can have functions (or if you prefer). Functions. methods Person { firstname: = "Klaus" lastname: age: class var String var String var Int **func** firstname(): **String** = { firstname } **func** setFirstname(name: **String**) = { firstname = name } } In the example above, we have a getter and a setter for the property . A more idiomatic way to write those functions would be to omit the curly braces because there is only one in the body of each of those functions. firstname expression Person { firstname: = "Klaus" lastname: age: class var String var String var Int **func** firstname(): **String** = firstname **func** setFirstname(name: **String**) = firstname = name } . However, it’s possible to make a function private by adding the modifier before the keyword. Functions are public by default in Blink private func age(): Int = // ... private func A class in Blink has constructor. By default, a class in Blink has a default constructor which takes no parameter. An explicit custom constructor can be defined by adding a list of parameters enclosed in parenthesis to the name of the class. Constructor. one and only one Person**( : String,** lastname**: String)** { firstname(): = firstname setFirstname(name: ) = firstname = name class firstname func String func String **func** lastname(): **String** = lastname **func** setLastname(name: **String**) = lastname = name } The constructor parameters automatically act as properties of the class and can be accessed from the body of all the functions in the class or from the initialization of any additional property. expression person = "Baudelaire" {Console.println(person**.firstname() .lastname()**)} let new Person(" , Klaus" ) in )Console.println(person The above will print KlausBaudelaire Inheritance in Blink is expressed with the keyword. Inheritance. extends Employee Person {} class extends If the class being inherited (the superclass) has an explicit constructor, the inheriting class must honor the superclass’s constructor by passing the arguments necessary to create an object of the superclass. Employee**( : String,** lastname**: String,** company**: String)** Person**(firstname, lastname)** {} class firstname extends The modifier is used to override a function in the superclass. Overriding functions. override Person(firstname: , lastname: ) { toString(): = firstname + " " + lastname} class String String override func String In the above example, we’re overriding the available in the class. All classes inherit from in Blink. toString Object Object Defining operators Just like it’s possible to use arithmetic operators , , or to perform operations between 2 or more s, Blink allows us to define the behavior of the binary operators , , , , and unary operators and for our own classes. + - * / Int + - * / % — ! is defined by adding a function which name is the symbol of the operator to define. For example, let’s define a operator for a class (representing rational numbers like ). A binary operator + Rational 3/4 Rational(num: , den: ) { +(other: ): = // ...} class Int Int func Rational Rational Given two variables and of type , we can now write . Blink automatically converts the expression to the function call . a b Rational let c: Rational = a + b a + b a.+(b) is defined by adding a function which name starts with followed by the symbol of the operator being defined. A unary operator unary_ Rational(num: , den: ) { unary_-(): = (-num, -den)} class Int Int func Rational new Rational In the example above, we’re defining the behavior of the unary operator on our class . Given a variable of type , we can write . Blink converts the to the function call . — Rational a Rational let b: Rational = -a expression -a a.unary_-() is also supported in Blink, meaning that it is possible to define multiple operators, for example, as long as the parameters list of each operator function is different. Operator overloading + For instance, for our class, we may want to add 2 s together but also want to add a and an . Operator overloading allows us to do just that. Rational Rational Rational Int Rational(num: , den: ) { +(other: ): = // ... +(integer: ): = // ... +(double: ): = // ...} class Int Int func Rational Rational func Int Rational func Double Rational With the above example and given 2 variables and of type , we can write like , or . a b Rational expressions a + b a + 42 b + 3.14 Everything is an object Everything in Blink is an object. Even primitives like s or s are actually objects. And an arithmetic expression like is, in fact, the function call . This is a neat feature that enables us, among others things, to define operators for our own types in Blink. Int Double 32 + 21 32.+(21) There is no statement in Blink, only expressions (and definitions) To conclude our tour of Blink, it is worth noting that unlike other programming languages like Java or JavaScript, there is no statement in Blink, only (and definitions like function or property definitions) The difference between an and a statement is that an always evaluates to a while a statement just performs some action. expressions . expression expression value For example in JavaScript, the statement executes a certain block of code if a condition is or . In Blink, is an expression and evaluates to a value. if true false if That means, in Blink, it’s perfectly valid to write isLegallyAdult = (age > 18) { } else { } let if true false will be equal to if or will be equal to if not. isLegallyAdult true age > 18 false This concludes our quick tour of Blink. As you can see, Blink has the necessary concepts to express almost any program, is simple enough to tackle writing an interpreter for it in a relatively small amount of time and has some interesting features like its usage of objects and expressions. If you’re ready to jump in, head over to which will give a high level overview of compilers and interpreters. the second article