Click here to Skip to main content
Click here to Skip to main content

Programming in ObjectScript 1.5

, 25 Mar 2013
Rate this:
Please Sign up or sign in to vote.
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.
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;
    ;{ // nested block
        var c = a; // c is block scoped variable
    }

Comments

Comment syntax is the same as in C++ and many other languages.

    // a short, one-line comment

    /* this is a long, multi-line comment
       about my script. May it one day
       be great. */

    /* Comments /* may not be nested */ 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 // environment variable, because it is not declared using var statement
    var function test(){ return 1 } // local function
    function f() // environment function
    {
      var z, r = 'foxes', 'birds' // initialized 2 local variables
      m = 'fish' // environment because it wasn't declared anywhere before
      var function child()  // local function
      {
         var r = 'monkeys'  // this variable is local and does not affect the "birds" r of the parent function
         z = 'penguins'     // the child function is able to access the variables of the parent function, this is called closure
      }
      ;{ // nested block
          twenty = 20       // environment variable, because it is not declared using var statement
          var twenty = twenty   // local variable initialized by environment variable
          var z = z         // initialize local z variable using z of parent
          z = 'bears'       // assign local z variable
          child()           // call child function of parent block
      }
      return x;             // we can use x here because it is environment
    }
    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) // calls printf with 3 arguments

Declare the argument, as variables, when you declare the function

    function myFunction(var1, var2)
    {
        return var1 + var2
    }
    print(myFunction(2, 3)) // outputs: 5

If the function call has no arguments, you must write an empty list () to indicate the call

    function test(){ print("Hello world!") }
    test() // outputs: Hello world!

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" }
    // it's equivalent to
    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 // it's OK
        ;{
            print a // it's OK
        }
        // it's incorrect to call math.round with 'a' argument here, parentheses required
        // print(2 + math.round a)
        print(2 + math.round(a)) // it's OK
    }
    test 10 // it's OK

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
  1. null, a special keyword denoting a null value
  2. boolean, either true or false
  3. number, such as 10, 0x123, 2.3, 5.7e23
  4. string, such as "Hello World!"
  5. object, such as {"one", "two", 12:"at index 12"}
  6. array, such as ["one", 31]
  7. function, such as function(){ print "Hello World!" }
  8. 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))  // outputs: null
    print(val === null) // outputs: true
    print(val === 0)    // outputs: false
    print(val == 0)     // outputs: true

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))         // outputs: boolean
    print(null ? true : false)  // outputs: false
    print(1 > 2 ? true : false) // outputs: false
    print(1 ? true : false)     // outputs: true
    print(0 ? true : false)     // outputs: true
    print("0" ? true : false)   // outputs: true
    print("" ? true : false)    // outputs: true
    print([1,2] ? true : false) // outputs: true

Number

You can write numeric constants with an optional decimal part, plus an optional decimal exponent. Examples of valid numeric constants are
    12      // decimal, base 10
    3.14    // 3.14, floating-point number
    3.14f   // 3.14, floating-point, C++ compatibility
    2.5e3   // 2500, exponent format number
    0xfe    // 254, hexadecimal, "hex" or base 16
    0123    // 83, octal, base 8
    0b110   // 6, binary, base 2

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)  // outputs: 0.93000000000000005000
    printf("%f\n", 0.94 - 0.01)     // outputs: 0.930000

    // use human friendly number format
    printf("%n\n", 0.94 - 0.01)     // outputs: 0.93

    // numbers are converted to string using human friendly format
    print(0.94 - 0.01)              // outputs: 0.93

    print(typeOf(10.5))             // outputs: number

Note: see printf, sprintf, print, echo functions.

A number has prototype of Number class

    print(10.prototype === Number) // outputs: true

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"))     // outputs: string
    print("0123456789".sub(2, 4))   // outputs: 2345

A string has prototype of String class

    print("".prototype === String) // outputs: true

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'

    // outputs: Arnold once said: "I'll be back"
    print 'Arnold once said: "I\'ll be back"'

    // outputs: You deleted C:\*.*?
    print 'You deleted C:\\*.*?'

    // outputs: You deleted C:\*.*?
    print 'You deleted C:\*.*?'

    // outputs: This will not expand: \n a newline
    print 'This will not expand: \n a newline'

    // outputs: Expressions do not ${expand} ${either}
    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"

    // outputs: three plus three is 6
    print "three plus three is ${3+3}"

    foobar = "blah"
    // outputs: the value of foobar is blah
    print "the value of foobar is ${foobar}"

    // outputs: 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
    // outputs: factorial of 10 is 3628800
    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.

    // outputs: this is a string
    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))    // outputs: 2345
    print(str[2])           // outputs: 2
    print(str[2, 3])        // outputs: 234

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 = {}; // create an empty object and store its reference in 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])  // outputs: Thursday

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))        // outputs: object

    b = "one";
    a = { [b] = "great" };  // it's the same as: a = {}; a[b] = "great"
    print(a[b])             // outputs: great
    print(a["one"])         // outputs: great
    print(a.one)            // outputs: great

    a = { one = "great" };  // it's the same as: a = { "one": "great" }
    print(a[b])             // outputs: great
    print(a["one"])         // outputs: great
    print(a.one)            // outputs: great

    a = { one = "great", two = "awesome" };
    a = { one: "great", two: "awesome" }; // JavaScript object notation is fully supported
    a = { one: "great", two: "awesome", }; // a comma after the last entry is valid

    a = {"zero", "one", "two"}
    print(a) // outputs: {0:"zero", 1:"one", 2:"two"}
    delete a[1]
    print(a) // outputs: {0:"zero", 2:"two"}

