Joo: A Smooth Curved Language For the Web

Abstract. Development of Web-applications is labor-consuming task. Difficulty is partially dependent on restrictions of scripting language JavaScript which used to program behaviour of Web-pages. The reasons of it are clear: with language complication become difficult to reveal all thin places in architecture of VM engine that can lead to user problems, in the form of trojans, viruses and other infections that use these security holes. On another hand, JavaScript obstruct a developer from writing sophisticated app with ten of KLOC as he knows at that point difficulty of maintaining increased dramatically due to lacks of type control and unhandled errors. JooScript aims to make this curve smoother by providing at start basic constructive components, metaprogramming, common errors handling and strict types, all within JavaScript bounds.


 1. Introduction

We would say that Joo is

a new language for creating web documents with almost any sort of content, from simple formatted to text to complex interactive applications,

but honestly Joo is just JavaScript on steroids. What JavaScript do, JooScript can do as well in more robustness manner.

Joo pronounced as [dʒuː], but it will no mistake saying Yoo.

 2. Current status

JooScript currently on its own halfway. Core components and concept are mostly ready but it required disciplined work toward fixing bugs and writing documentation. We also listen to feedbacks to choose right way of futher development.

We would recommend to use Joo for complex task because it speed up creative process a lot. That is no need to worry about accidental errors, just write down your ideas.

It also has stable basis which is not going to be changed any time soon.

As for us, we used Joo for our main project so that we well tested it and carefully designed.

 3. Some highlights

 4. Usage

Download jooscript.js and add it into your web-page:

<script type="text/javascript" src="jooscript.js"></script>

Additionaly JooScript parameters can be changed in this way:

joo_settings({
    compat_mode: true,
    runtime_typing: false
})

Had jooscript.js used in parallel with MooTools (known for its use global object Type as well) or other JS-framework, compatibility mode required to avoid problems. In compat mode all internal classes available only within module context. By default compat mode is on.

By default runtime typing is on and while it is useful during development cycle (you will be warned of common programmer mistakes), it is required that it will be switched off in production.

 5. Security and compatibility

One of distant goals was ability to load third-party code within sandbox mode. To achieve this we have to keep compatibility with even outdated platforms such as old Internet Explorers. So that unless don't used fancy new property descriptors, code will be fully compliant, otherwise only Firefox will properly support all features of JavaScript 1.8 (test it).

 6. Metaprogramming and new approach to the code construct

JooScript is introducing new architecture capabilities and metaprogramming for JS developers. This approach was inspired by a few mature languages (Python one of them).

With no extra words lets look at generic example of a module skeleton:

// Create first Module
$Unit(function(unit, root, glob, None){

    // Importing required classes
    unit.ImportFrom("acme", ["stars.*", "beings.*", "stuff.*"])

    // Create package
    this.Package("acme.milkyway.solarsystem", function(solarsystem){

        // Create our first class and bind it to the group namespace
        solarsystem.Earth = unit.buildClass(

            // class name
            "Earth", 

            // list of ancestors
            [unit.Rivers, unit.Birds, unit.Animals, unit.Human],

            // Class role (specification), where are declared all class props and its configuration.
            // Idea of role syntax come from ECMAScript 5'th property descriptors and mostly related
            // to it as opposite values. For example "internal" descriptor = true is a shortcut 
            // for enumerable, writable and configurable descriptors = false, hidden = true is
            // enumerable = false and so on ... By default  "public" descriptor is true.
            // It is also required that type of a property is defined.
            // Role record can be string, number or object.
            {
                "name" : "String",
                "earth_init": {
                    type: "Function",
                    constructor: true,
                    argumentType: ["String", "Number", "Number", "Number"],
                    defaultArguments: [1800, 70, 20],
                    restArgument: true,
                    docs: "Earth constructor"
                }
            },

            // class body, within it this context is class prototype
            function(__class__){
                // initialize properties with default value (it is required)
                this.name = None
                // constructor
                this.earth_init = function(self, name, rivers, birds, animals, args) {
                    // initialize ancestors 
                    self.rivers_init(rivers)
                    self.birds_init(birds)
                    self.animals_init(animals)
                    self.name = name
                    trace(__class__.className, "was created. Rest arguments", args)
                }
            },

            // Last but not least set a parent of the Earth
            unit.Sun
        );
    },{
        // Export list
        Earth : "Class"
    })

    // Finally, test our example
    unit.Import(["acme.milkyway.solarsystem.Earth"])
    // Create a new instance of the Sun
    var star = unit.Sun()
    // Create a new instance of the Earth
    var earth = unit.Earth("Terra", 5300, 940, 520, 80, 15)
    var man = earth.createHuman()
    trace("His name is " + man.name)
    setTimeout(function(){
        // do nothing
    }, 60*60*24*7)
})

