VEJIS

0.5 It's JavaScript!

Getting Started

How VEJIS Works

Related Links

Getting Started

You will be able to learn the basic knowledge of VEJIS in this section.

What is VEJIS

VEJIS['vedʒi:s] is a grammar level runtime framework for JavaScript. It provides a typed programming experience with powerful method overloading, enhanced classes and a simple but useful module loader.

If you work with Visual Studio 2012, just add the intellisense file of VEJIS as reference, you will find out what an amazing experience will VEJIS bring to you. Also, if you want to use a module that is developed using VEJIS, you don't even need to look up the documentation. VEJIS intellisense file will tell you almost all the information about the methods, classes and etc.

VEJIS and Visual Studio 2012

There are accurate tips almost every where if you are using Visual Studio 2012 and added VEJIS intellisense file as reference.

You may find them out from this video.

Aternative http://v.youku.com/v_show/id_XNDY0MDE3MjMy.html

Downloads

VEJIS has three planed version to download, the intellisense file is for Visual Studio 2012 and will bring you a brand new typed programming experience of JavaScript. The development version contains some debug information and is used for browser debugging. And the product version contains only necessary stuffs and will bring a better performance.

How VEJIS Works

In this section, you will be able to learn how to write JavaScript using VEJIS. Here is the notation that will be used for describing functions provided by VEJIS later.

ReturnType functionName(TypeA paramA, TypeB paramB)

Detailed rules:

Methods

Method overloading is one of the most important features of VEJIS, even though it had ever been removed in version 0.3 considering performance issues. But then I realized, even though this feature did have performance issues, if we use it properly, the negative influence can just be ignored.

_([Type... Types], Function fn)

VEJIS provides a powerful method overloading feature, you can define the types of parameters as well as “this” pointer and return value. The value of type can be either of the classic constructor, a VEJIS class/interface or a VEJIS type mark.

Here are some examples that can help you understand how to use this feature.

//Create a VEJIS method.
var test = _(String, function (str) {
    return str.length;
});

//Add an overload with a certain return type.
test._(Number, Number, function (a, b) {
    //Return the addition of two numbers.
    //If you are using VS 2012 as IDE, and added reference of VEJIS intellisense file,
    //you will be able to see the description for each overload and parameters like below.
    //a: the first number.
    //b: the second number.
    return a + b;
}).as_(Number);

//Add an overload with a certain "this" pointer type.
test._(Number, function (num) {
    return this.length % num;
}).with_(String);
//And of course you can use as_ and with_ the same time.

//Add an overload with a certain "this" pointer value.
test._(function () {
    return this.x + this.y;
}).bind_({
    x: 123,
    y: 321
});
//bind_ and with_ can't be used at the same time on a single overload.

//And you can call these overloads like below.
test("tell me my length!"); //returns 18
test(123, 456); //returns 579
test.call("abcdefg", 2); //returns 1
test(); //returns 444
            

Meanwhile, VEJIS provides opt_ and params_ to enhance the overloading experience.

opt_(Type Type, [Object? defaultValue])

You can write optional parameters at the beginning, middle and end, but you must write them together with no other types between them.

And when there are more than one optional parameters in an overload, you can’t skip one optional parameter and try to pass a value to the latter one.

The default value of opt_ is optional only when the type is Number, Integer, String, Boolean, Object, PlainObject, Array and List(...), whose default values are 0, 0, “”, false, {}, {}, [] and [], or when the type is nullable, see nul_.

//Create a method with optional parameters.
var test2 = _(opt_(Boolean, true), opt_(Number), Number, function (mark, n1, n2) {
    var num = n1 + n2;
    return mark ? num : -num;
});

//And you can call this overload like below.
test2(false, 10, 11); //returns -21
test2(true, 12); //returns 12
test2(13); //returns 13
            

params_(Type Type)

There can be at most one group of variable-length parameters used in an overload, and if there are optional parameters, it must follow the optional ones.

//Create a method with variable-length parameters.
var test3 = _(params_(String), function (strs) {
    return strs.join(", ");
});

//Add an overload with both optional and variable-length parameters.
test3._(Number, opt_(Boolean), params_(String), function (num, trigger, strs) {
    console.log(num);
    console.log(trigger);
    return strs.join(", ");
});

//To call the first overload, you can try something like below.
//These two ways are almost equal, but if both of them matches the parameter types of the same overload, the first one is prior.
test3(["a", "bc", "def"]);
test3("a", "bc", "def");
            

VEJIS provides a powerful way to create and manage classes, and these classes created are free to be used with method overloading.

class_([String name], Function ClassBody)

Let's go through the basic usage of this feature first.

var MyClass = class_("MyClass", function () {
    //Defining "name" parameter will make intellisense for VS work better, it will also improve debugging experience.
    this.pre = undefined;
    this.test = _(String, function (str) {
        console.log(this.pre + str);
    }).bind_(this);

    //Define a constructor.
    this._(String, function (pre) {
        //You can also add descritpion for a constructor here.
        //pre: and description for parameter.
        this.pre = pre;
    });

    //Define another constructor.
    this._(Number, function (num) {
        this.pre = num.toString() + ". ";
    });
});

var o1 = new MyClass("1. ");
var o2 = new MyClass(2);

o1.test("hello"); //logs "1. hello"
o2.test("world"); //logs "2. world"
            

Prototype and public/private static objects.

var A = class_(function (pub, pri) {
    this.set = _(nul_(Object), function (value) {
        pri.priValue = value;
    });

    //BTW, you can also define a method in a class this way.
    this._("get", function () {
        return pri.priValue;
    });

    this._(function () { });
    this._(String, String, function (s1, s2) {
        pub.pubValue = s1;
        pri.priValue = s2;
    });
}).prototype_({
    test: _(function () {
        //Method defined with prototype.
        console.log("hello!");
    })
}).prototype_(function () {
    this._("test2", function () {
        //Method defined with prototype.
        console.log("hello 2!");
    });
}).static_(function () {
    //Define static public properties, optional.
    this.public_({
        pubValue: "pub"
    });

    //Define static private properties, optional.
    this.private_({
        priValue: "pri"
    });
});

//The static public properties will be copied to the class "A".
//A.pubValue == "pub";

var a1 = new A();
var a2 = new A();

a1.set(123);
a2.get(); //returns 123

//Actually, if you only want private static properties, you can use something like this.
var AnotherClass = class_(function (pub, pri) {
    //code...
}).static_({
    priValue: 'pri'
});
            

And VEJIS makes inheriting very easy. Assuming that we have already a class A defined as the code above.

var B = class_(function (pub, pri, sup) {
    this.getFromB = function () { return pri.priValue; };

    this._(function () { });
    this._(String, String, function (s1, s2) {
        //You can also call A's constructor.
        sup(s1, s2);
    });
}).inherit_(A);

var a = new A();
var b = new B();

//Call the method defined in class A from the two instances.
a.set(987);
b.set(789);

a.get(); //returns 987
b.get(); //returns 789
b.getFromB(); //returns 789
a.test(); //logs "hello!";
b.test(); //logs "hello!";
a.test2(); //logs "hello 2!";
b.test2(); //logs "hello 2!";
            

This example shows some relationship of the private objects in class "A" and class "B": they are individual. And so are the public objects. So, don't worry about your changes to a new class on the two objects would influence the old ones (the premise is, you are using the objects "pub" and "pri").

Besides, we can also use the factory way to create an instance of a class. What we need is just return an object or a function.

//Define a factory class.
var StringMap = class_("StringMap", function () {
    var hasOwnProperty = Object.prototype.hasOwnProperty;
    var map = {};

    //Return a function.
    return function (key, value) {
        if (arguments.length == 2)
            map[key] = value;
        return hasOwnProperty.call(map, key) ? map[key] : undefined;
    };
});

//Use the same way to create a new instance as non-factory classes.
var map = new StringMap();

map("abc", 123);
map("abc"); //returns 123
is_(map, StringMap); //returns true
            

interface_([String name], PlainObject body)

I won't introduce what's interface, and let's meet the examples directly.

//Define an interface by a plain object specifying the type of each properties.
var BaseI = interface_({
    name: String,
    age: Number
});

//Inherit from another interface.
var I = interface_("I", {
    from: String,
    say: Function
}).inherit_(BaseI);

//Define a method with a parameter, of which the type is I.
var fn = _(I, function (i) {
    i.say(i.name, i.age, i.from);
});

//Call it.
fn({
    say: function (name, age, from) {
        console.log("Hi, I am " + name + " from " + from + ". And I am " + age + " years old.");
    },
    name: "VILIC",
    age: 20,
    from: "China"
});
            

A VEJIS class can of course implement an interface. If implement_ feature is used, we must ensure every item defined in the interface is implemented. Otherwise it will throw an error.

var MyClass = class_("MyClass", function () {
    this.name = undefined;
    this.age = undefined;
    this.from = undefined;

    this._(String, Number, String, function (name, age, from) {
        this.name = name;
        this.age = age;
        this.from = from;
    });

    this.say = _(function () {
        console.log("Hi, I am " + this.name + " from " + this.from + ". And I am " + this.age + " years old.");
    }).bind_(this);
}).implement_(I); //implement_ should be the last feature called when create a class.
            

enum_([String... eles]) +1 overload

The enum_ is used to declare an enumeration, a distinct type consisting of a set of named constants called the enumerator list.

By using this feature, we can make our code more readable.

Basic usage.

//Define an enum type. It support at most 32 elements.
var Day = enum_("mon", "tue", "wed", "thu", "fri", "sat", "sun");
var today = Day.sun;

switch (today) {
    case Day.sat:
    case Day.sun:
        console.log("It's weekend!");
        break;
    default:
        console.log("It's weekday.");
        break;
}
            

The code above can also be written as this.

var Day = enum_("mon", "tue", "wed", "thu", "fri", "sat", "sun");
var weekends = Day.sat | Day.sun; //use bitwise operator | to make things easier.
var today = Day.sun;

if (today.test(weekends)) //use test method to check weather the value matches.
    console.log("It's weekend!");
else
    console.log("It's weekday.");
            

enum_(String name, String[] eles)

It is suggested to give the enum type a name.

//Give the enum type a name is always a better way.
var Day = enum_("Day", ["mon", "tue", "wed", "thu", "fri", "sat", "sun"]);
            

nul_(Type Type)

Sometimes we might want to allow an argument to be null, and you can use nul_ when defining the parameter type.

//Create a method with nullable parameter type.
var fn = _(nul_(Function), function (callback) {
    if (callback)
        callback();
    else
        console.log("No callback...");
});

//There are many situations that you might use opt_ and nul_ together.
fn._(opt_(nul_(Object)), String, function (o, str) {
    if (o)
        console.log("O! " + str);
    else
        console.log("X! " + str);
});

//Call these overloads.
fn(null);
fn(function () {
    console.log("Hi!");
});
fn("Foo!");
            

What we should understand is the return value of nul_ actually represents a type, and it's different from opt_ or params_. So, something like nul_(opt_(...)) will not work correctly.

delegate_([String name], [Type... Types], Function? body)

Similar to nul_, delegate_(...) also represents a type. So you can use it wherever a type can be used in.

var Say = delegate_(String, Number, String, function (name, age, from) { });

var I = interface_("I", {
    name: String,
    age: Number,
    from: String,
    say: Say //replace the previous Function with the delegate Say.
});

var fn = _(I, function (i) {
    i.say(i.name, i.age, i.from);
    //Use a delegate is also mainly for improving intellisense and debugging experience.
});

//Also you can use with_/bind_, as_ as when you define a VEJIS method.
var D = delegate_(Boolean, null).with_(String).as_(Number);
            

I guess everyone who's interested in VEJIS knows why we need modules. So, again we'll go straight to examples.

use_([String... names], Function handler)

When the modules specified in use_ are ready, they will be passed as arguments to the handler in order.

To use a module there have to be a way to create a module.

module_(String name, Function builder) +1 overload

From the examples below, you will understand how they work.

//Use the modules that will be defined later.
//Once the modules are ready, the handler will be called.
use_("m-a", "m-b", function (a, b) {
    var o = new a.MyClass();
    console.log(a.name, o.abc);
    b.test();
});

//Create a module. And "this" point is just the module object.
module_("m-a", function () {
    this.name = "guess what...";

    //this.class_(Strng name, Function ClassBody);
    this.class_("MyClass", function () {
        this.abc = "abc";
    });

    //The code above equals:
    //this.MyClass = class_("MyClass", ...);

    //The methods below are similar:
    //this.delegate_(String name, [Type... Types], Function? body);
    //this.enum_(String name, String[] eles);
    //this.interface_(String name, PlainObject body);
});

//Create another module.
module_("m-b", function () {
    this.id = 123;
    this.test = function () {
        console.log(this.id);
    };
});
            

module_(String name, String[] parts, [Function? builder])

Sometimes we may want to put the code of one module into several files, and this overload may help you out.

//The handler won't be called until the module and its parts are all loaded.
use_("main", function (main) {
    main.test();
});

//Create a module names main, and declare the names of its parts.
module_("main", ["part-1", "part-2"], function () {
    this.p1 = "p1 not ready.";
    this.p2 = "p2 not ready.";

    this.test = function () {
        console.log(this.p1, this.p2);
    };
});

//Define part-1 using a "/".
//The builder of a part will only be called when the main one is ready.
module_("main/part-1", function () {
    this.p1 = "p1 ready!";
});
                
//Define part-2 and its parts.
module_("main/part-2", ["part-2-1", "part-2-2"], function () {
    this.p2 = "p2 ready!";
});

module_("main/part-2/part-2-1", function () {
    this.p2 += " p2-1 ready!";
});

module_("main/part-2/part-2-2", function () {
    this.p2 += " p2-2 ready!";
});

            

If you know that when a certain code is executed, some modules have been ready, you can also use import_ to get the module objects.

import_(String name)

Assuming we have a module defined as code above, you can use import_ this way.

//If module main is not ready, import_ will return undefined and log a message.
var main = import_("main");

main.test();
            

Of course, when we use modules in practice, they are usually separated in files. You can use the feature below to load a file.

require_([String... srcs]) +1 overload

//Simply require two files.
require_("script/a.js", "script/b.js");
            

require_(String baseDir, String[] srcs)

//When you want to load files in the same directory, you can try this.
require_("script/", ["a.js", "b.js"]);
            

Extended types

There are several very useful types built in VEJIS, and they are defined as global variables. Here're some details about these types.

IList

Represents an interface defined as below.

var IList = interface_("IList", {
    length: Integer
});
            

Integer

Represents number that mods 1 equals 0.

List(Type Type)

Represents an instance of Array, of which the elements are instances of the type given.

E.g. the following statement returns true.

is_(["a", "b"], List(String)); //the array can be empty.
            

PlainObject

Represents an object that makes the test function below return true.

function test(object) {
    return !!object && typeof object == "object" && object.constructor == Object;
}
            

Type

Represents a type, such as a classic function, a VEJIS class/interface, and the return value of nul_/delegate_, etc.

Others

Boolean is_(Object? object, Type Type)

This function is the core of VEJIS, and I suggest you using this function instead of the operator "instanceof" when using VEJIS. To avoid confusing, I need to point out that the statements below return true.

is_(0, Number);
is_("", String);
is_(true, Boolean);

//Asumming a is an instance of A, and A is inherited from B.
is_(a, B);

//Asumming i is an object that contains all the items defined in interface I.
is_(i, I);
            

Boolean for_(IList array, delegate(Object value, Integer i, Integer length) handler) +1 overload

Traverse an array, returns true if the traversal is completed.

array: the array to be traversed.
handler: the handler, return false to break traversal; and return a number to specify the increasement of i.

var arr = [1, 2, 2, 3, 3, 3];

var complete =
for_(arr, function (value, i, length) {
    if (i > length / 2)
        return false; //return false to break the traversal.
});

if (complete)
    console.log("completed");
else
    console.log("not completed");

for_(arr, function (value, i) {
    if (value == 2) {
        arr.splice(i, 1);
        return 0; //return 0 to let i keep its value, because we have deleted an item.
    }
});

console.log(arr);
            

Boolean for_([IList... arrays], Function handler)

Sometimes we may want the full permutation of several arrays, this overload will help you out.

arrays: the array to be traversed.
loop: the handler, the full permutation of the arrays given will be passed in as arguments, return false to break traversal.

for_([1, 2], [3, 4], function (a, b) {
    console.log(a, b);
});
//logs:
//1 3
//1 4
//2 3
//2 4
            

Boolean forin_(Object object, delegate(Object value, String name) handler)

Traverse the properties of an object, returns true if the traversal is completed.

object: the target object.
handler: the handler, return false to break traversal.

//An object for traversing.
var object = {
    p1: "abc",
    p2: 123
};

var complete =
forin_(object, function (value, i) {
    if (typeof value == "string")
        return false; //return false to break the traversal.
});