HOME - RSS


O

21 May 2019 - 9 minute read


I'm going through a bit of a "new blog" rush, so expect quite a few posts now, but then I imagine they'll tail off over time to a more reasonable trickle.

One of my projects that I've been particularly enjoying is O. For those unaware, O is a high-level Object-Oriented general purpose programming language. It takes inspiration from C++, D, C#, and a little from sh/Bash and Ruby. If you've seen the O-lang category page, then you'll see a little hello world program there to give just a little taste of the syntax, but there's a lot more to it than that.

On O's GitLab page, under docs, there's a directory named "Design Articles" in which I've been making little articles describing the design and development process as it happens. Now I have this site, these articles will be appearing here. I probably won't move all the existing ones over to here because most of them are moot now anyway and I can do plenty of nice explaining with pretty examples here. In this post I want to introduce O for anyone that doesn't already know about it and also explain what the end goal is for O as I haven't really talked about that at all yet.


QUICK FEATURE DEMO

O will have - among a lot of other things - a pipeline, built in systems for dependency injection and plugin frameworks, user-definable statements, functions/methods as first-class citizens, a broad standard library, compile options to a bytecode or binary, and no null by default. Here's one code sample that I think fairly well captures the sort of thing I'm going for with O:

import std.io ;

class coolstuff ;

restricted string name ;

int action( int x ) ;

/// Constructor takes the name {this.name} and the action {this.action.body} for the object to do.
this( piped this.name , body this.action.body ) {
	[
		term.writeln( "Tada!" ) ,
		{ name == "jeff" ?? term.writeln( "Hi Jeff" ) ##
		term.writeln( "Hey you're not Jeff!" ) ; }
	][ action( 12 ) ]() ;
}
entrypoint myprogram ;

/// Constructor of the entrypoint is the entrypoint of the program.
this() {
	var object = "jeff" | coolstuff {
		x == 12 ?? return 1 ## return 0 ;
	}
}
This is a rather arbitrary and extreme example, but it covers plenty of little clever things, so let's start at the top and work our way down:

In the first block we import the std.io namespace which contains the static term class that provides terminal-related methods and attributes. Next, we declare our coolstuff class. Class definitions are just the one line and don't have braces because there is only one in a file so we save ourselves a whole indent level. In this class, we have an attribute 'name' that's marked as restricted, which means other classes can read it like a public member, but only the class itself may write to it. Next, we declare a method called 'action' that takes an integer called 'x'. Next, we have the constructor for our class. Note the arguments it takes. Rather than take a new local variable as a parameter, it's just taking an existing member of the class. In O, this is totally do-able, and passing in the value will assign it to that member. The first parameter is also marked as piped, which means it may be passed in through the pipeline as we see later on. It also assigns to the 'body' of 'action', that being a block that stores the definition of the method. This is marked as a body, which means the parameter is provided as a statement body in braces rather than in the section in parentheses.

In the constructor definition, we make an array of blocks with 2 items. We then index into that array with the return of 'action' when called with its parameter x being 12. Lastly, the parentheses on the end mean that we're invoking the block we've just selected. What this means is that we're essentially indexing into an array of locally defined methods and executing one of them, which would require a switch statement in other languages, which would be less efficient than just indexing into an array.

Now, our second section is a new file where we define our entrypoint. An entrypoint is just a class like any other, except it's this class that is where the program starts. The O compiler will check for a set of method signatures for constructors and mark the first it finds as the "main" method of the program. What this theoretically allows is instantiating the entrypoint elsewhere in the program and using it in other ways. In our constructor, we create a new variable (using type-inference) named object and set it equal to the result of a pipeline. We pipe the string "jeff" into a call to coolstuff, which is the constructor for that class. Note that you don't need to use new. If you use new then memory is allocated immediately for it, but if you don't, then memory is only allocated when the object is written to, and reads from that class are replaced with reads of the default values for that class. Both options are available as sometimes having memory allocated at an obvious time is helpful, but also, only allocating it when needed can help to optimise memory usage, as there are some objects that are created that may never be written to, and so memory is never allocated as it's never needed. Our constructor call doesn't have parentheses, this is because the only parameters being passed are via the pipeline and the body. The string is being piped in for our object's name, and the body of the action is being defined in the body in the method call. Notice how the body has access to x as it was declared in the declaration of action, and how it has to return an int as that's what the definition of action requires.

So, now say we compile and execute our program - what happens? We start at the entrypoint's constructor, where we declare a new variable object and get ready to assign it a value. We take the string "jeff" and pass it into coolstuff along with our block. The string is assigned to object's name, and the block is assigned to the body of action. As we're calling the constructor, the code within that is called, which creates the array of 2 blocks, then evaluates action(12), which returns 1, so we invoke item 1 in the array, which checks the name attribute and calls term.writeln( "Hi Jeff"), printing "Hi Jeff" to the screen. That constructor then finishes and our new object is assigned to object, then we reach the end of the entrypoint method, marking the end of our program and we exit.


PLANS FOR THE FUTURE

Ha... "quick"... anyway...
This is just a little bit of what I'm hoping to have this language be able to do. I've finished the lexer for the bootstrap compiler and I've started working on the parser too. Progress is definitely being made, but it'll be a while before I'm compiling anything. That said, I have been learning a tremendous amount working on this and I'm hoping to learn even more as I work more on this. My end goal for O is to build up a user base and start making real programs with it. It would be incredible to be able to build a company out of it so I can be working on this project for a living. That's all a long way away though.


CATEGORIES

O-lang - Programming


HOME - RSS

Copyright Oliver Ayre 2019. Site licensed under the GNU Affero General Public Licence version 3 (AGPLv3).