(run it)

Although the code above is an intermediate layer between JooScript and JavaScript, jooscript.js can be used as standalone library without compiler. And in Joo syntax my skeleton will looks like this:


from acme import stars.*, beings.*, stuff.*

package acme.milkyway.solarsystem 
{
    export Earth

    class Earth(Rivers, Birds, Animals, Human) childof Sun {
        role Earth {
            public prop name typeof String: "",
            public constructor earth_init( name typeof String, 
                                           rivers typeof Number,
                                           birds typeof Number,
                                           animals typeof Number ): 
                                           "Earth constructor"
        }
        name = None
        function earth_init(self, name, rivers=1800, birds=70, animals=20, *args){
            self.rivers_init(rivers)
            self.birds_init(birds)
            self.animals_init(animals)
            self.name = name
            trace("Earth was created. Rest arguments", args)
        }
    }
}

// Finally, test our example
import acme.milkyway.solarsystem.Earth
// Create a new instance of the Sun
let star = Sun()
// Create a new instance of the Earth
let earth = Earth("Terra", 5300, 940, 520, 80, 15)
let man = earth.createHuman()
trace("His name is " + man.name)
setTimeout(function(){
    // do nothing
}, 60*60*24*7)

(run it) - sadly, compiler is not ready yet.

 7. Types

Types in JooScript are the result of several years of strong effort and research. We believe that this work will be extensively used by many developers.

Types in JooScript are feature of language design, that is not a hack. Types has static nature, that is once defined, it cannot be substituted.

Since the purpose of this work were fully pragmatic, there is no academic paper which would describe internal mechanic. For technical details please refer to source code.

Here is a simple example of "typed properties":

$Unit(function(unit, _, _, None)
{
    // debug helper
    function raises(f){
        try{
            f()
        } catch(e) {
            trace(e)
        }
    }

    // i will create new class with virtual props "name" and "age",
    // note that getter and setter have to be name of internal function
    unit.buildClass("Person", [], 
        {
            person_init: {
                type: "function",
                constructor: true
            },
            name: {
                type: "string",
                get: "get_name",
                set: "set_name"
            },
            age: {
                // i used uint type for age to catch arithmetic errors, 
                // e.g. function returned unexpected NaN (which has Number type by the way)
                type: "uint",
                get: "get_age",
                set: "set_age"
            }
        },

        function()
        {
            // private

            var roster = []

            // public

            this.person_init = function(self, name)
            {
                self.id = roster.length
                roster.push({name: None, yearOfBirth: None})
                self.name = name
            }

            this.get_name = function(self)
            {
                return roster[self.id].name
            }

            this.set_name = function(self, name)
            {
                roster[self.id].name = name
            }

            this.get_age = function(self)
            {
                var age = (new Date).getFullYear() - roster[self.id].yearOfBirth
                // test for age is not NaN 
                return age && Uint(age)
            }

            this.set_age = function(self, age)
            {
                roster[self.id].yearOfBirth = (new Date).getFullYear() - age
            }
    })

    var bob

    // Person constructor required at least one passed argument, 
    // so that next call will raise an error
    raises(function(){
        bob = unit.Person()
    })

    bob = unit.Person("Bob")

    // if we are trying to get Bob's age before setting his
    // date of birth, we got only type exception due to calculation error
    raises(function(){
        bob.age
    })

    // in the same time assigning value with inappropriate type will fail
    raises(function(){
        bob.age = 56
    })

    // age property required integer number to assign
    bob.age = Uint(56)

    // check results
    trace(bob.name, bob.age)
})

