Spider is a programming language that compiles to JavaScript. It takes the best ideas of Swift, Python, C# and CoffeeScript.

It's just JavaScript, but better.

func TimeMachine(pilot) {
  this.pilot = pilot;
  
  this.go = func (noise) {
    ::console.log(noise);
  };
}

func Tardis() 
  extends TimeMachine("The Doctor") {
  
  this.go = () -> 
    super.go("vorp vorp");
}

func DeLorean() 
  extends TimeMachine("Marty") {

  this.go = () -> 
    super.go("One point twenty-one gigawatts!");
}

for timeMachine in ::getTimeMachines() {
  timeMachine?.go();
}
Note: Spider is still work in progress. Make sure to star the project in GitHub if you are interested!

Why Spider?

There are many languages that compile to JavaScript. The most popular ones are CoffeeScript, TypeScript and Dart.

But all of them have problems:

  • CoffeeScript (and its derivatives: LiveScript, Coco, etc) have serious issues of ambiguous code, alien syntax and scoping problems.
  • TypeScript (and AtScript) are supersets of JavaScript, so they inherit all of its problems. Additionally, loose typing can be one of JavaScript's best features if used correctly.
  • Dart is an extreme departure from JavaScript, which makes it impossible to interact with JavaScript libraries directly. Instead, you must use a special interop library that exposes wrapped versions of any JavaScript objects you access.

Spider tries to learn from its predecessors and build a real programming language that will make developers and teams happy:

  • Instead of bringing a completely new indentation-based syntax like CoffeeScript, Spider preserves JavaScript's familiar syntax that we all use everyday.
  • Instead of just introducing classes, Spider embraces JavaScript's prototype-based OOP and makes it much easier to use using keywords like extends and super.
  • Spider is fully compatible with libraries like Angular.js, Meteor, jQuery, Sails.js and other existing JS libraries without any extra effort.
  • Spider is an extremely safe language. It has a great compile-time error reporting system, and all uses of the global scope must be explicit. Spider also fixes many JavaScript problems. For example, == is automatically compiled to ===, and the typeof operator supports array, date, regexp and more.
  • Spider has useful syntactic sugar like Splats, Array Slicing and Splicing, Existential Operator, Ranges, List Comprehensions and much more.

Spider also has great debugging support:

Installation

Install Spider via npm, the node.js package manager:

npm install -g spider-script

Command-line usage

To execute a script, run:

spider /path/to/script.spider

To compile a script:

spider -c /path/to/script.spider

The compiler will generate the output JavaScript file and a source map file for debugging. Note that the original spider file and the source map file aren't necessary for production.

All Spider code is compiled to JavaScript strict mode and wrapped in Immediately-Invoked Function Expression (IIFE).

Global Scope Usage

In Spider, all uses of the global scope must be explicit. Use :: to access the global scope.

::console.log("Hello world!");

You can also pull global variables into the local scope with the use statement:

use console;

// 'console' doesn't need '::' before it because of 'use'
console.log("Hello world!");

For convenience, you can also use :browser to automatically import DOM global variables, and use :node to automatically import Node.js-related global variables.

:browser :node
console console
window global
document require
screen exports
location module
navigator process
alert

Scoping

The Spider compiler will raise an error if you try to refer to a variable that doesn't exist in the current scope or in one of its parents.

Scoping just works, without any surprises. Spider will protect you against any unintended behaviour.

Logical Operators Consistency

In Spider, logical operators always result in a boolean value regardless of the types passed to it. In addition, == is automatically compiled to ===.

x = false or 5;  // x == true;
x = 5 and 4;     // x == true;
x = 1 == "1";    // false (== compiles to ===)

Note that Spider adds the and and or operators that behave exactly like && and || respectively.

Spider provides the null-coalescing operator to replace the useful options.property || defaultValue idiom.

Typeof

Spider's typeof operator returns the lowered-cased constructor name of the argument.


typeof { a: 4 };          // "object"
typeof [1, 2, 3];         // "array"
typeof new Date;          // "date"
typeof new Number(4);     // "number"
typeof new String("abc"); // "string"
typeof new Boolean(true); // "boolean"

String interpolation is a way to construct a new string from a mix of constants, variables, literals, and expressions by including their values inside a string literal.

var multiplier = 3;
var message = "\(multiplier) times 2.5 is \(multiplier * 2.5)";

// message is "3 times 2.5 is 7.5"

Declaration

Functions in Spider are declared using the func keyword:

func square(x) { 
  return x * x;
}

var cube = func (x) { 
  return square(x) * x;
};

Arrow Syntax

Functions can also be expressed using the arrow syntax:

var isEven = (x) -> x % 2 == 0;
              
app.controller("MyCtrl", ($scope, MyService) -> {
  $scope.items = [];
});

Default Params Value

Functions may also have default values for arguments, which will be used if the incoming argument is missing (null or undefined).

func legCount(animal = "spider") {
  switch animal {
    case "spider": {
      return 8;
    },  
    
    default: {
      return 2;
    }
  }
}

Splats

The JavaScript arguments object is a useful way to work with functions that accept variable numbers of arguments. Spider provides splats ..., both for function definition as well as invocation, making variable numbers of arguments a little bit more palatable.

var gold, silver, rest;
              
func awardMedals(first, second, others...) {
  // others is an array
  gold = first;
  silver = second;
  rest = others; 
}

var contenders = [
  "Michael Phelps"
  "Liu Xiang"
  "Yao Ming"
];

awardMedals(contenders...);

Inheritance

Instead of just adding classes, Spider embraces JavaScript prototypes and makes them much easier to use. Spider's inheritance is compatible with almost any JavaScript class implementation out there.

func Animal(name) {
  this.move = func (distance) {
    alert("\(name) moved \(distance)m.");
  };
}

func Snake(name) 
  extends Animal(name) {
  
  this.move = func() {
    alert("Slithering...");
    super.move(5);
  };
}

func Horse(name) 
  extends Animal(name) {
  
  this.move = func() {
    alert("Galloping...");
    super.move(45);
  };
}

var sam = new Snake("Sammy the Python");
var tom = new Horse("Tommy the Palomino");

sam.move();
tom.move();

If Statement

In Spider, if statements (as well as most other statements) can be written without the use of parentheses.

if happy and knowsIt {
  clapHands();
} else {
  showIt();
}

Conditional Expression

There's no ternary operator (?:) in Spider. Instead, Spider provides Python-like if expressions:

var date = sue if friday else jill;

For Statement

Spider provides 3 types of for statements. The first type allows a block of code to be executed repeatedly while incrementing a counter, as long as a condition remains true.

for var i = 0; i < 5; i++ {
  ::console.log(i);
}

For-In Statement

A for-in statement allows a block of code to be executed once for each item in an array.

var foods = ['broccoli', 'spinach', 'chocolate'];

for food in foods {
  eat(food);
}

You can also get the index of the item by adding another variable:

for food, i in foods {
  console.log("Eating food number \(i).");
  eat(food);
}

For-Of Statement

A for-of statement allows a block of code to be executed once for each property in an object.

var player = { 
  "name": "Daniel", 
  "address": "Tel Aviv",
  "age": 18
};

for key of player {
  console.log("Found property \(key).");
}

You can also get the value of the key by adding another variable:

for key, value of player {
  console.log("\(key): \(value)");
}

While Statement

Spider provides a while loop that behaves exactly like the JavaScript equivalent except parenthesis aren't required:

while supply > demand {
  buy();
}

Until Statement

For readability, Spider also provides an until loop that is equivalent to while not:

until supply > demand {
  sell();
}

List Comprehensions

Spider provides Python-like list comprehensions. List comprehensions provide a concise way to create list from existing list.

var list = [1, 2, 3, 5, 6, 7];
var doubles = [x * 2 for x in list];

You can also filter elements with a condition:

var doubles = [x * 2 for x in list if x % 2 == 0];

Existential Operator

It's a little difficult to check for the existence of a variable in JavaScript. if (variable) ... comes close, but fails for zero, the empty string, and false. The existential operator ? returns true unless a variable is null or undefined

if game? {
  play();
}

The accessor variant of the existential operator ?. can be used to soak up null references in a chain of properties. Use it instead of the dot accessor . in cases where the base value may be null or undefined. If all of the properties exist then you'll get the expected result, if the chain is broken, undefined is returned instead of the TypeError that would be raised otherwise.

var zip = lottery.drawWinner?().address?.zipcode;

Null-Coalescing Operator

The null-coalescing operator ?? returns the right expression if the left expression is null or undefined.

var name = options.name ?? "default name";

In Operator

The in operator checks if an array contains an specific item, or if an object contains a specific property.

var people = [billy, robin, daniel];
              
var details = {
  "price": 45,
  "address": "New York, USA",
  "age": 27
};

console.log(billy in people);     // => true
console.log("address" in details) // => true

Chained Comparisons

Chained comparisons make it easy to test if a value falls within a certain range.

var isHealthy = 200 > cholesterol > 60;

Exponentiation (**)

To simplify math expressions, ** can be used for exponentiation.

var volume = a ** 3; // => Math.pow(a, 3)

Integer division (#)

The # operator performs integer division.

var floor = a # b; // => Math.floor(a / b)

Math modulo (%%)

The # operator provides true mathematical modulo.

var modulo = a %% b; // => (a % b + b) % b

Range Literal

The range literal provides a simple way to generate a numbers array. With two dots (3..6), the range is inclusive (3, 4, 5, 6); with three dots (3...6), the range excludes the end (3, 4, 5)

var a = [1..5];    // => [1, 2, 3, 4, 5]
var b = [1...5];   // => [1, 2, 3, 4]

It can also work in the opposite direction:

var c = [5..1];    // => [5, 4, 3, 2, 1]
var d = [5...1];   // => [5, 4, 3, 2]

Array Slicing

Ranges can also be used to extract slices of arrays.

var numbers = [1...10];

var start  = numbers[0..2];
var middle = numbers[3...-2];
var end    = numbers[-2..];
var copy   = numbers[..];

Array Splicing

The same syntax can be used with assignment to replace a segment of an array with new values, splicing it.

numbers[3..6] = [-3, -4, -5, -6];

The switch statement in Spider is much more powerful than the JavaScript equivalent:

switch day {
  case "Monday": {
    go(work);
  },

  case "Tuesday": {
    go(relax);
  },
  
  case "Friday", "Saturday": {
    go(bingo);
  },
  
  default: {
    go(work);
  }
}

Note that break is not required.

Switch statements can also be used with ranges:

switch score {
  case ..60: {
    return "F";
  },

  case 60..70: {
    return "D";
  },
  
  case 70..80: {
    return "C";
  },
  
  case 80..90: {
    return "B";
  }
  
  default: {
    return "A";
  }
}