Seam— a new programming language

Kornelije Petak
7 min readNov 2, 2023

--

Photo by Michael Dziedzic on Unsplash

🔗 This article is part of the series: What do programmers and computers do?
🔗 Previously: What is this thing called programming language?

As part of this blog post series, we’ll go through the development of a small programming language, explaining along the way what’s going under the hood. Over time, post by post, we’ll extend it with more and more features until it gets too complicated.

Language name

As the name for this programming language, I have chosen the name Seam. I have several reasons for that.

As we established in the previous article, programming languages’ purpose is to instruct computers to do what we want. I also provided a very high-level overview of how the translation from the programming language code to machine code works.

I also mentioned that it’s possible to translate the code written in some language to an intermediate code representation (usually called bytecode) to be executed by an interpreter. The interpreter can be written in any other language.

Here’s the thing. My language of choice is C#. I’ve been with it for almost two decades, through thick and thin. Imagine now that I have a business application written in C#, and I want my users to be able to provide some custom calculations or behavior within the app. If they are tech-savvy users, I could make a C# plugin system so they can author their custom components using C#.

However, what if I provide a way for my app to execute code written in Seam? Why would that be beneficial?

Firstly, I can make Seam as simple as possible, making it easier to work with for newcomers who may not know C#.

Secondly, I can design Seam with some constructs specific to my application, which C# does not have.

Thirdly, what if, in the future, I want to port my application to some other platform and write my code in a different language? Kotlin, for example? Or Javascript? Or Java? Nah, not Java. 😆 Then, I could reuse all code written in Seam without having the users learn this new platform. They can learn Seam, and that’s all they need.

Of course, this all implies that learning Seam has any value to the users of my app. We’ll get to that in the future, but for now, let’s keep this endeavor as an academic pursuit to learn how compilers work.

So, this explains one of the reasons why I have chosen this name. I wanted the language to be used as a seam between a host application and the code written in that language.

Of course, nothing prevents me from writing a compiler to turn this into machine code and execute it as a standalone thing. But for now, let’s keep only the purpose of integrating with other apps in mind when we discuss Seam.

Let me also explain the rest of the reasons why I named the language — Seam. One is that among all the words that would describe its integrative purpose, it’s the only one I liked, that wasn’t already taken by another programming language. You would be surprised how many languages there are. The other is that some of its core syntax will resemble C-like languages (the comments, the braces, etc.), so its name starts with the same sound.

It’s almost like naming it C’m, but I’ll refrain from that. 😊

Capabilities

So, for the first language prototype, I’ll reduce the language to just a tiny set of capabilities. It will not be too useful at first, but we’ll get there.

🗃 Data.

Every program deals with data in one way or another. To be useful, a program has to process input and produce output. Input can be a piece of information the user provides, a single mouse click (or many of them), or a file. These are all valid inputs and there are many other types of input.

Then, the program has to do something with this data (input) and then output some other data to the user somehow, either by showing some result on screen, providing a resulting file, or anything along those lines.

But it has to process a piece of data (or many pieces) into something else (be it another piece of data or an event).

Vending machines are not any different. They also run a program. You input money and a choice of beverage, and it outputs your favorite drink. Its program processes your input to get you the output you want.

So, in a way, programs are data processors.

Thus, to process data, we need to decide what kind of data Seam can work with.

For now, we’ll allow only one type: numbers.

Yes, this means that in this first iteration, any program written in Seam can only do calculations. But that’s fine for now; there’s a lot we need to cover before expanding our type system.

Data is stored in boxes called variables. They are called variables because their values can change over time.

You should note that it’s an overly simplified mental model to treat variables as boxes where we put and remove values. But for now, it will do.

Seam language defines a way for variables to be defined and their values assigned.

var age = 12

This creates a variable age containing the initial value of 12.

We can later say:

age = 40

to overwrite the old value with a new one.

As for the operations we can do on the numbers, we’ll only be able to do:

var a = 12
var b = 6

// Mathematical operations
var addition = a + b // 18
var subtraction = a - b // 6
var division = a / b // 2
var multiplication = a * b // 72

// Comparisons, we use 1 for true statements,
// and 0 for false ones as the result of these operations
var greaterThanComparison = a > b // 1
var greaterThanOrEqualComparison = a >= b // 1
var lessThanComparison = a < b // 0
var lessThanOrEqualComparison = a <= b // 0
var equals = a == b // 0, note the double equals sign
var notEqual = a != b // 1

// And we can also use parentheses to denote calculation order
var total = (a + b) * (a - b)

Execution.

Programs execute some code. Usually, the instructions given to computers are written top-down, and they execute them in that order.

For example, we could tell a computer to (on a very very high-level of abstraction):

- open doors
- wait 10 seconds
- close doors
- wait 3 seconds

Programs also have to make decisions based on specific inputs. If you have chosen a Coke, you don’t want a Pepsi to come out of the vending machine. So, it’s essential that a program can execute some code conditionally.

And thus, something like this has to be possible:

- open doors
- wait 10 seconds
- If no one is standing in front of the doors
- close doors
- wait 3 seconds

However, if that were all we had, it would be very tedious to repeat something 200 times. And furthermore, impossible to repeat something indefinitely.

- open doors
- wait 10 seconds
- close doors
- wait 3 seconds

- open doors
- wait 10 seconds
- close doors
- wait 3 seconds

... 788 more lines of the same code

- open doors
- wait 10 seconds
- close doors
- wait 3 seconds

Fortunately, that’s why programming languages always have some control flow (or execution flow).

For Seam, we’ll have only two constructs for now:

  • If-Else
  • While

This is how they look like syntactically:

// imagine 'isDoorsOpen' and 'counter' variables are already defined

if(isDoorsOpen)
{
isDoorsOpen = 0
}
else
{
counter = counter + 1
}
var attempt = 1

while (attempt <= 10)
{
print(attempt) // imagine there's a function print available
attempt = attempt + 1
}

Besides the ‘var’ keyword and the lack of semicolons, this almost looks like code written in C programming language. For now, it’s pretty similar, but it will diverge in the future.

Functions.

Functions are a way to group some code and give that group a name. This way, it can be reused by simply using the name instead of repeating the same code many times. A simple function in Seam would look like this:

function add(first: number, second: number) -> number
{
return first + second
}

Okay, this syntax is a bit different. So, to define functions, we use the keyword function. Then, for each parameter, we have to specify the type. Both are number parameters for now because that’s the only type we currently want to support.

After the list of parameters, we write an arrow -> followed by the type name of the result the function will return.

We can calculate what we want inside a function and then return the value using the keyword return.

For now, we support only pure functions. Pure functions have two important traits:

  • For the same inputs, they always return the same output.
  • Any output is calculated only by using its inputs (and nothing else)

We can call this function in the following way:

var a = 12
var b = 13

// The 'result' will be 25 when this line is executed.
var result = add(a, b)

// We can also use the function result as an argument
// to the next function call
var c = 5
result = add(add(a, b), c) // the 'result' will become 30

It’s also important to note that a function can have local variables, which are scoped only to that one function. The parameters of a function are its local variables.

function calculate(a: number, b: number, c: number) -> number 
{
// a, b, and c are all local variables

var sum = a + b + c // local variable sum
var product = a * b * c // local variable product

if(product > sum)
{
return sum
}
else
{
return product
}
}

That’s it. That’s all we support.

One type, variables, if-else, while loop, and simple pure functions.

Oh, yes. And the built-in ‘print’ function.

In the next installment, we explore what a compiler for such a language will do.

--

--