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.
Slow down! You're too fast!