Overview

RapydScript Documentation

What is RapydScript?

RapydScript is a pre-compiler for JavaScript, similar to CoffeeScript, but with cleaner, more readable syntax. The syntax is mostly Python, but allows JavaScript as well. This project was written as an alternative to Pyjamas for those wishing Python-like JavaScript without the extra overhead and complexity Pyjamas introduces.

RapydScript allows to write your front-end in Python without the overhead that other similar frameworks introduce (the performance is the same as with pure JavaScript). To allow this, RapydScript leverages PyvaScript (a compiler designed to do nothing more than add Pythonic syntax sugar to JavaScript), adding some much needed improvements. To those familiar with PyvaScript, the best way to describe RapydScript is PyvaScript++ (it really is like comparing C++ to C in terms of what it brings to the table). To those familiar with CoffeeScript, RapydScript is like CoffeeScript with syntax (and some features) of Python. To those familiar with Pyjamas, RapydScript brings many of the same features and support for Python syntax without the same overhead. Don't worry if you've never used either of the above-mentioned compilers, if you've ever had to write your code in pure JavaScript you'll appreciate RapydScript. RapydScript combines the best features of Python as well as JavaScript, bringing you features most other Pythonic JavaScript replacements overlook. Here are a few features of RapydScript:

  • classes that work and feel similar to Python
  • optional function arguments that work similar to Python
  • inheritance system that's both, more powerful than Python and cleaner than JavaScript
  • support for object literals with anonymous functions, like in JavaScript
  • ability to invoke any JavaScript/DOM object/function/method as if it's part of the same framework, without the need for special syntax
  • variable and object scoping that make sense (no need for repetitive 'var' or 'new' keywords)
  • ability to use both, Python's methods/functions and JavaScript's alternatives
  • similar to above, ability to use both, Python's and JavaScript's tutorials (as well as widgets)

Let's not waste any more time with the introductions, however. The best way to learn a new language/framework is to dive in.

Installation

Current implementation of RapydScript relies on PyvaScript (this might change in the future, I'm considering using CoffeeScript as base instead). You will first need to install PyMeta (Ometa Tokenizer used by PyvaScript), then PyvaScript, and finally RapydScript. If you're using Linux or BSD, just run the following 3 commands:

wget http://bitbucket.org/wkornewald/pymeta/get/tip.zip; unzip tip.zip; cd wkornewald-pymeta*; sudo python setup.py install; cd ..; rm tip.zip

wget http://bitbucket.org/wkornewald/pyvascript/get/tip.zip; unzip tip.zip; cd wkornewald-pyva*; sudo python setup.py install; cd ..; rm tip.zip

NOTE: there is a bug with typeof in PyvaScript that you may want to fix before running the setup.py command. This fix is optional since RapydScript implements an alternative work-around in its stdlib. PyvaScript erroneously throws an error both when using typeof object and typeof(object) (because the check happens in two places, and they contradict). To apply the fix, open pyvascript/grammar.py and remove 'typeof' from the keywords in Grammar class. Now you can use typeof(object) notation. From there, you can continue the installation as normal.

You can use RapydScript from the same directory you unpack it to, or 'install' it, which will copy the files to your system path, making it accessible from anywhere. The following command will perform the installation:

wget http://bitbucket.org/pyjeon/rapydscript/get/tip.zip; unzip tip.zip; cd pyjeon-rapydscript*; sudo python setup.py install; cd ..; rm tip.zip

If you're using OSX, you can probably use the same commands (let me know if that's not the case). If you're using Windows, just download the files I linked to in the above URLs and install them in a similar manner.

Compilation

Once you have installed RapydScript, compiling your application is as simple as running the following command:

rapydscript <location of main file>

