RedScript

A Ruby Flavored JavaScript Experiment

Download .zip Download .tar.gz View on GitHub

  • Paste (almost) any JS into a RedScript file
  • Native feeling module syntax (AMD & CommonJS)
  • Easier class like inheritance or self based prototypal inheritance syntax

RedScript is an experiment created to provide a better syntax for AMD modules, easier inheritance and a more usable ES5 object syntax. It was also created as a side project to learn more about Node, NPM Modules and Regular Expressions. In the future I would also like convert this prototype compiler into a proper one with a lexer/parser to implement more advanced features like optional parens, variable scope and attr_accessor like ES5 object properties. Pull requests welcome ;-)

To Install Globally and Run

sudo npm install -g redscript
redscript watch [fileName1.rs fileName2.rs]

Syntax

If you have suggestions on different syntax, please create an issue: here.


Comments

Status: Working

Line comments are made with a #. RedScript currently does not have multi-line comments but /*...*/ can be used if needed.

# I'm a comment                          // I'm a comment
$('#someID').html(foo)                   $('#someID').html(foo) # No worries here


Alias @ with this

Status: Working

function Foo(name, age) {               function Foo(name, age){
  @name = name                             this.name = name;
  @age = age                               this.age = age;
}                                        }


Loops

Status: Working

Note, for in range loops only support 0..3, 0...3 or variables e.g. begin..ending. Note, currently the range does not go backwards (10..0).
To prevent confusion until loops have an //until comment next to the compiled output.

while foo < 200                        while (foo < 200) {
  puts "I'm looping forever"              console.log("I'm looping forever");
end                                     }


# loops until a condition is true
until i == 5                            while (!(i == 5)) { // loop until  
   puts i                                  console.log(i);                
   i += 1                                  i += 1;                        
end                                     }                                 


# prints 5x, 0,1,2,3,4                  
for i in 0..5                           for (var i=0; i < 5; i++) {    
  puts i                                  console.log(i);           
end                                     }                             


# prints 6x, 0,1,2,3,4,5                
for i in 0...5                          for (var i=0; i <= 5; i++) {
  puts i                                  console.log(i)       
end                                     }                          


Iterating over arrays and objects

Status: Working

# Iterate over arrays
basket = ['lemon', 'pear', 'apple']     var basket = ['lemon', 'pear', 'apple'];

for fruit inArr basket                  for (var i1=0, len1=basket.length; i1 < len1; i1++) { var fruit = basket[i1];    
  puts fruit                              console.log(fruit);                                                            
end                                     }                                                                                


for key in obj                          for (var key in obj) {
   alert(key)                             alert(key);
end                                     }


for key,val in obj                      for (var key in obj) { var val = obj[key];
  puts 'My key is: #{key}'                console.log('My key is: ' + key);
  puts 'My value is: #{val}'              console.log('My value is: ' + val);
end                                     }


Convenience method aliases for puts & printf

Status: Working
puts and printf are aliases to console.log and process.stdout.write. printf is only available with node enviroments. These are the only methods that have optional parens at this time. If parens are omitted a closing paren will be appended to the end of the line. Parenless multi lines are not possible.

puts "Hello there"                      console.log("Hello there");
puts("Hai!")                            console.log("Hai")
printf "Hola"                           process.stdout.write("Hola")
printf("Hola")                          process.stdout.write("Hola")


Variables & automatic semi-colon insertion

Status: Partially implemented, known bug

Variables are automatically declared. Using the var keyword is optional and allowed.
Constants are pre-processed at compile time. This allows some memory savings in certain cases where itcan be utilized. Semi-colons are automatically inserted as needed (currently not implemented).

Note currently RedScript does not take function scope into account. Any variables that are declared inside of a function multiple times can lead to unintended global leaks. To disable auto declaration pass no--declare. Another workaround is to manually declare variables with the var keyword.

foo = 12                 var foo = 12;
foo = 20                 foo = 20;

# Constants are preprocessed
AMOUNT = 233            
puts AMOUNT             console.log(233)

# !! Lexical scoping bug in current version !!
func foo                var foo = function() {
  baz = 20                var baz = 20;       
end                     };                    

func bar                var bar = function() {
  baz = 30                baz = 30;       
end                     };                     


Optional Parenthesis

Status: Implemented with RedScript constructs only