See Object-oriented programming (OOP)

An object has prototype of Object class

    print({}.prototype === Object) // outputs: true

Array

An array is a list of indexed values

    a = ["zero", "one", "two"]
    print(typeOf(a))    // outputs: array
    print(a.length)     // outputs: 3
    print(a[1])         // outputs: one
    delete a[1]
    print(a.length)     // outputs: 2
    print(a[1])         // outputs: two

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) // outputs: true

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)) // outputs: 10

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 // outputs: 1
    print y // outputs: 2 

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)   // outputs: [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() // outputs: 2
    print b() // outputs: 3
    print b() // outputs: 4

Function returns the last expression evaluated

    print({|a, b| a + b}(2, 3)) // outputs: 5

A function has prototype of Function class

    print((function(){}).prototype === Function) // outputs: true

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){ // it's equal to Number.times = function(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; // number of values that function returns
    }

    int main()
    {
        OS * os = OS::create();     // create OS instance
        os->pushCFunction(test);
        os->setGlobal("test");
        os->eval("print(test())");  // outputs: 123
        os->release();              // reelease OS instance
        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));   // use binder
        os->eval("print(test(2, 3))");      // outputs: 5
        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

  1. Assignment operator
  2. Comparison operators
  3. Arithmetic operators
  4. Bitwise operators
  5. Logical operators
  6. String operator
  7. 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                // swap x for y
    a[i], a[j] = a[j], a[i]    // swap a[i] for a[j]

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)         // 0   1   null
    a, b = a+1, b+1, b+2   // value of b+2 is ignored
    print(a,b)             // 1   2
    a, b, c = 0
    print(a,b,c)           // 0   null   null

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)           // 0   0   0

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") // outputs: false
    print(2 === "2") // outputs: false
    print(2 === 2) // outputs: true

!== is not exactly equal to (value and type)

    print(2 !== "5") // outputs: true
    print(2 !== "2") // outputs: true
    print(2 !== 2) // outputs: false

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){
        // returns negative number if this < b
        // returns positive number if this > b
        // returns 0 if this == b
    }

<=> compare arguments using the specification of the __cmp method

    print(2 <=> 3) // outputs: -1
    print(2 <=> 2) // outputs: 0

== is equal to

    print(2 == 3) // outputs: false
    print(2 == 2) // outputs: true

!= is not equal to

    print(2 != 3) // outputs: true
    print(2 != 2) // outputs: false

> is greater than

    print(2 > 3) // outputs: false
    print(2 > 2) // outputs: false

< is less than

    print(2 < 3) // outputs: true
    print(2 < 2) // outputs: false

>= is greater than or equal to

    print(2 >= 3) // outputs: false
    print(2 >= 2) // outputs: true

<= is less than or equal to

    print(2 <= 3) // outputs: true
    print(2 <= 2) // outputs: true

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) // outputs: 5

- subtraction or call magic __sub method

    print(2 - 3) // outputs: -1

* multiplication or call magic __mul method

    print(2 * 3) // outputs: 6

/ division or call magic __div method

    print(2 / 4) // outputs: 0.5

% modulus (division remainder) or call magic __mod method

    print(5 % 2) // outputs: 1

** raises the first argument to the power of the second argument or call magic __pow method

    print(25 ** 0.5) // outputs: 5

unary + doesn't change the sign of number parameter or call magic __plus method

    print(+(5))     // outputs: 5
    print(+(-5))    // outputs: -5

unary - changes the sign of number parameter or call magic __neg method

    print(-(+5))    // outputs: -5
    print(-(-5))    // outputs: 5

++ increments (this operator is allowed only for local variables)

    var a = 5
    print(++a)      // expanded to (a = a + 1), outputs: 6
    print(a)        // outputs: 6
    print(a++)      // expanded to (temp = a; a = a + 1; temp), outputs: 6
    print(a)        // outputs: 7

