Hello World! in AIM

This is a continuation of The AIM Project series, so if you haven't read the introductory post, then take a time to do that.

So, let's start this project with the syntax idea. It's both the easiest and the hardest part of language design. As I said in the last post, AIM's syntax is meant to be different compared to any other programming languages, and so it is. You may like it or not, but here it goes.


person holding light bulb
Photo by Diego PH / Unsplash

The idea

When thinking about this syntax and AIM language as a whole, I've been driven mainly by my personal experience in web development. I wanted to target this language to people who develop native, modern and interactive applications. Also keeping in mind performance, reactivity, ease-of-use and logical sequence of language structures. Development in AIM should be easy and shouldn't have too big learning curve. Also, I considered syntax from the side of its implementation. Its AST should be not complicated, simple to be later used for easy editor autocompletion, prettifying tools, linters etc. Now, without limitation to just follow the standard for most languages syntax form, I think I created something new, yet really similar to what is being used.


The syntax

I was given this privilege to get to know how hard designing a good (or any for that matter) programming language is. Sure, you can easily invent some new language constructions etc. but to make them into a logical sequence - that's a different story. Also, you have to keep in mind that feature you invent must be possible to implement (especially if you'll be doing this yourself).


For setup, I've created a monorepo on GitHub for this particular project. By now, I've started writing the documentation for the syntax of this language. It's quite important to have a centralized base/reference point for the following development. Otherwise, you can quickly get lost in all this - trust me. πŸ˜‰ So this documentation is highly Work In Progress but should be at least readable and understandable for most. Of course, it will get better with time. As its now open-source, any pull-requests, ideas, and suggestions are really, really welcome! For reading the following part of this post it's recommended to read the mentioned documentation or at least take a look at it.


Say "Hello"!

Let's start with basic syntax overview (at its current state) with simple and standard "Hello World!". From the start, I'm sorry for not-so-good syntax highlighting for this fresh programming-language-idea. πŸ˜‚

>> Say hello!
{
	console = @import('console')
	<>{
		hello #string = "Hello World!"
		..console.log(hello)
	}
}

Now let's discuss this nice monstrosity-like creation of mine!


First comes a comment. Single-line comments in AIM starts with the >> symbol. For multi-line comments, I considered the same beginning as for single-line ones with following ending by << symbol. This solution simplifies syntax but can bring some troubles with its implementation. It might be required to set the upper limit for the number of lines for a multi-line comment or change its syntax. This is due to the fact that you have to know if this comment is just a one-liner or not. If you have any ideas how to solve this, then I'll be grateful.


Going back to the code, we're finally starting to write AIM code. We start with the main codeblock where our basic code is located. I'll get back to this in a second, but now let's explore the concept of codeblocks and advanced types in AIM, as this is really important for the whole syntax.


So, in AIM there's no such thing as function, object, interface or class (at least in a literal way). All these constructions that can be found in other Object-oriented languages are replaced in AIM by so-called advanced types. These allow you to express all of the previously mentioned constructs with one, simple and universal syntax. There are 3 advanced types you should know of codeblock, receiver, and runner.


Codeblock

Codeblock is the most important of all advanced types. It consists of nothing more than curly brackets and code inside them. Codeblocks simply allow you to group your code. Of course, they can be nested, assigned to variables or passed as arguments - just like normal types.

myCodeblock #codeblock = {}

Receivers

Receivers are a form of helper-type for codeblocks. These allow passing arguments into codeblocks' local scopes. So by combining receivers with codeblock, you can create structures commonly referred to as functions with parameters support.

myReceiver #receiver = <arg1 #int32, arg2 #int32>

Runners

Runners allow executing code written in codeblock. When codeblock is bound with a receiver, then you can use the runner to pass values for given parameters.

myRunner #runner = (1,2)

All together

As you could see, as advanced types are just types, you can create separate variables for each one of them. Then use the bind modifier to make them interact with each other.

>> take arguments and run codeblock
myReceiver => myCodeblock => myRunner

You can also define them together, then type for such construction will be codeblock just because receiver and runner are only helper-types. Bind modifier can be omitted when working with static values.

myBindedStruct #codeblock = <a #int32>{}(1)

Back to code

>> Say hello!
{
	console = @import('console')
	<>{
		hello #string = "Hello World!"
		..console.log(hello)
	}
}

So, in our main codeblock where our code starts its execution, we see the first variable. Its name is console and it's assigned with assignment modifier to the result of @import directive execution. Directives are nothing more than codeblock-like structures referred to with preceding @ - directive modifier. These provide the main way of extending language functionality. In this example, we use the @import directive to import a module from stdlib, called console to interact with the console/terminal. Keep in mind that the standard library hasn't been defined yet, so this console module is just for the purpose of this example (although it's really possible that it'll appear in the future).


Next, we have a codeblock bounded with a receiver. It isn't assigned to any variable and so it's called the main method of the codeblock. This is just a place where you can put code that you want to execute (when you want to separate this main part of your code). It'll be most commonly used when using codeblocks as classes (these will serve as constructors). It must be bound with a receiver and shouldn't be assigned to any variable. When writing the opening part of your code (like the Hello World! example above does) you must use it (compare it to the main method in C/C++).


Inside this main method, we define a variable of type string using type modifier and type identifier (string). Then, in the next line, we use context modifier (..) to access upper scope and get access to console variable from the context of parent codeblock. Next, we use it with the runner and pass our variable as a parameter. This should output "Hello World!" to the console.


question mark neon signage
Photo by Emily Morter / Unsplash

What's your opinion?

I know that some things written above may not be as clear as they should but, as I said - you can always read the docs. Also, if this syntax interested you and you want to help make it better - consider a pull-request on GitHub or giving me any ideas. Of course, before any of that, read the docs. πŸ˜‚ If you liked this article or the general idea behind this series (programming language development) or AIM itself - consider following me on Twitter for any more updates. And last but not least, consider leaving a star if you want to follow AIM development more directly.