Currently I cannot implement this until I can create a proper lexer. Pull requests welcome ;-) For the time being you have to include parens. A temporary kludgey alias for end) is end-. This is slightly easier to look at for the short run, but expect this to be dropped in the future.

alert 'Hello World!'

getUser '/users/1', do
  alert user
end

# Currently you still have to use parens for anything that's not a RedScript construct.
getUser( '/users/1', do
  alert(user)
end)

# A kludgey alias for `end)` is `end-`
getUser( '/users/1', do
  alert(user)
end-


Ruby flavored functions

Func [name] declares the function as an expression and sets [name] to a variable. Parens are optional if no arguments are being passed. If arguments are passed braces must be used. Anonymous functions are declared as func, parens are also optional. This type of anonymous function should only be used when using do |x| would be awkward, such as in an async's array of parallel functions.

Status: Working

func say(msg)                            var say = function(msg) {
  puts "Message: #{msg}"                   console.log("Message: " + msg);
end                                      };

# Optional function parens
func sayHello
  puts "Hello"
end

# anonymous function
func                                    function() {
  # do stuff                              // do stuff
end                                     }

func(a,b)                               function(a,b) {
  # do stuff                              // do stuff
end                                     }


If/else/else if

Status: Partially implemented

If statements currently are multi-line. If you would like to use a one liner, you can still use vanilla js if (err) throw err;.

if foo == 10                            if (foo === 10) {
  bar("do stuff")                         bar("do stuff");
else if foo == 20                       } else if (foo === 20) {
  bar("do stuff")                         bar("do stuff");
else                                    } else {
  bar("do stuff")                         bar("do stuff");
end                                     }

throw err if err                        if (err) { throw err; }


Methods & Object Literal

Status: Working

Methods are declared using the def keyword and parens are optional if arguments are not used.
Attaching a method to an object's prototype be defined using def objName >> mthdName.

object auto                                   var auto = {
  wheels: 4,                                    wheels: 4,
  engine: true,                                 engine: true,

  def honk                                      honk: function() {
    puts "hoooonk"                                console.log("hooooonk");
  end,                                          },

  def sayHi(msg)                                sayHi: function(msg) {
    puts msg                                      console.log(msg);
  end                                           }
end                                           }

# Define outside of object literal
def auto.add(x, y)                            auto.add = function(x,y) {
  return x + @wheels                            return x + this.wheels;
end                                           };

# Define method attached to prototype
def Car >> sub(x,y)                          Car.prototype.sub = function(x, y) {
  return x - y                                  return x - y;
end                                           };


Block-like Syntax for Anonymous Functions

Status: Working

Block like syntax is a shorter way to write an anonymous function. The preceding comma is optional. However, including it may be easier to understand if it's included. Once we have optional parens working this will look much nicer!

app.get('/users/:user', do |req|        app.get('/users/:user', function(req) {
  puts req.params.name                    console.log(req.params.name);
end)                                    });

# no params
$myBtn.click( do                        $myBtn.click( function() {
  alert("hi")                             alert("hi");
end)                                    });


CommonJS Modules

Status: Partially Implemented

# Require a module as set variable as that name
require 'express'                                          var express = require('express');

# Require a directory as a different name
require './lib/widget' as Widget                           var Widget = require('./lib/widget');

# Require a property of a module
require 'events'.EventEmitter as EM                        var EM = require('events').EventEmitter;

# Require file foo and set it's variable to foo
require '../lib/Router'                                    var Router = require('../lib/Router'); 

# export methods
export def getName                                         exports.getName = function() {
  return user.name                                           return user.name;
end                                                        };

# export properties
export age                                                 exports.age = 22;
func name                                                var name = function() {
  return "John"                                            return "John";
end                                                      }

age = 92                                                 var age = 92;

export                                                    module.exports = {
  getName from name                                           getName: name,
  age                                                       age: age
end                                                       }


RequireJS Module Sugar

Status: Not Implemented

Using define module wraps the current file in a new anonymous RequireJS module. Exports can either be exported in a export object literal or by using export foo to export the foo variable only. Any dependencies can be declared by using require 'foo' as foo which assigns 'foo''s returned value to a variable called foo.

define module                                                  define(function() {

func foo(x)                                                      var foo = function(x) {
  puts x                                                           console.log(x);
end                                                              }

export foo                                                       return foo; });
define module                                                   define([ 'jquery',
require 'jquery' as $                                                    './views/widget'],
require './views/widget' as Widget                                function($, Widget) {        

options = {                                                        var options = {
  moonRoof: true,                                                    moonRoof: true,   
  seats: 5                                                           seats: 5      
}                                                                  }          

getCost = 16899                                                    var getCost = 16899;
wheels = 4                                                         var wheels = 4;

# export literal compiles to an object that gets returned
export                                                              return {   
  getCost                                                               getCost : getCost,
  hasMoonRoof from options.moonRoof                                     hasMoonRoof : options.moonRoof,   
  getWheels from wheels                                                 getWheels : wheels     
end                                                                 }           
                                                                }); 


Ruby / Coffee style case switch statement

Status: Working

Switch statements still need break inserted to prevent falling through. The exception is one liners, then it's appened to the line.

switch fruit                                             switch (fruit) {
when "apple"                                               case "apple":
  puts "it's an appppple"                                    console.log("it's an appppple");
  break                                                      break;
when "bannana" then puts("bannana")                        case "bannana": console.log("bannana"); break;
when "orange"                                              case "orange":
  puts "it's an orange"                                      console.log("it's an orange");
  break                                                      break;
default                                                    default:
  puts "uh oh, bummer"                                       console.log("uh oh, bummer");
end                                                      }


Private Blocks

Status: Working

Private blocks keep variables scoped inside the block using function scope. There is a slight performance hit due to the IIFE. Again, due to the lack of proper lexing/parsing I can't yet use an end block. A workaround is endPriv, this calls the IIFE. Also due to the variable declaration bug mention above, variables like the example below will currently need var manually inserted to keep foo from leaking out and changing global foo to 10. The beginning of the IIFE is nerfed with a semi-colon to prevent any potential errors (especially with the current state of RedScript not having automatic semi-colon insertion).

foo = 200                                                 var foo = 200; 

private                                                  (function(){
  var foo = 10                                              var foo = 10;
endPriv                                                   })();

alert(foo) #alerts 200                                    alert(foo);


Classical Inheritance

Status: Working

RedScript classes are currently using John Resig's simple inheritance. In the future a solution closer to coffeescript would be ideal, allowing one to inherit from any constructor and still have the correct syntax. The current implementation will not inherit unless the base class is created with RedScript. However, backbone and ember both use the .extend method which is convenient since using
class Foo < Backbone.Model.extend( will call their own extend implementation. Backbone does not have a this._super method so if you want to call super you would need to use a backbone plugin for that. Ember uses the same this._super syntax as RedScript so calling super will call Ember's implementation.

If you're only using their inheritance implementation you can disable the insertion of RedScript's inheritance by passing the --no-class-insertion flag.

class Animal                                          var Animal = Class.extend({           
  def init(name)                                        init: function(name) {              
    @name = name                                          this.name = name;                 
  end,                                                  },                                  

  def sayHi                                             sayHi: function() {                 
    puts "Hello"                                          console.log("Hello");             
  end                                                   }                                   
end)                                                   });                                   

class Duck < Animal                                   var Duck = Animal.extend({            
  def init                                              init: function(name) {  
    super name                                            this._super(name);                    
    alert("#{@name} is alive!")                           alert(this.name + " is alive!")   
  end,                                                  },                                  

  def sayHi                                             sayHi: function() {                 
    super                                                 this._super();                    
    puts "Quack"                                          console.log("Quack");             
  end                                                   }                                   
end)                                                   });                                   

duck = new Duck('Darick')                             var duck = new Duck('Darick');
duck.sayHi()                                          duck.sayHi();


Prototypal Inheritance

Status: Partially Implemented

This is an experiment to try and bring out JavaScripts true prototypal nature. The goal is to be able to easily inherit without using constructors or faux classes. Vanilla JS makes it very verbose to inherit from another object, unlike Self, JavaScript's inheritance inspiration. One of the drawbacks to property delegation is keeping state in an object. Using object myDuck clones duck will copy all of the properties from duck into myDuck, ensuring it won't grab it's parents property by accident. Currently clones is not implemeneted yet.

object animal
  name: null,

  def sayHi
    puts "Hello"
  end
end

object duck
  parent*: animal,
  name: null,

  def sayHi
    puts "Quack"
  end
end

# this duck's attr's are cloned instead of deligated
# with `parent*`, usefull for statefull objects
#
object myDuck clones duck
  name: 'Darick'
end