-- decrements (this operator is allowed only for local variables)

    var a = 5
    print(--a)      // outputs: 4
    print(a)        // outputs: 4
    print(a--)      // outputs: 4
    print(a)        // outputs: 3

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 // outputs: foo5

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) // outputs: 2

| 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) // outputs: 7

^ 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) // outputs: 5

unary ~ is bitwise NOT, inverts the bits of its operand (or call magic __bitnot method)

    a = 6
    print(~a) // outputs: -7

<< 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) // outputs: 12

>> 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) // outputs: 3

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) // outputs: null

|| 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) // outputs: 6

?: is conditional operator, returns a if conditional can be converted to true, otherwise, returns b.

    a = 6
    b = 3
    print(a < b ? a : b) // outputs: 3

unary ! is logical NOT, returns false if its single operand can be converted to true, otherwise, returns true.

    a = 6
    print(!a)     // outputs: false

Note: use !! to convert value to boolean type.

String operator

.. is string concatenation (see Convert to string)

    a = 5
    b = true
    print(a .. b) // outputs: 5true

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)           // outputs: true
    print("second" in a)    // outputs: true
    print("one" in a)       // outputs: false

    var b = [1, "one", "two"]
    print(1 in b)           // outputs: true
    print("second" in b)    // outputs: false
    print("one" in b)       // outputs: true

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)    // outputs: [1,3,4,5]

    a = {one=1, two=2]
    delete a.one
    print(a)    // outputs: {"two":2}

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()        // create new inctance of classA
    b = classB()        // create new inctance of classB
    print(a is classA)  // outputs: true
    print(b is classA)  // outputs: true
    print(a is classB)  // outputs: false
    print(b is classB)  // outputs: true
    print(classA is classA)     // outputs: false
    print(classB is classA)     // outputs: true
    print(classA is classB)     // outputs: false
    print(classB is classB)     // outputs: false

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()        // create new inctance of classA
    b = classB()        // create new inctance of classB
    print(a isprototypeof classA)   // outputs: true
    print(b isprototypeof classA)   // outputs: true
    print(a isprototypeof classB)   // outputs: false
    print(b isprototypeof classB)   // outputs: true
    print(classA isprototypeof classA)      // outputs: true
    print(classB isprototypeof classA)      // outputs: true
    print(classA isprototypeof classB)      // outputs: false
    print(classB isprototypeof classB)      // outputs: true

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)   // outputs: 3
    a = [1, 2, 3, 4, 5]
    print(#a)   // outputs: 5

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)     // outputs: 3
    a = [1, 2, 3, 4, 5]
    print(a.length)     // outputs: 5

... 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)  // outputs: null
    a = 10
    print(a)  // outputs: 10

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)  // outputs: null

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' }   // outputs: 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'
    }
    // outputs: 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'
    }
    // outputs: equal

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;;){ // infinite loop
            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       // outputs: red
    a.color = "blue"
    print a.color       // outputs: blue

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       // outputs: red
    a.color = "blue"
    print a.color       // outputs: blue
    delete a.color
    print a.color       // outputs: null

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]
        },
    }
    // it's compiled to a.__setdim(5, 1, 2)
    a[1, 2] = 5         
    // it's compiled to print(a.__getdim(1, 2)
    print a[1, 2]                               // outputs: 5
    // it's compiled to a.__deldim(1, 2)
    delete a[1, 2] 
    print a[1, 2]                               // outputs: null

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] // outputs: 5

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     // outputs: 3

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()    // outputs: James Bond is walking!
    print p     // outputs: {"firstname":"James","lastname":"Bond"}

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()    // outputs: Ivan Petrov is walking!
    print p     // outputs: {"firstname":"Ivan","lastname":"Petrov"}

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()         // create new instance of Test class
    v1.b[0] = 10
    var v2 = Test2()        // create new instance of Test2 class
    print "v1: "..v1        // outputs: v1: {"a":0,"b":[10,2]}
    print "v2: "..v2        // outputs: v2: {"a":12345,"b":[1,2]}
    print "class: "..Test   // outputs: class: {"__object":{"a":0,"b":[1,2]}}

Alternative 'this' syntax

There is alternative syntax in ObjectScript to read and write instance properties

    __construct = function(firstname, lastname){
        @firstname = firstname  // it's the same this.firstname = firstname
        @lastname = lastname    // it's the same this.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://github.com/unitpoint/objectscript.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   

License

This article, along with any associated source code and files, is licensed under The MIT License

Share

About the Author

unitpoint

United States United States
No Biography provided

Comments and Discussions

 
GeneralMy vote of 5 Pinmemberdalstorp25-Mar-13 6:18 
GeneralRe: My vote of 5 Pinmemberunitpoint27-Mar-13 7:36 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web01 | 2.8.140823.1 | Last Updated 25 Mar 2013
Article Copyright 2013 by unitpoint
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid