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.🎉
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-linecomments, 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 directiveexecution. 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.
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. 🦄
Top comments (16)
From a language design perspective the syntax is a bit odd, but the concept of advanced type pique my interest. Is AIM inspired by any other languages?
I know it is a bit odd but not much. Anyway it's meant to be this way. As for inspiration, not in obvious manner, mainly JavaScript and C++ I think. But its mostly whole new language.
I wonder you mentioned in the doc about how advanced types could be combined to represent class and interface. Have you thought about that through? For instance, how would you represent methods with a block and refer to itself in the receiver (i.e.
this
)? How would you represent a collection of methods for interfaces?At the start - I'm sorry if I haven't understood you correctly. So, you're meant to be able to access something like
this
with context modifier..
. As for the other one, I think an example might be the best way of showcasing it.Now, that's the basic idea but I understand your concerns. It's all in early stages with me writing the lexer right now. The syntax might change (even drastically) until I finish work on the parser, so yeah. Of course, I'm open-minded and welcome any ideas.
No worries, I'm interested in your design of advanced types, though I'm not sure where they're headed. It's a fresh approach on programming language complexity at this point. Obviously, modern languages tend to not priority class-based OOP (Rust and Go).
According to your idea, is it possible to define advanced types and compose them later, something maybe like this:
Excuse me if I didn't get it right, but hope the idea went across.
That's mostly right, you just didn't get the way to bind advanced types. It's like you have created something like 3 runners.
The correct way to do this is by using bind modifier for binding advanced types variables - they're not needed for static values.
It also applies everywhere else in the code. Aside from that, everything is just right.
That's interesting, though
=>
might be confusing since it's used quite differently from other languages like JS or Ocaml, which is a construct for lambdas. (Ocaml actually uses->
). Moreover, it wouldn't make sense to do something like this:Or is that valid AIM too? How would the two blocks work in this way? Would the second one be a nested block of the first?
Hm, that's a good question. Most likely not as only different advanced types should be bonded with blocks. As for the syntax, it's inspired by JS arrow function.
Exactly, but JS arrow function syntax actually has a deep root in lambda calculus and functional programming. For instance, in Ocaml, a lambda function can be defined as such:
Familiar?
=>
or->
has the definition of referential transparent mapping, meaning it maps a valuen
ton + 1
. This makes it possible to curry indefinitely:Same thing in JS.
In Rust, a block statement, interesting enough, takes cue from Ruby.
Point is I think it would be confusing to use the arrow for this type of composition. What AIM is doing with advanced types are macro-level thing, changing the syntactical meaning. That's why it piqued my interest in the first place. A language with macro built-in.
Naturally, I would expect advanced types to play out like this, if arrow was to be used:
Of course to make it less ambiguous you could come up with a totally new symbol and I think it would totally be justified since what AIM is doing here is not mapping a function to its application.
Good job on the design. It feels like a fun language to hack in.
Good to hear that you're interested in the project! Anyway, it's most likely that many decisions will be made at the time of finishing the parser. Even if, you've given me quite a rethink on this actually. 😅
I'd be great if you could keep refining the design as well. When the design is solid, anyone could implement it in anything.
I'm interested with this project. This looks good.
My question, what would you use for the Lexer, and the parser? Have you thought about what you will use? If not, I recommend use moo for the lexer and for nearley or jison parsers. Personally I like nearley more because it's easier.
Or, maybe you want to create your own? Thanks.
By this moment I explored only Jison, but thanks for other suggestions. Most likely I try to do my own.
I know @areknawo is interested in writing a compiler in JS or TS, but something to look at is ReasonML, which is a JS-like language over Ocaml, a very nice parser language.
>>
for comments is very odd since it is already a mathematical operator.See: developer.mozilla.org/en-US/docs/W...
Yeah, I have changed all bytewise operators by preceding them with & e.g.
&>>
or&|
. This is coming to the syntax repo soon. Anyway, still thinking about the design tho. I think this is good enough.