This will generate a JavaScript file with the same name as your main file, in the same directory. Now all you have to do is reference it in your html page the same way as you would with a typical JavaScript file. If you're only using RapydScript for classes and functions, then you're all set. If you're using additional Python methods, such as range(), print(), list.append(), list.remove(), then you will want to link RapydScript's stdlib.js in your html page as well. There are two versions of this library, the normal one and jQuery-safe. Read the quirks section to understand the difference in more detail. The short answer is use jQuery-safe one if your page also includes any jQuery logic (even if RapydScript doesn't interact with it).

Getting Started

Like JavaScript, RapydScript can be used to create anything from a quick function to a complex web-app. RapydScript can access anything regular JavaScript can, in the same manner. Let's say we want to write a function that greets us with a "Hello World" pop-up. The following code will do it:

def greet():
    alert("Hello World!")

Once compiled, the above code will turn into the following JavaScript:

function greet() {
    alert("Hello World!");
}

Now you can reference this function from other JavaScript or the page itself (using "onclick", for example). For our next example, let's say you want a function that computes factorial of a number:

def factorial(n):
    if n == 0:
        return 1
    return n * factorial(n-1)

Now all we need is to tie it into our page so that it's interactive. Let's add an input field to the page body and a cell for displaying the factorial of the number in the input once the input loses focus.

<input id="user-input" onblur="computeFactorial()"></input>
<div id="result"></div>

NOTE: To complement RapydScript, I have also written RapydML (http://bitbucket.org/pyjeon/rapydml), which is a pre-compiler for HTML (just like RapydScript is a pre-compiler for JavaScript).

Now let's implement computeFactorial() function in RapydScript:

def computeFactorial():
    n = document.getElementById("user-input").value
    document.getElementById("result").innerHTML = factorial(n)

Again, notice that we have access to everything JavaScript has access to, including direct DOM manipulation. Once compiled, this function will look like this:

function computeFactorial() {
    var n;
    n = document.getElementById("user-input").value;
    document.getElementById("result").innerHTML = factorial(n);
}

Notice that RapydScript automatically declares variables in local scope when you try to assign to them (this is something we get as a freebie from PyvaScript). This not only makes your code shorter, but saves you from making common JavaScript mistake of overwriting a global.

Leveraging other APIs

Aside from Python-like stdlib, RapydScript does not have any of its own APIs. Nor does it need to, there are already good options available that we can leverage instead. If we wanted, for example, to rewrite the above factorial logic using jQuery, we could easily do so:

def computeFactorial():
    n = $("#user-input").val()
    $("#result").text(factorial(n))

Many of these external APIs, however, take object literals as input. Like with JavaScript, you can easily create those with RapydScript, the same way you would create one in JavaScript, or a dictionary in Python:

styles = {
    'background-color': '#ffe',
    'border-left':      '5px solid #ccc',
    'width':            50,
}

Now you can pass it to jQuery:

$('#element').css(styles)

Another feature of RapydScript is ability to have functions as part of your object literal (something PyvaScript doesn't handle unless you assign the function to a temporary variable beforehand). JavaScript APIs often take callback/handler functions as part of their input parameters, and RapydScript let's you create such object literal without any quirks/hacks:

params = {
    'width':    50,
    'height':   30,
    'onclick':  def(event):
        alert("you clicked me"),
    'onmouseover':  def(event):
        $(self).css('background', 'red'),
    'onmouseout':   def(event):
        # reset the background
        $(self).css('background', ''),
}

One important thing to note, here, however, is that RapydScript currently uses the comma at the end to decide that the given key definition is over. Hence, while the comma at the end is optional if the last parameter is a variable, it's mandatory if the last parameter is a function. I will fix this in the future version, for now the workaround doesn't seem like a big deal.

Anonymous Functions

Like JavaScript (and PyvaScript), RapydScript allows the use of anonymous functions. In fact, you've already seen the use of anonymous functions in previous section when creating an object literal ('onmouseover' and 'onmouseout' assignments). This is similar to Python's lambda function, except that the syntax isn't awkward like lambda, and the function isn't limited to one line. The following two function declarations are equivalent:

def factorial(n):
    if n == 0:
        return 1
    return n * factorial(n-1)

factorial = def(n):
    if n == 0:
        return 1
    return n * factorial(n-1)

This might not seem like much at first, but if you're familiar with JavaScript, you know that this can be extermely useful to the programmer, especially when dealing with nested functions, which are a bit syntactically awkward in Python (it's not immediatelly obvious that those can be copied and assigned to other objects). To illustrate the usefulness, let's create a method that creates and returns an element that changes color while the user keeps the mouse pressed on it.

def makeDivThatTurnsGreen():
    div = $('<div></div>')
    turnGreen = def(event):
        div.css('background', 'green')
    div.mousedown(turnGreen)
    resetColor = def(event):
        div.css('background', '')
    div.mouseup(resetColor)
    return div

At first glance, anonymous functions might not seem that useful. We could have easily created nested functions and assigned them instead. By using anonymous functions, however, we can quickly identify that these functions will be bound to a different object. They belong to the div, not the main function that created them, nor the logic that invoked it. The best use case for these is creating an element inside another function/object without getting confused which object the function belongs to.

Chaining Blocks

RapydScript wouldn't be useful if it required work-arounds for things that JavaScript handled easily. If you've worked with JavaScript or jQuery before, you've probably seen the following syntax:

function(){
    // some logic here
}.call(this);

This code calls the function immediatelly after declaring it instead of assigning it to a variable. Python doesn't have any way of doing this. The closest work-around is this:

def tmp():
    # some logic here
tmp.__call__()

While it's not horrible, it did litter our namespace with a temporary variable. If we have to do this repeatedly, this pattern does get annoying. This is where RapydScript decided to be a little unorthodox and implement the JavaScript-like solution:

def():
    # some logic here
.call(this)

RapydScript will bind any lines beginning with . to the outside of the block with the matching indentation. This logic isn't limited to the .call() method, you can use it with .apply() or any other method/property the function has assigned to it. This can be used for jQuery as well:

$(element)
.css('background-color', 'red')
.show()

The only limitation is that the indentation has to match, if you prefer to indent your chained calls, you can still do so by using the \ delimiter:

$(element)\
    .css('background-color', 'red')\
    .show()

Some of you might welcome this feature, some of you might not. RapydScript always aims to make its unique features unobtrusive to regular Python, which means that you don't have to use them if you disagree with them. In the future, we also plan to add support for a do/while loop using similar logic:

do:
    # some logic here
.while(condition)

In my opinion, this is something even Python could benefit from.

Optional Arguments

Like Python, Javascript allows optional arguments to be passed into a function. Unlike Python, however, JavaScript doesn't assign default values to these, nor does it work well with optional arguments. If you forget to pass an argument, JavaScript will simply set the variable to undefined, and it's up to you to handle it. Should you forget about it, you'll probably pay the price later, when you use this variable in any sort of mathematical computation. By that time, you'll either end up with NaN (not a number), infinity, or some other weird value, causing an error a few hundred lines after the line responsible for causing it (good luck debugging it). Luckily, RapydScript allows sane optional arguments, similar to Python. The following function, for example, allows you to pass in a color for the background, or it will default to black (the format is r,g,b,a):

def setColor(color=[0,0,0,1]):
    $('body').css('background', 'rgba('+','.join(color)+')')

One thing to note here, is that unlike Python, RapydScript will create a separate object for the optional argument each time the function is called. This makes it slightly less efficient, but prevents it from messing up existing objects.

Python vs JavaScript Methods

RapydScript allows you use both, Python and JavaScript names for the methods. For example, we can 'push()' a value to array, as well as 'append()' it:

arr = []
arr.push(2)
arr.append(4)
print(arr) # outputs [2,4]

In order to use Python's methods, you will have to include RapydScript's stdlib.js in your html:

<script src='stdlib.js'>

Admittedly, I had to bastardize a couple of JavaScript's own methods for this. For example, array.pop() has been overwritten to work like Python's pop() (or JavaScript's splice()):

arr.pop()       # removes last element (expected behavior in JavaScript and Python)
arr.pop(2)      # removes third element (expected behavior in Python, but not JavaScript)
arr.splice(2,1) # removes third element (expected behavior in JavaScript, but not Python)

There is a subtle difference between the last 2 lines above, arr.pop(2) will return a single element, arr.splice(2,1) will return an array containing that single element. Most of the keywords are interchangeable as well:

RapydScript     JavaScript

self            this
None            null
False           false
True            true
                undefined

NOTE: If you want to be able to use null/false/true keywords in your RapydScript, you want to modify pyvascript/grammar.py, removing them from the keyword list the same way you did with 'typeof' in Installation section. Make sure to rerun 'sudo python setup.py install' command again from pyvascript directory so the changes can take effect.

The JavaScript operators, however, are not supported. You will have to use Python versions of those. If you're unfamiliar with them, here is the mapping RapydScript uses:

RapydScript     JavaScript

and             &&
or              ||
not             !
is              ===
is not          !==
+=1             ++
-=1             --

Admittedly, 'is' is not exactly the same thing in Python as '===' in JavaScript, but JavaScript is quirky when it comes to comparing objects anyway.

In rare cases RapydScript might not allow you to do what you need to, and you need access to pure JavaScript. When that's the case, you can wrap your JavaScript in a string, passing it to JS() method. Code inside JS() method is not a sandbox, you can still interact with it from normal RapydScript:

JS('a = {foo: "bar", baz: 1};')
print(a.foo)    # prints "bar"

One last thing to note is the difference between 'print()' and 'console.log', print() is part of RapydScript's stdlib, and is designed to work similar to Python's print statement, console.log() is JavaScript's version of debug output, which is more powerful but also more tedious when you just want a quick output. Here are some examples:

arr = [1,2,3,4]
print(arr)          # [1,2,3,4]
console.log(arr)    # [1,2,3,4]

arr2 = [[1,2],[3,4]]
print(arr2)         # [[1,2],[3,4]]
console.log(arr2)   # [Array[2], Array[2]]

hash = {'dogs': 1, 'cats': 2}
print(hash)         # {"dogs":1,"cats":2}
console.log(hash)   # Object

Loops

RapydScript's loops work like Python, not JavaScript. You can't, for example use 'for(i=0;i<max;i++)' syntax. You can, however, loop through arrays using 'for ... in' syntax without worrying about the extra irrelevant attributes regular JavaScript returns.

animals = ['cat', 'dog', 'mouse', 'horse']
for animal in animals:
    print('I have a '+animal)

If you need to use the index in the loop as well, you can do so by using enumerate():

for pair in enumerate(animals):
    print("index:"+pair[0], "animal:"+pair[1])

Admittedly this is slightly more awkward than Python, which allows you to avoid the need of a temporary 'pair' variable. Perhaps future version of RapydScript could do the same, by doing a bit of extra pre-parsing. Like in Python, if you just want the index, you can use range:

for index in range(len(animals)):           # or range(animals.length)
    print("animal "+index+" is a "+animals[index])

List Comprehensions

RapydScript also supports list comprehensions, using Python syntax. Instead of the following, for example:

myArray = []
for index in range(1,20):
    if index*index % 3 == 0:
        myArray.append(index*index)

You could write this:

myArray = [i*i for i in range(1,20) if i*i%3 == 0]

Which is not only shorter, but easier to read too. There are a few gotchas you might want to be aware of. RapydScript implements list comprehensions via a combination of map() and filter() functions, which are now part of the stdlib. For example, the above line compiles to:

var myArray;
myArray = range(1,20).filter(function(i) { return i*i%3 == 0; }).map(function(i) { return i*i; });

I have not tested RapydScript with more complex list comprehensions, and given its current implementation (regex), it's likely to break when you start nesting them (however, this in general is a bad idea anyway, when your list comprehensions start getting incomprehensibly long, you're better off with a loop anyway). Second, RapydScript takes the i*i and i*i%3 == 0 portions and inserts them into the output verbatim. This means that Python-only syntax, such as i**2 might not get compiled correctly here, but ** is not supported by RapydScript anyway, and I couldn't think of another case, so this is a low-priority bug for now (also, the work-around is quite simple, define a function doing the same operation before-hand). Which brings me to my last point. To accomodate filter() and map() syntax, RapydScript wraps this logic in an additional function call. So i*i becomes:

function (i) {
    return i*i;
}

This makes sense. However, this also means that if you write the following comprehension:

[stuff(x) for x in [1,2,3]]

then stuff(x) also gets wrapped in an extra function. I'd rather deal with the overhead of an extra function call, however, than adding more special cases that could introduce additional bugs. The current list comprehension in RapydScript is not built with performance in mind. It does omit the filter() part if you don't include the if statement, and it always filters first, to reduce the number of elements it needs to run map() on. However, in the worst case scenario (when filter doesn't remove any elements), it will generate two extra arrays internally of the same size as original.

Classes

This is where RapydScript really starts to shine. JavaScript is known for having really crappy class implementation (it's basically a hack on top of a normal function, most experienced users suggest using external libraries for creating those instead of creating them in pure JavaScript). Luckily RapydScript fixes that. Let's imagine we want a special text field that takes in a user color string and changes color based on it. Let's create such field via a class.

class ColorfulTextField:
    def __init__(self):
        field = $('<input></input>')
        changeColor = def(event):
            field.css('backround', field.val())
        field.keydown(changeColor)
        self.widget = field

This class abuses DOM's tolerant behavior, where it will default to the original setting when the passed-in color is invalid (saving us the extra error-checking logic). To append this field to our page we can run the following code:

textfield = ColorfulTextField()
$('body').append(textfield.widget)

If you're used to JavaScript, the code above probably set off a few red flags in your head. In pure JavaScript, you can't create an object without using a 'new' operator. Don't worry, the above code will compile to the following:

var textfield;
textfield = new ColorfulTextField()
$('body').append(textfield.widget);

RapydScript will automatically handle appending the 'new' keyword for you, assuming you used 'class' to create the class for your object. This also holds when creating an object inside a list or returning it as well. You could easily do the following, for example:

fields = [ColorfulTextField(), ColorfulTextField(), ColorfulTextField()]

This is very useful for avoiding a common JavaScript error of creating 'undefined' objects by forgetting this keyword. One exception to note here, however, is that regular DOM/JavaScript objects are not bound by this (not yet, I will handle this in a later version). So if you want to create a DOM image element, you should still use the 'new' keyword:

myImage = new Image()

But RapydScript's capability doesn't end here. Like Python, RapydScript allows inheritance. Let's say, for example, we want a new field, which works similar to the one above. But in addition to changing color of the field, it allows us to change the color of a different item, with ID of 'target' after we press the 'apply' button, located right next to it. Not a problem, let's implement this guy:

class TextFieldAffectingOthers(ColorfulTextField):
    def __init__(self):
        ColorfulTextField.__init__(self)
        field = self.widget
        submit = $('<button type="button">apply</button>')
        applyColor = def(event):
            $('#target').css('background', field.val())
        submit.click(applyColor)
        self.widget = $('<div></div>')\
            .append(field)\
            .append(submit)

A couple things to note here. We can invoke methods from the parent class the same way we would in Python, by using Parent.method(self, ...) syntax. This allows us to control when and how (assuming it requires additional arguments) the parent method gets executed. Also note the use of \ operator to break up a line. This is something Python allows for keeping each line short and legible. Likewise, RapydScript, being indentation-based, allows the same.

An important distinction between Python and RapydScript is that RapydScript does not allow multiple inheritance. This might seem like a big deal at first, but in actuality it barely matters. When using multiple inheritance, we usually only care about a few methods from the alternative classes. Leveraging JavaScript prototypical inheritance, RapydScript allows us to reuse methods from another class without even inheriting from it:

class Something(Parent):
    def method(self, var):
        Parent.method(self, var)
        SomethingElse.method(self, var)
        SomethingElse.anotherMethod(self)

Notice that Something class has no __init__ method. Like in Python, this method is optional for classes. If you omit it, an empty constructor will automatically get created for you by RapydScript (or when inheriting, the parent's constructor will be used). Also notice that we never inherited from SomethingElse class, yet we can invoke its methods. This brings us to the next point, the only real advantage of inheriting from another class (which you can't gain by calling the other classes method as shown above) is that the omitted methods are automatically copied from the parent. Admittedly, we might also care about instance() method, to have it work with the non-main parent, but we're already overwriting JavaScript's instanceof() method in stdlib, so feel free to tweak it further, if you have the need to. To summarize classes, assume they work the same way as in Python, plus a few bonus cases. The following, for example, are equivalent:

class Aclass:
    def __init__(self):
        pass

    def method(self):
        doSomething(self)

class Aclass:
    def __init__(self):
        self.method = def():
            doSomething(self)

Note that when binding the method to another object, self inside the bound function will no longer correspond to the original object. To remedy this, you can create another variable storing self from original class:

class Main:
    def __init__(self):
        main = self
        method = def():
            main.doSomething()
        $('#element').click(method)

    def doSomething(self):
        ...

Importing

Like Python, RapydScript allows you to import additional modules into your code. Unlike Python, however, (and like RapydML) the current implementation of the importing logic is naive. This means that RaoydScript doesn't separate different modules into separate namespaces, nor does it support module aliasing yet (eventually I do want that functionality).

For those unfamiliar with importing, let's imagine we're writing a very large program. This program is several thousand lines of code. We could dump it all into the same file, but that wouldn't be too clean (especially when we have multiple developers working on it). Alternatively, we could separate different chunks of the program into different files. Let's imagine, for example, that we're writing a videogame. We've already written a module that implements 'BasicCharacter' class, used by NPCs, monsters, and the main character. We've saved this class to Basic.pyj (that's the extension RapydScript prefers). Now let's create the main character in a different module:

import Basic

class MainCharacter(BasicCharacter):
    """
    This is the main character class, similar to basic but it implements attack and hp
    """
    def __init__(self):
        BasicCharacter.__init__(self)
        self.hp = 100
        self.damage = 10

    def attack(self, something):
        something.getHurt(self.damage)

    def getHurt(self, damage):
        self.hp -= damage

With RapydScript's importing, you don't need to worry about including each JavaScript file individually in your html. All your .pyj files will get concatenated into a single .js file that you can then include in your page. The name of the .js file will match the name of the file you used input for the RapydScript compiler.

Quirks

In a perfect world, software works flawlessly and doesn't have any special cases requiring workarounds on the user's part. In a less-perfect world, all quirks are due to the software itself and can be fixed. In our world, we not only have to deal with the quirks of the software we're using, but other software it interacts with. RapydScript is no exception, in addition to its own quirks there are a few quirks brought upon us by the browser as well as a few bugs in jQuery that affect us. Here is a list of things you need to be aware of:

  • RapydScript automatically appends 'new' keyword when using classes generated by it, it does not append it to objects created by another library or ones part of W3C (this second part I can fix in a later version).
  • jQuery erroneously assumes that no other library will be modifying JavaScript's 'Object', and fails to do object.hasOwnProperty() check in multiple places where it should. The standard stdlib.js will therefore break jQuery. To remedy this, you should use stdlib.jquery.safe.js instead. There is also a difference in how hash/dict methods are used between the two libraries. The regular stdlib.js allows you to call hash.keys() as well as dict.keys(hash), like pure Python. The jQuery-safe version only supports the second notation - which is admittedly a bit more awkward.
  • Any anonymous function in hash/object literal must be followed by a comma, even if it's the last argument.
  • List comprehensions could break when nesting multiple of them together on one line
  • List comprehensions could break when using Python-only shorthand syntax in them (i**2, for example). Things like 'i+=1' should work fine, however.

Additional Features

In addition to the above features, RapydScript also checks for obvious errors during compilation. These errors include common mistakes made by developers as well as things that aren't really errors but PyvaScript will still break on (due to its own bugs). Some examples include mixing tabs and spaces in leading whitespace, using wrong library for JavaScript's Math methods (you want to use Math.sqrt() instead of math.sqrt() or sqrt()), reusing same names for global variables/classes/functions, omitting space after 'if' even if the next character is a bracket, and some other minor PyvaScript bugs. This should be enough to get you started with RapydScript. If I omitted anything that you think is useful, feel free to contact me.