Welcome to the ObjectScript 1.5 !
The ObjectScript is a new embedded programing language that mixes benefits of JavaScript, Lua, Ruby, Python and PHP. The ObjectScript has syntax from JavaScript, multiple results from Lua, sugar syntax from Ruby, magic methods from Python and much more.
Getting Started
ObjectScript is a dynamically typed language. That means you do not have to specify the data type of a variable when you declare it, and data types are converted automatically as needed during script execution. So, for example, you could define a variable as follows:
var a = 12;
And later, you could assign the same variable a string value, for example:
a = "Hello World!";
Because ObjectScript is dynamically typed, this assignment does not cause an error message.
Case sensitivity
ObjectScript is case sensitive, null is not the same as Null, NULL, or any other variant. It is common to start the name of a class with a capitalised letter, and the name of a function or variable with a lower-case letter.
Whitespace and semicolons
Spaces, tabs and newlines used outside of string constants are called whitespace. Whitespace in ObjectScript source doesn't impact semantics. ObjectScript automatically detects statements, well formed statements will be considered complete (as if a semicolon were inserted between statements). Programmers can supply statement-terminating semicolons explicitly.
Use semicolons explicitly after return statement and before nested block to avoid unintended effects.
return;
return a, b;
;{ var c = a; }
Comments
Comment syntax is the same as in C++ and many other languages.
Syntax error */
Variables
Variables have no type attached, and any value can be stored in any variable. Variables are declared with a var statement, multiple variables can be declared at once. An identifier must start with a letter, underscore (_), or dollar sign ($); subsequent characters can also be digits (0-9) and the at sign (@). Variables are block scoped. Declared variable can be accessed by child blocks and functions. A variable value is null until it is initialized. Variables without declaration are environment variables (see Environments and the Global Environment).
x = 0 var function test(){ return 1 } function f() {
var z, r = 'foxes', 'birds' m = 'fish' var function child() {
var r = 'monkeys' z = 'penguins' }
;{ twenty = 20 var twenty = twenty var z = z z = 'bears' child() }
return x; }
f()
When ObjectScript tries to resolve an identifier, it looks in the local block scope (the block scope could be a function). If this identifier is not found, it looks in the outer block that declared the local one, and so on along the scope chain until it reaches the environment scope where environment variables reside. If it is still not found, ObjectScript will use environment magic getter or setter methods (see Properties, Getters And Setters).
When assigning an identifier, ObjectScript does exactly the same process to retrieve this identifier, except that if it is not found in the environment scope, it will create the "variable" as a property of the environment object (see Environments and the Global Environment). As a consequence, a variable never declared will be environment if assigned. Declaring a variable (with the keyword var) in the environment code (i.e. outside of any function body) will declare a new local variable because of ObjectScript uses wrap function to execute code.
Note that you can forbid read an undeclared environment variable implementing magic __get method
function __get(name){
throw "read undeclared '${name}' variable"
}
You can implement autoload of classes (modules) using the same technique
var checked = {}
function __get(name){
if(!(name in checked)){
checked[name] = true
require(name)
if(name in this){
return this[name]
}
}
throw "unknown class or global variable ${name}"
}
Function call syntax
When you call a function, you can pass along some values to it, these values are called arguments or parameters. These arguments can be used inside the function. You can send as many arguments as you like, separated by commas (,)
printf("My name is %s, I'm %d age old", "Ivan", 19)
Declare the argument, as variables, when you declare the function
function myFunction(var1, var2)
{
return var1 + var2
}
print(myFunction(2, 3))
If the function call has no arguments, you must write an empty list () to indicate the call
function test(){ print("Hello world!") }
test()
There are special cases to this rule. If the function has one single object argument, then the parentheses are optional
print { firstname = "Ivan", lastname = "Petrov" }
print({ firstname = "Ivan", lastname = "Petrov" })
If the function has one single argument (not object) and the function call is located just at top level of block statement (not in expression), then the parentheses are optional
function test(a){
print a ;{
print a }
print(2 + math.round(a)) }
test 10
See Function
Magic constants
There are several magic constants in ObjectScript
__FILE__ - filename of the current file, __LINE__ - the current line number
print("filename: ", __FILE__)
print("line: ", __LINE__)
Types and Values
ObjectScript recognizes the following types of values
- null, a special keyword denoting a null value
- boolean, either true or false
- number, such as 10, 0x123, 2.3, 5.7e23
- string, such as "Hello World!"
- object, such as {"one", "two", 12:"at index 12"}
- array, such as ["one", 31]
- function, such as function(){ print "Hello World!" }
- userdata, allows arbitrary C data to be stored in ObjectScript variables
null, boolean, number and string are primitive types.
object, array, function and userdata are not primitive types and could be used as named containers for values.
Null
Null is a type with a single value
null. All variables have a
null value by default, before a first assignment, and you can assign
null to a variable to delete it. ObjectScript uses
null as a kind of non-value.
If you want to check is a value is null, you should use === or !== operator, for example
val = null
print(typeOf(val)) print(val === null) print(val === 0) print(val == 0)
Boolean
The boolean type has two values, false and true, which represent the traditional boolean values. However, they do not hold a monopoly of condition values: In ObjectScript, any value may represent a condition. Conditionals (such as the ones in control structures) consider false, null and NaN (not a number) as false and anything else as true.
Beware that ObjectScript considers both zero and the empty string as true in conditional tests.
print(typeOf(true)) print(null ? true : false) print(1 > 2 ? true : false) print(1 ? true : false) print(0 ? true : false) print("0" ? true : false) print("" ? true : false) print([1,2] ? true : false)
Number
You can write numeric constants with an optional decimal part, plus an optional decimal exponent. Examples of valid numeric constants are
12 3.14 3.14f 2.5e3 0xfe 0123 0b110
Numbers are represented in binary as IEEE-754 doubles (by default), which provides an accuracy nearly 16 significant digits. Because they are floating point numbers, they do not always exactly represent real numbers, including fractions
printf("%.20f\n", 0.94 - 0.01) printf("%f\n", 0.94 - 0.01)
printf("%n\n", 0.94 - 0.01)
print(0.94 - 0.01)
print(typeOf(10.5))
Note: see printf, sprintf, print, echo functions.
A number has prototype of Number class
print(10.prototype === Number)
Convert to number
Convert to number of primitive types
- null is converted to 0
- true is converted to 1
- false is converted to 0
- "12" is converted to 12
- "0x12" is converted to 18
- "0123" is converted to 83
- "0b10" is converted to 2
- "10hello" is converted to 0
- 12 value is a number already
all other types are converted to number calling valueOf method that must return value of a primitive type.
You can check number using numberOf function and convert to number using toNumber
printf(" %9s %9s\n", 'numberOf', 'toNumber')
printf("---------------------------\n")
printf(" null: %9s %9s\n", numberOf(null), toNumber(null))
printf(" true: %9s %9s\n", numberOf(true), toNumber(true))
printf(" false: %9s %9s\n", numberOf(false), toNumber(false))
printf(" \"12\": %9s %9s\n", numberOf("12"), toNumber("12"))
printf("\"0x12\": %9s %9s\n", numberOf("0x12"), toNumber("0x12"))
printf("\"0123\": %9s %9s\n", numberOf("0123"), toNumber("0123"))
printf("\"0b10\": %9s %9s\n", numberOf("0b10"), toNumber("0b10"))
printf("\"12lo\": %9s %9s\n", numberOf("12lo"), toNumber("12lo"))
printf(" 12: %9s %9s\n", numberOf(12), toNumber(12))
var obj = {
valueOf = function(){
return 10
}
}
printf(" obj: %9s %9s\n", numberOf(obj), toNumber(obj))
var arr = [1,2,3]
printf(" array: %9s %9s\n", numberOf(arr), toNumber(arr))
outputs
numberOf toNumber
---------------------------
null: null 0
true: 1 1
false: 0 0
"12": 12 12
"0x12": 18 18
"0123": 83 83
"0b10": 2 2
"12lo": null 0
12: 12 12
obj: null 10
array: null 0
String
A string in ObjectScript is a sequence of characters. String is immutable, it means that you cannot change the object itself. All identical strings reference the same place in memory (including calculated at runtime). So when ObjectScript compares strings to equality, it compares only reference pointers.
print(typeOf("0123456789")) print("0123456789".sub(2, 4))
A string has prototype of String class
print("".prototype === String)
A string literal can be specified in four different ways:
- single quoted
- double quoted
- raw heredoc
- heredoc
Single quoted
The simplest way to specify a string is to enclose it in single quotes (the character ').
To specify a literal single quote, escape it with a backslash (\). To specify a literal backslash, double it (\\). All other instances of backslash will be treated as a literal backslash: this means that the other escape sequences you might be used to, such as \r or \n, will be output literally as specified rather than having any special meaning.
print 'this is a simple string'
print 'Arnold once said: "I\'ll be back"'
print 'You deleted C:\\*.*?'
print 'You deleted C:\*.*?'
print 'This will not expand: \n a newline'
print 'Expressions do not ${expand} ${either}'
Double quoted
If the string is enclosed in double-quotes ("), ObjectScript will interpret more escape sequences for special characters:
- \n - linefeed (LF or 0x0A (10) in ASCII)
- \r - carriage return (CR or 0x0D (13) in ASCII)
- \t - horizontal tab (HT or 0x09 (9) in ASCII)
- \" - double-quote
- \\ - backslash
- \$ - dollar sign
- \[1-9]\d{0,2} - the sequence of characters matching the regular expression is a character in decimal notation
- \0[0-7]{1,3} - the sequence of characters matching the regular expression is a character in octal notation
- \x[0-9A-Fa-f]{1,2} - the sequence of characters matching the regular expression is a character in hexadecimal notation
As in single quoted strings, escaping any other character will result in the backslash being printed too.
The most important feature of double-quoted strings is usage of string expression executed within it.
Syntax of string expression
A string expression begins with ${ and ends with }. To specify a literal ${, escape it with a backslash \${.
print "this is a string"
print "three plus three is ${3+3}"
foobar = "blah"
print "the value of foobar is ${foobar}"
print "the value of foobar is \${foobar}"
function factorial(a){
return a <= 1 ? 1 : a*factorial(a-1)
}
var p = 10
print "factorial of ${p} is ${factorial(p)}"
Raw heredoc
Raw heredoc begins with <<<MARKER' (<<< + MARKER + ' - single quote) and ends with MARKER. The string itself follows, and then the same marker again to close the string. The raw heredoc text behaves just like a single-quoted string. There is no parsing is done inside a raw heredoc. The construct is ideal for embedding code or other large blocks of text without the need for escaping. It shares some features in common with the SGML <![CDATA[ ]]> construct, in that it declares a block of text which is not for parsing.
The raw heredoc removes the first line with spaces and new line character. Also the one removes and last new line character if end marker is located at start line position.
print <<<~~~'this is a string~~~
// outputs: true
print 'this is a string' == <<<~~~'
this is a string
~~~
print <<<==='
Harry Potter is due to start
his fifth year at Hogwarts School
of Witchcraft and Wizardry.
===
print 'end'
Heredoc
Heredoc begins with <<<MARKER" (<<< + MARKER + " - double quote) and ends with MARKER. The string itself follows, and then the same marker again to close the string. A heredoc is specified similarly to a raw heredoc, but parsing is done inside a heredoc. See syntax of string expression.
ObjectScript will interpret escape sequence \$ as $ (dollar sign).
Convert to string
Convert to string of primitive types
- null is converted to "null" string
- true is converted to "true" string
- false is converted to "false" string
- 0.3 is converted to "0.3" string, numbers are converted to string using human friendly number format %n (see printf function)
- string value is a string already
all other types are converted to string calling valueOf method that must return value of a primitive type.
You can check string using stringOf function and convert to string using toString
printf(" %9s %9s\n", 'stringOf', 'toString')
printf("---------------------------\n")
printf(" null: %9s %9s\n", stringOf(null), toString(null))
printf(" true: %9s %9s\n", stringOf(true), toString(true))
printf(" false: %9s %9s\n", stringOf(false), toString(false))
printf(" \"12\": %9s %9s\n", stringOf("12"), toString("12"))
printf("\"0x12\": %9s %9s\n", stringOf("0x12"), toString("0x12"))
printf("\"0123\": %9s %9s\n", stringOf("0123"), toString("0123"))
printf("\"0b10\": %9s %9s\n", stringOf("0b10"), toString("0b10"))
printf(" 12: %9s %9s\n", stringOf(12), toString(12))
var obj = {
valueOf = function(){
return 10
}
}
printf(" obj: %9s %9s\n", stringOf(obj), toString(obj))
var arr = [1,2,3]
printf(" array: %9s %9s\n", stringOf(arr), toString(arr))
outputs
stringOf toString
---------------------------
null: null null
true: true true
false: false false
"12": 12 12
"0x12": 0x12 0x12
"0123": 0123 0123
"0b10": 0b10 0b10
12: 12 12
obj: null 10
array: null [1,2,3]
Note: string doesn't implement character access but you could implement it like this (see Properties, Getters And Setters)
function String.__get(i){
return this.sub(i, 1)
}
function String.__getdim(i, count){
return this.sub(i, count)
}
var str = "0123456789"
print(str.sub(2, 4)) print(str[2]) print(str[2, 3])
Object
An object is a list of zero or more pairs of property names and associated values
{"one", "two", 12:"at index 12", ["user" .. "name"]: "at index username", some = "extended syntax"}
The simplest constructor of object is the empty constructor
a = {};
You should not use an object literal { at the beginning of a statement. This will lead to not behave as you expect, because the { will be interpreted as the beginning of a block
{
var a = 12;
}
Lets view some examples
days = {"Sunday", "Monday", "Tuesday", "Wednesday",
"Thursday", "Friday", "Saturday"}
will initialize days[0] with the string "Sunday" (the first element has always index 0), days[1] with "Monday", and so on
print(days[4])
Constructors do not need to use only constant expressions. You can use any kind of expression for the value
tab = {sin(1), sin(2), sin(3), sin(4)}
To initialize an object to be used as a record, ObjectScript offers the following syntax
a = {x=0, y=0}
it's equivalent to
a = {x=0, y=0,}
a = {["x"]=0, ["y"]=0}
a = {x:0, y:0}
a = {x:0; y:0;}
so you can always put a comma after the last entry, you can use ":" or "=" to make pair of a property, you can always use a semicolon (;) instead of a comma (,)
a = { one = "great", two = "awesome", 1 = "value at index 1" };
print(typeOf(a))
b = "one";
a = { [b] = "great" }; print(a[b]) print(a["one"]) print(a.one)
a = { one = "great" }; print(a[b]) print(a["one"]) print(a.one)
a = { one = "great", two = "awesome" };
a = { one: "great", two: "awesome" }; a = { one: "great", two: "awesome", };
a = {"zero", "one", "two"}
print(a) delete a[1]
print(a)
See Object-oriented programming (OOP)
An object has prototype of Object class
print({}.prototype === Object)
Array
An array is a list of indexed values
a = ["zero", "one", "two"]
print(typeOf(a)) print(a.length) print(a[1]) delete a[1]
print(a.length) print(a[1])
The array is very compact structure so if you don't need a container of properties it's a good reason to use array instead of object.
An array has prototype of Array class
print([].prototype === Array)
Function
You can think of functions as procedures that your application can perform
var max = function(a, b){ return a > b ? a : b }
print(max(10, 3))
Functions are first-class values in ObjectScript. That means that functions can be stored in variables, passed as arguments to other functions, and returned as results.
An unconventional, but quite convenient feature of ObjectScript is that functions may return multiple results
var func = function(){ return 1, 2 }
var x, y = func()
print x print y
The functions are objects themselves. As such, they have properties and methods. Any reference to a function allows it to be invoked using the () operator
var func = function(){ print arguments }
var func2 = function(f){ f.apply(null, ...) }
func2(func,1,2,3)
Three dots (...) is rest arguments, it returns array of undeclared arguments passed to function, arguments returns array of all arguments passed to function.
Moreover, ObjectScript supports nested functions and closures. The nested functions are functions defined within another function. They are created each time the outer function is invoked. In addition to that, each created function forms a lexical closure: the lexical scope of the outer function, including any local variables and arguments, become part of the internal state of each nested function object, even after execution of the outer function concludes
var a = function(){
var x = 2
return function(){ retutn x++ }
}
var b = a()
print b() print b() print b()
Function returns the last expression evaluated
print({|a, b| a + b}(2, 3))
A function has prototype of Function class
print((function(){}).prototype === Function)
Chain function calls
var obj = {
do = function(a){
print a;
return this;
}
}
obj.do{
1: "one"
}.do{
2: "two"
}.do{
3: "three"
}
outputs
{1:"one"}
{2:"two"}
{3:"three"}
Standard functions variables
Any function contains following local variables
- this - method can access the instance variables through the this variable
- _F - alias of the current function
- _E - function environment (see Global Variables)
- _G - global variables (see Global Variables)
Alternative function syntax
You can declare function using {|params|body} syntax, for example
3.times{|i| print i}
outputs
0
1
2
How could it work in ObjectScript?
First of all let's declare times method for numbers. The numbers have Number prototype so we should add new method for the Number prototype
function Number.times(func){ for(var i = 0; i < this; i++){
func(i)
}
}
Everything is done, let's use it
5.times(function(i){ print i })
or just use sugar syntax
3.times{|i| print i}
One more example
print "factorial(20) = " .. {|a| a <= 1 ? 1 : a*_F(a-1)}(20)
outputs
factorial(20) = 2432902008176640000
Function in C++
ObjectScript can call functions written in ObjectScript and functions written in C++ or C. All the standard library in ObjectScript is written in C++. Application programs may define other functions in C
#include "objectscript.h"
int test(OS * os, int, int, int, void*)
{
os->pushNumber(123);
return 1; }
int main()
{
OS * os = OS::create(); os->pushCFunction(test);
os->setGlobal("test");
os->eval("print(test())"); os->release(); return 0;
}
There is also binder included in ObjectScript so you can declare C++ functions like this
#include "objectscript.h"
#include "os-binder.h"
float test(float a, float b){ return a + b; }
int main()
{
OS * os = OS::create();
os->setGlobal(def("test", test)); os->eval("print(test(2, 3))"); os->release();
return 0;
}
See osbind MSVC example project and more examples included in repository.
Userdata
An userdata allows arbitrary C++ or C data to be stored in ObjectScript variables. It's used to represent new types created by an application program or a library written in C++ or C.
Operators
ObjectScript has the following types of operators
- Assignment operator
- Comparison operators
- Arithmetic operators
- Bitwise operators
- Logical operators
- String operator
- Special operators
Assignment and Multiple assignment operator
Assignment is the basic means of changing the value of a variable or a object property:
a = "Hello" .. " world!"
t.n = t.n + 1
a = b = 2
ObjectScript allows multiple assignment, where a list of values is assigned to a list of variables in one step. Both lists have their elements separated by commas
a, b = 1, 2
the variable a gets the value 1 and b gets 2. In a multiple assignment, ObjectScript first evaluates all values and only then executes the assignments. Therefore, you can use a multiple assignment to swap two values, as in
x, y = y, x a[i], a[j] = a[j], a[i]
ObjectScript always adjusts the number of values to the number of variables. When the list of values is shorter than the list of variables, the extra variables receive null as their values, when the list of values is longer, the extra values are silently discarded
a, b, c = 0, 1
print(a, b, c) a, b = a+1, b+1, b+2 print(a,b) a, b, c = 0
print(a,b,c)
The last assignment in the above example shows a common mistake. To initialize a set of variables, you must provide a value for each one
a, b, c = 0, 0, 0
print(a, b, c)
You can use multiple assignment simply to write several assignments in one line. But often you really need multiple assignment, for example, to swap two values. A more frequent use is to collect multiple returns from function calls
a, b = f()
f() returns two results: a gets the first and b gets the second.
To initialize a set of variables by one single value, use following syntax
a = b = c = 0
Comparison operators
=== is exactly equal to (value and type)
print(2 === "5") print(2 === "2") print(2 === 2)
!== is not exactly equal to (value and type)
print(2 !== "5") print(2 !== "2") print(2 !== 2)
All other comparison operators use fast implementation for following types: null, boolean, number. In this case arguments are converted to numbers (null converts to 0, true to 1 and false to 0) and the ones are used to make real comparison (__cmp method is not used).
If one of arguments is not one of the types: null, boolean, number then the comparison operator process result of __cmp method of specification
function __cmp(b){
}
<=> compare arguments using the specification of the __cmp method
print(2 <=> 3) print(2 <=> 2)
== is equal to
print(2 == 3) print(2 == 2)
!= is not equal to
print(2 != 3) print(2 != 2)
> is greater than
print(2 > 3) print(2 > 2)
< is less than
print(2 < 3) print(2 < 2)
>= is greater than or equal to
print(2 >= 3) print(2 >= 2)
<= is less than or equal to
print(2 <= 3) print(2 <= 2)
Arithmetic operators
All arithmetic operators use fast implementation for following types: null, boolean, number. In this case arguments are converted to numbers (null converts to 0, true to 1 and false to 0) and the ones are used to make arithmetic operation (magic method are not used).
If one of arguments is not type of: null, boolean, number then the arithmetic operation returns result of magic method called.
+ addition or call magic __add method
print(2 + 3)
- subtraction or call magic __sub method
print(2 - 3)
* multiplication or call magic __mul method
print(2 * 3)
/ division or call magic __div method
print(2 / 4)
% modulus (division remainder) or call magic __mod method
print(5 % 2)
** raises the first argument to the power of the second argument or call magic __pow method
print(25 ** 0.5)
unary + doesn't change the sign of number parameter or call magic __plus method
print(+(5)) print(+(-5))
unary - changes the sign of number parameter or call magic __neg method
print(-(+5)) print(-(-5))
++ increments (this operator is allowed only for local variables)
var a = 5
print(++a) print(a) print(a++) print(a)
-- decrements (this operator is allowed only for local variables)
var a = 5
print(--a) print(a) print(a--) print(a)
Note: there is no + operator for string but you could implement + as concatenation for example
function String.__add(b){
return this .. b
}
print "foo" + 5
Bitwise operators
All bitwise operators use fast implementation for following types: null, boolean, number. In this case arguments are converted to numbers (null converts to 0, true to 1 and false to 0) and the ones are used to make bitwise operation (magic method are not used).
If one of arguments is not type of: null, boolean, number then the bitwise operation returns result of magic method called.
& is bitwise AND, returns a one in each bit position for which the corresponding bits of both operands are ones (or call magic __bitand method)
a = 6
b = 3
print(a & b)
| is bitwise OR, returns a one in each bit position for which the corresponding bits of either or both operands are ones (or call magic __bitor method)
a = 6
b = 3
print(a | b)
^ is bitwise XOR, returns a one in each bit position for which the corresponding bits of either but not both operands are ones (or call magic __bitxor method)
a = 6
b = 3
print(a ^ b)
unary ~ is bitwise NOT, inverts the bits of its operand (or call magic __bitnot method)
a = 6
print(~a)
<< is left SHIFT, shifts a in binary representation b bits to the left, shifting in zeros from the right (or call magic __lshift method)
a = 6
b = 1
print(a << b)
>> is right SHIFT, shifts a in binary representation b bits to the right, discarding bits shifted off (or call magic __rshift method)
a = 6
b = 1
print(a >> b)
Logical operators
&& is logical AND, returns a if it can be converted to false, otherwise, returns b. Thus, when used with boolean values, && returns true if both operands are true, otherwise, returns false.
a = 6
b = null
print(a && b)
|| is logical OR, returns a if it can be converted to true, otherwise, returns b. Thus, when used with boolean values, || returns true if either operand is true, if both are false, returns false.
a = 6
b = null
print(a || b)
?: is conditional operator, returns a if conditional can be converted to true, otherwise, returns b.
a = 6
b = 3
print(a < b ? a : b)
unary ! is logical NOT, returns false if its single operand can be converted to true, otherwise, returns true.
a = 6
print(!a)
Note: use !! to convert value to boolean type.
String operator
.. is string concatenation (see Convert to string)
a = 5
b = true
print(a .. b)
Special operators
in retuns a boolean value that depends on second argument type.
If the second argument is object then the in returns true if the specified property is in the specified object.
If the second argument is array then the in returns true if the specified value is contained in the specified array.
var a = {1: "one", second: "two"}
print(1 in a) print("second" in a) print("one" in a)
var b = [1, "one", "two"]
print(1 in b) print("second" in b) print("one" in b)
Actually this operator returns result of a global __in function.
extends operator extends a one object by another. Usually it's used for Object Oriented Programming.
var IvanPerson = extends Person {
__construct: function(){
super("Ivan", "Petrov")
}
}
Actually this operator returns result of a global __extends function
function __extends(obj, obj2){
obj2.prototype = obj
return obj2
}
See Object-oriented programming (OOP).
delete deletes property or array index (see Properties, Getters And Setters)
a = [1, 2, 3, 4, 5]
delete a[1]
print(a)
a = {one=1, two=2]
delete a.one
print(a)
Actually this operator calls a global __delete function.
is returns a boolean value that indicates if an object is an instance of a particular class.
classA = {}
classB = extends classA {}
a = classA() b = classB() print(a is classA) print(b is classA) print(a is classB) print(b is classB) print(classA is classA) print(classB is classA) print(classA is classB) print(classB is classB)
Actually this operator returns result of a global __is function.
isprototypeof returns a boolean value that indicates if an object is a particular class or an instance of a particular class.
classA = {}
classB = extends classA {}
a = classA() b = classB() print(a isprototypeof classA) print(b isprototypeof classA) print(a isprototypeof classB) print(b isprototypeof classB) print(classA isprototypeof classA) print(classB isprototypeof classA) print(classA isprototypeof classB) print(classB isprototypeof classB)
Actually this operator returns result of a global __isprototypeof function.
unary # returns result of magic __len method. The __len method is already declared for following types
- array - returns number of elements
- string - returns length of string in bytes
- object - returns number of properties
for example
a = "abc"
print(#a) a = [1, 2, 3, 4, 5]
print(#a)
There is also length property in ObjectScript
function Object.__get@length(){ return #this }
So you can use length property as alias to # operator
a = "abc"
print(a.length) a = [1, 2, 3, 4, 5]
print(a.length)
... is rest arguments, returns array of undeclared arguments passed to function
function test(a, b){
print(...)
print(arguments)
}
test(1, 2, 3, 4, 5)
outputs
[3,4,5]
[1,2,3,4,5]
Global Variables
Global variables do not need declarations. You simply assign a value to a global variable to create it. It is not an error to access a non-initialized variable, you just get the special value null as the result
print(a) a = 10
print(a)
Usually you do not need to delete global variables, if your variable is going to have a short life, you should use a local variable. But, if you need to delete a global variable, just assign null to it
a = 10
a = null
print(a)
Environments and the Global Environment
Any reference to a global name v is syntactically translated to _E.v. Moreover, every function is compiled in the scope of an external local variable called _E, so _E itself is never a global name in a function.
Any object used as the value of _E is called an environment.
ObjectScript keeps a distinguished environment called the global environment. This value is kept at a special object in the C registry. In ObjectScript, the variable _G is initialized with this same value.
When ObjectScript compiles a function, it initializes the value of its _E upvalue with the global environment. Therefore, by default, global variables in ObjectScript code refer to entries in the global environment. Moreover, all standard libraries are loaded in the global environment and several functions there operate on that environment.
You can execute any function with a different environment using Function.applyEnv method. For example let's view code of evalEnv functions inside of std.os file:
function evalEnv(str, env){
return compileText(str).applyEnv(env || _G, null, ...)
}
If ... Else Statements
Conditional statements are used to perform different actions based on different conditions.
Very often when you write code, you want to perform different actions for different decisions. You can use conditional statements in your code to do this.
There are the following conditional statements in ObjectScript:
if statement - use this statement to execute some code only if a specified condition is true
a = 2
b = 5
if(a < b){ print 'true' }
if...else statement - use this statement to execute some code if the condition is true and another code if the condition is false
a = 7
b = 5
if(a < b){
print 'true'
}else{
print 'false'
}
if...elseif....else statement - use this statement to select one of many blocks of code to be executed
a = 5
b = 5
if(a < b){
print 'true'
}elseif(a == b){
print 'equal'
}else{
print 'false'
}
Note: you can use else if instead of elseif if you like.
For loop
There are two kind of for loop in ObjectScript. the first one is the same to C++ and some other languages
for(var i = 0; i < 3; i++){
print i
}
outputs
0
1
2
The second one is used for iteration process
for(var i, v in ["zero", "one", "two"]){
print("${i}: ${v}")
}
outputs
0: zero
1: one
2: two
See Iterators.
Break and Continue
The break statement "jumps out" of a loop.
for(var i = 0; i < 10; i++){
print i
if(i == 3) break
}
outputs
0
1
2
3
The continue statement "jumps over" one iteration in the loop.
for(var i = 0; i < 5; i++){
if(i % 2 == 0) continue
print i
}
outputs
1
3
Try, Catch, Throw
The try statement lets you to test a block of code for errors.
The catch statement lets you handle the error.
The throw statement lets you create custom errors.
function printException(e){
print e.message
for(var i, t in e.trace){
printf("#${i} ${t.file}%s: %s, args: ${t.arguments}\n",
t.line > 0 ? "(${t.line},${t.pos})" : "",
t.object && t.object !== _G ? "<${typeOf(t.object)}#${t.object.id}>.${t.name}" : t.name)
}
}
function testFunction(){
try{
var a = 1
var b = 0
var c = a / b
}catch(e){
print "\ncatch exception #1"
printException(e)
throw "error message"
}
}
try{
testFunction()
}catch(e){
print "\ncatch exception #2"
printException(e)
}
outputs
catch exception #1
division by zero
#0 ../../examples-os/try.os(14,13): testFunction, args: {}
#1 ../../examples-os/try.os(23,14): {{main}}, args: {}
#2 {{CORE}}: require, args: {"filename":"../../examples-os/try.os","required":true,"source_code_type":0,"check_utf8_bom":true}
catch exception #2
error message
#0 ../../examples-os/try.os(18,3): testFunction, args: {}
#1 ../../examples-os/try.os(23,14): {{main}}, args: {}
#2 {{CORE}}: require, args: {"filename":"../../examples-os/try.os","required":true,"source_code_type":0,"check_utf8_bom":true}
Iterators
An iterator allows you to iterate over the elements of a collection. For example
a = { null, true, 12, "0" }
for(k, v in a){
print("${k} --> ${v}")
}
outputs
0 --> null
1 --> true
2 --> 12
3 --> 0
ObjectScript compiles the above program to
a = { null, true, 12, "0" }
{
var iter_func = a.__iter();
for(var iter_valid;;){ iter_valid, k, v = iter_func();
if(!iter_valid) break;
print("${k} --> ${v}")
}
}
The first result value of iter_func indicates either valid or not valid current step, second and other return values are user used values.
Any iterator needs to keep some state between successive calls, so that it knows where it is and how to proceed from there. Closures provide an excellent mechanism for that task. Remember that a closure is a function that accesses local variables from its enclosing function.
For example, array iterator is declated as
Array.__iter = function(){
var i, self = 0, this
return function(){
if(i < #self){
return true, i, self[i++]
}
}
}
Note that iterator of function is itself. It's declared as
Function.__iter = function(){ return this }
So we can write iterator like that
var range = function(a, b){
return function(){
if(a <= b){
return true, a++
}
}
}
for(var i in range(10, 13)){
print( "i = ", i )
}
outputs
i = 10
i = 11
i = 12
i = 13
another example
Range = {
__construct = function(a, b){
if(b){
this.a, this.b = a, b
}else{
this.a, this.b = 0, a - 1
}
},
__iter = function(){
var a, b = this.a, this.b
return a <= b
? {|| a <= b && return true, a++ }
: {|| a >= b && return true, a-- }
},
}
for(var i in Range(-2, -4))
print i
outputs
-2
-3
-4
and one more example
function Number.to(b){
return Range(this, b)
}
for(var i in 5.to(7))
print i
outputs
5
6
7
Properties, Getters And Setters
A getter is a method that gets the value of a specific property. A setter is a method that sets the value of a specific property.
a = {
__color: "red",
__get@color = function(){ return this.__color },
__set@color = function(v){ this.__color = v },
}
print a.color a.color = "blue"
print a.color
Note that @ is not a special symbol, any functions and variables can contain @.
Another example
a = {
__color: "red",
__get = function(name){
if(name == "color")
return this.__color
},
__set = function(name, v){
if(name == "color")
this.__color = v
},
__del = function(name){
if(name == "color")
delete this.__color
},
}
print a.color a.color = "blue"
print a.color delete a.color
print a.color
Multi dimensional properties
ObjectScript supports a.color (or a["color"]) syntax. What about?
a[x, y] = 12
Yes, ObjectScript supports the above syntax, it's called multi dimensional properties
a = {
__matrix = {},
__getdim = function(x, y){
return this.__matrix[y*4 + x]
},
__setdim = function(value, x, y){
this.__matrix[y*4 + x] = value
},
__deldim = function(x, y){
delete this.__matrix[y*4 + x]
},
}
a[1, 2] = 5
print a[1, 2] delete a[1, 2]
print a[1, 2]
Note that __setdim method receives the first argument as new property value and other arguments as dimensional attributes of property. For example
a = {
__matrix = {},
__getdim = function(x, y, z){
return this.__matrix[z*16 + y*4 + x]
},
__setdim = function(value, x, y, z){
this.__matrix[z*16 + y*4 + x] = value
},
}
a[1, 2, 3] = 5
print a[1, 2, 3]
Empty dimensional properties
What about?
b = a[]
a[] = 2
delete a[]
ObjectScript provides following special methods __getempty, __setempty and __delempty that you can use if necessary.
Object-oriented programming (OOP)
Object-oriented programming is a programming paradigm that uses abstraction to create models based on the real world. It uses several techniques and paradigms, including modularity, polymorphism, and encapsulation.
ObjectScript is OOP language, also you can override arithmetic, bitwise, concatenation, comparison and unary operators.
Core Objects
ObjectScript has several objects included in its core, there are global variables named Object, Array, String, Number, Boolean, Function and Userdata.
Every object in ObjectScript is an instance of the Object and therefore inherits all its properties and methods.
Custom Objects
var a = {
num: 1,
__get@number: function(){
return this.num
},
__add = function(b){
return this.number + b.number
},
}
var b = extends a {
num: 2,
}
print a + b
The Class
ObjectScript is a prototype-based language which contains no class statement. Any object could be used as class.
Person = {
__construct = function(firstname, lastname){
this.firstname = firstname
this.lastname = lastname
},
__get@fullname = function(){
return this.firstname .. " " .. this.lastname
},
walk = function(){
print this.fullname .. " is walking!"
},
}
The Object (Class Instance)
To create a new instance of an object we use () operator for class variable
var p = Person("James", "Bond")
is equivalent to
var p = {}
p.prototype = Person
p.__construct("James", "Bond")
run
p.walk() print p
Inheritance
Inheritance is a way to create a class as a specialized version of class. You need to use extends operator
var IvanPerson = extends Person {
__construct: function(){
super("Ivan", "Petrov")
},
}
var p = IvanPerson()
p.walk() print p
the extends operator has syntax extends exp1 exp2 where exp1 and exp2 are any valid expressions, it's equivalent to
(function(exp1, exp2){
exp2.prototype = exp1
return exp2
})()
Encapsulation
In the previous example, IvanPerson does not need to know how the Person class's walk() method is implemented, but still can use that method. The IvanPerson class doesn't need to explicitly define that method unless we want to change it. This is called encapsulation, by which every class inherits the methods of its parent and only needs to define things it wishes to change.
Default instance properties
There is special property named __object in ObjectScript to describe default instance properties
Test = {
__object = {
a = 0,
b = [1, 2],
}
}
Test2 = extends Test {
__object = {
a = 12345
}
}
var v1 = Test() v1.b[0] = 10
var v2 = Test2() print "v1: "..v1 print "v2: "..v2 print "class: "..Test
Alternative 'this' syntax
There is alternative syntax in ObjectScript to read and write instance properties
__construct = function(firstname, lastname){
@firstname = firstname @lastname = lastname }
Simple OOP example
var vec3 = {
__construct = function(x, y, z){
@z, @y, @x = z, y, x
},
__add = function(b){
vec3(@x + b.x, @y + b.y, @z + b.z)
},
__mul = function(b){
vec3(@x * b.x, @y * b.y, @z * b.z)
},
}
var v1 = vec3(10, 20, 30)
var v2 = vec3(1, 2, 3)
var v3 = v1 + v2 * v2
print v3
outputs
{"x":11,"y":24,"z":39}
Compiling ObjectScript from source code
Clone repo
git clone git:cd objectscript
Compile ObjectScript for Linux
mkdir build && cd build
cmake -DCMAKE_INSTALL_PREFIX=$(pwd)/../ ..
make
make install
The result files (os, oscript) will be located inside of bin folder. Run ObjectScript program
oscript script.os
Note: ObjectScript uses PCRE (perl-compatible regular expression library) and cURL (client-side URL transfer library) libraries installed externally.
Compile ObjectScript for Windows
Open proj.win32\examples.sln, there are helpful projects in MSVC solution.
Resources
ObjectScript is universal scripting language, there are no compromises any more.
Thank you for your reading. Looking forward to your reply.
Evgeniy Golovin