(run it)

 8. Casting to type

Any object in JooScript can be a first-class type. For example, to cast anonymous function to the class type, can be used following code:

// class Anonymous
function Anonymous(context, args){
    // to check whether function is called from class constructor
    // (it is required to keep parenting chain intact)
    if( context === Anonymous ) {
        this.abc = args[0]
    }
}
// substitute function constructor with class constructor
Anonymous.new = Class.new
// register class to change object type
Class.registerClasses([Anonymous])
// finally static method 'new()' is a class constructor, and Anonymous is a new unique type
var anon = Anonymous.new("abc")
trace( Type.is_a(Anonymous, Class) ) // => true
trace( Type.is_a(anon, Anonymous) ) // => true

(run it)

 9. Signals processing

In JooScript has been implemented appropriate messages queue as implementation of this idea:

when "signalname" do something
once "signalname" in thread do somewhat
raise "signalname"

Compiler will translate Joo syntax into JavaScript ...

Signal.when("signalname", something)
Signal.once.call(thread, "signalname", somewhat)
Signal.raise("signalname")

Where something and somewhat is a signal processors and thread is a class instance.

A cute demonstration of using Signal class as ancestor:


$Unit(function(unit) {

    // She is Alice
    unit.buildClass("Alice", [unit.Signal], 
        {
            "alice_init": {
                type: "Function",
                constructor: true
            }
        },
        function() {
            this.alice_init = function(self){
                self.signal_init()
                self.addSignalListener("sayhello", function(msg){
                    if( Type.is_a( msg.from, unit.Bob ) ) {
                        trace("Alice: Hello!")
                        msg.from.raise("hello")
                    }
                })
            }
    })

    // He is Bob
    unit.buildClass("Bob", [unit.Signal], 
        {
            "bob_init": {
                type: "Function",
                constructor: true
            }
        },
        function() {
            this.bob_init = function(self, she){
                self.signal_init()
                self.addSignalListener("hello", function(msg){
                    trace("Bob: That's all she said.")
                })
                she.raise({type:"sayhello", from:self})
            }
    })

    // They have a short conversation
    unit.Bob( unit.Alice() )

})

(run it)

You can use also global events contentloaded and initialize:

Signal.once("contentloaded", function(){
    // fired once after DOMContentLoaded event
})
Signal.once("initialize", function(){
    // fired once after DOMContentLoaded event and after all 
    // internal initialization routines
})

Note: if application running on Node these events will be triggered only internally. You need to dispatch them at the bottom of script. As for browser, module will be called after contentloaded and package after initialize signal.

One more thing is that signals are fired immediately, that is they are not postponed.

In sum, using Signals are good way to avoid regression while internal logic changing frequently. Just open new channel and send messages between different persons of your program. If they were not respond, it's not a big problem.

 10. Dictionary

Dictionary is a new underlying structure which combined features both of array and object. Similar type can be found in PHP, Perl and Python. Now in JavaScript.

$Unit(function(unit)
{
    // Create dict from pairs [key, value]
    var lib = unit.Dictionary([
        ["0345453743", {
            title: "The Ultimate Hitchhiker's Guide to the Galaxy", 
            author: "douglasadams"}],
        ["0545139708", {
            title: "Harry Potter and the Deathly Hallows", 
            author: "jkrowling"}],
        ["014028334X", {
            title: "One Flew Over the Cuckoo's Nest", 
            author: "kenkesey"}],
    ])

    // Create from object
    var authors = unit.Dictionary({
        "douglasadams": {
            "fullname": "Douglas Adams"
        },
        "jkrowling": {
            "fullname": "J.K. Rowling"
        }
    })

    function searchBook(queryISBN)
    {
        var result
        for( var i=0; i<lib.length; i++)
        {
            var bookISBN = lib.keyAtIndex(i)
            var book = lib.valueAtIndex(i)
            // Important difference from array is that dict returns exception if such
            // entry were not found
            var author
            try {
                author = authors.__get__(book.author).fullname
            } catch(e) {
                if( unit.Type.is_a(e, unit.KeyError) ){
                    author = "Unknown"
                } else {
                    throw e
                }
            }
            if( bookISBN == queryISBN )
            {
                result = {
                    author: author,
                    title: book.title
                }
            }
        }
        return result
    }

    var book = searchBook("014028334X")
    trace("Author:", book.author)
    trace("Title:", book.title)
})

