Om

Published March 18, 2015
Advertisement
Thought rather than posting random stream of conciousness updates about the Om scripting language, I'd try and post a bit more of a structured introduction to the syntax today. Will write some stuff up about the C++ API as an when it is more finished.

Om is a dynamically typed language, in that each value carries its type around with it, and anything can be assigned to any variable. As a result, while far more flexible than statically typed languages, it does mean a lot of errors we are used to catching at compile time have to be instead caught during runtime. However, the API doesn't make such an obvious distinction between compilation and execution (even though this happens under the hood).

Let's start with some simple declarations.var a;var b = 12, c = 20.0;var d = "hello" + "world";
Here a is assigned the special null value (can be explicit with var a = null), b is of type Om::Int, c is Om::Float and d is Om::String.

Internally all values are the same size (4 bytes on a 32 bit system) and the fundamental types (int, bool, float) are simply stored by value. More complex objects like strings, arrays and objects are stored by a reference. All passing in Om is by value except that the value of complex objects is their reference ID so they are effectively automatically by reference.

Functions can be declared and assigned to any var as follows:var f = func{ // body};var g = func(a, b){// body};
White space is irrelevant, but note the function body is effectively a constant, no different to a number or a string and can be declared anywhere you would declare a constant. Example:var f = func(a){ a(23);};f(func(i){ out i, "\n"; });
Here the parameter passed to the function pointed to by f is the function defined inline as a constant in the function call.

There are two complex container types, Om::Array and Om::Object. Om::Array is defined like this:var a = [ 10, 20, "hello", true ];out a[2], "\n";
Om::Array works as you would expect, like a std::vector. You can append items with + and += and so on.

Om::Object is more like a std::map in that you can define members in the definition:var o ={ var x = 10; var y = 20;};
But you can also just assign to new members and they are added automatically:out o.x, "\n";o.z = "hello";out o.z, "\n";
Om::Object supports reflection in that its members can be accessed using [] and a string e.g.out o["x"], "\n";var s = "y";out o[s], "\n";
Eventually it will be possible to query an object to get an Om::Array of Om::Strings representing all its members, so the reflection possibilities will be endless here.

Finally if a function is a member of an object and the function is called in that object's context, the this keyword can be used to refer to the current object. For example:var o ={ var x = 10; var f = func { out this.x, "\n"; }};o.f();
The keyword this is always available in a function, but if the function is not called in the context of an object, it is simply null.

Everything nests as deeply as you wish, so functions can contain functions, can contain objects, can contain arrays, can contain etc.

As an example of the problem with catching errors at compile time in this model, consider this:var f = func{ if(something()) return 10; return [ 10, 20 ];};out f().length;
This is quite legal and the kind of thing designed to be possible. length is a built in property of Om::Strings and Om::Arrays but not meaningful to other types but whether it is an error here depends on what something() returns which may not be possible to ascertain until runtime, so the compiler allows this and the runtime errors out if you attempt to call .length on an Om::Int in this case. It will eventually point to the line in the source when it does this too, so it will be like a compile error.

However, it is quite possible to code around this e.g:[code=:0]var f = func{ if(something()) return 10; return [ 10, 20 ];};var n = f();if(n.type == "array") out n.length;
The usual if, else, while and C++-style for loops are all implemented, with the extra ability to declare a new var in the first clause of the for loop that then lives in the for's execution block context, much like C++.[code=:0]for(var x = 0; x <= 10; ++x) out x, "\n";
Later there will be a second form of for() that applies to Om::Arrays (not done this yet)[code=:0]for(var a: [10, 20, 30]) out a, "\n";
One can imagine this combined with object reflection support:[code=:0]var f = func(object){ for(var o: object.members) { out "member ", o, ": ", object[o], "\n"; }}
The API is not well developed yet but will look essentially like:void main(){ Om::Engine e; Om::Value v = e.evaluate("return 10 + 3;"); if(v.isError()) { std::cerr << v.error().message() << std::endl; return -1; } std::cout << "return - " << Om::typeToString(v.type()) << ": " << v.toInt();}
Om::Value will work with the reference-counting system to maintain the lives of complex objects outside of the scripting execution context and provide a framework for setting up native function callbacks that can be called from Om script with identical syntax to calling script functions.

So just a bit of an introduction to the language spec there anyway. Hope of interest and thanks for reading.
4 likes 6 comments

Comments

Migi0027

Slow down! You're too fast!

March 18, 2015 08:57 PM
dmatter
Interesting stuff!

What's in the name Om? Any meaning or back story to that name at all?

One thought from me, have you considered not having C-style for-loops and instead standardising on the foreach style loop in conjunction with literal range expressions on Om::Array? For example:

for(var x : [0..10]) out x, "\n";
March 19, 2015 12:03 AM
Aardvajk

Its just a short name really. No particular meaning to it.

That's an interesting idea re the for loop, and when what I have in mind is done, that would indeed work, but it wouldn't allow, for example, for(int i = 0; i < 20; i += 2) or whatever, so I think both forms are needed as the C style form is pretty much unlimited in flexibility.

March 19, 2015 08:17 AM
dmatter
That's fair.

I could suggest a little more syntactic sugar to support a step value:

for(var x : [0..20, 2]) out x, "\n"

It appeals to my personal taste of only using for-loops to iterate collection types or for simple sequential counts. For me this rules out many of the unlimited flexibility scenarios that C-style loops could technically handle but for which I would tend to prefer to use a while loop instead.
March 19, 2015 09:29 PM
Aardvajk

Yes, that's actually a very nice idea. This would not really be anything to do with for loops as such, but just a different way of declaring a list, since the syntax would work anywhere you did a list declaration like that. So you could equally do:

var a = [0..10, 2];

Should be easy enough to implement. Thanks. Think I'll keep support for the C style for as well though.

March 20, 2015 08:36 AM
Ravyne

I think I've seen in other languages pattern inference for such sequences, for example, an equivilent to the above would be:

var a = [0, 2..10];

Obviously its more complicated to do this kind of pattern detection more generally (though this single 'step' pattern is easy). I think the benefit is that provides consistency with bespoke sequences, such as:

var b = [0, 2, 2, 3, 7, 9];

Perhaps some notion of unbounded repetition could be useful too for example [0, 2...] would mean "count by twos for an undetermined amount of time", where loop exit is controlled by a conditional break statement. Or, it might be useful for a sort of "fire and forget" counter (though you'd have to do something smart with over/under-flow).

April 10, 2015 06:23 PM
You must log in to join the conversation.
Don't have a GameDev.net account? Sign up!
Profile
Author
Advertisement
Advertisement