(run it)

 11. DummyXML - XML constructor for dummies :)

DummyXML allow construct XML from objects and read XML streams easily than ever.

$Unit(function(unit)
{
    unit.Import(["dummyxml.*"])

    XML.prettyPrinting = false

    var contacts = unit.Dictionary({
        "Milagros Pokorny": "poko1004@wp.pl",
        "Guy Rippe": "rippy@foolkick.com",
        "Erik Leja": "leja@lazyelephant.org"
    })

    unit.contacts_count = contacts.length
    var xml = unit.DummyXML("<contacts total={contacts_count}/>")

    contacts.forEachPair(function(pair){
        var name = pair[0]
        var email = pair[1]
        var person = xml.append("<person/>")
        person.append("<name/>").append(name)
        person.append("<email/>").append(email)
    })

    xml = unit.DummyXML(xml.toXMLString())

    trace("Total contacts:", xml.attr("total"))
    xml.forEachElm(function(person){
        trace("Name:", person.elm("name").text())
        trace("Email:", person.elm("email").text())
    })
})

(run it)

Although we plan to extend it with some great ideas from Curl[2], DummyXML now usable for basic things like producing and reading HTML and XML documents.

To use DummyXML, load dummyxml.js package into your Web-page.

 12. Client vs. Server Side Processing

Because Joo is written in JavaScript it can be used to perform both client side and server side processing. For example it can be used for serving HTML documents on Node.js instance.

 13. Summary

We could cite the Curl authors[2]:

Producing an interactive program/document is much easier when the appropriate level of abstraction can be used for all components without having to cross language and representation barriers.

This is about Joo: we are trying to incorporate language and representation and solve conflicts between them in a natural and unobtrusive way.

Bundle package

Plus W3 shim for Internet Explorer. Call w3c(node) to be consistent.

Links

References

  1. What is a smooth curve?
  2. Curl: A Gentle Slope Language for the Web

Development

For quick start just load jooscript_debug.php into web-page.

For compiling you need basic GNU tools (it`s available in any Linux distro by default) and UglifyJS. Then type in command line:

git clone git://github.com/buzzilo/jooscript-basics.git
cd jooscript-basics
make
make tests

You are ready!

Authors and contacts

License

JooScript released under the MIT License.

Post Scriptum

Wanna to know more? Read the sources! I'm seriously, source code are worth to reading if you want to figure out how it works. I do my best but unfortunately I cannot spend half of my time on documenting all of features.

MiniChangeLog

7cc1cbe	Evgeny	2011-09-02	Updated tests; clean up and release 0.5 alpha 6
9d1c4bc	Evgeny	2011-09-02	Added Function::bind shim
f8b678a	Evgeny	2011-09-01	Implemented XML flags ignoreComments and prettyPrinting
d4fc5f6	Evgeny	2011-09-01	Updated initialization routine
70f61d0	Evgeny	2011-09-01	Group w3c renamed to org.w3
598dd9d	Evgeny	2011-08-31	Store ancestors in Dict
348c5ae	Evgeny	2011-08-31	Fixed Float repr; added virtual object property (with getter and setter)
7fb5ac3	Evgeny	2011-08-04	Added XML.ignoreWhitespace instruction
02dceda	Evgeny	2011-08-03	Removed debug hints
2e4c8e0	Evgeny	2011-08-03	Joo and Node became friends