GorillaScript is a scripting language that transcompiles to JavaScript. It is designed to empower the user while attempting to prevent some common errors. Specific additional features include Constants, Some new operators, String interpolation, Optional parameters, Spread parameters, Maps and Sets, Lexical scoping in loops, Array slicing, Cascades, Classes Destructuring, Topicless switch, Iterators, Promises, Parallel loops, Optional typing, Operators-as-functions, Getters and setters (only in engines that support it), Curried functions, Generics, Macros[1]

Syntax

edit

Access with ownership

edit

Every key owned by the object can be accessed.

assert if parent ownskey key
  parent[key] //{or parent.key}

// functionally equivalent to

assert parent![key] //{or parent!.key}

Apply syntax

edit

GorillaScript provides the @ syntax to replace  either .call or  .apply used with JavaScript. It transparently converts to .call or .apply (whichever is more appropriate), using the first argument as its this.

let f() this

let obj = {}
assert obj == f@ obj
assert obj == f@(obj)

Array.prototype.slice@ arguments, 1

Binding access

edit

In GorillaScript, the familiar @ syntax with access is, instead of Function.prototype.bind

let obj = {
  f: # this
}

let bound = obj@.f
assert bound() == obj
let unbound = obj.f
assert unbound() == window

Classes

edit

While Vanilla JavaScript does not allow definition of classes in the classical style, GorillaScript permits this. Classes can extend other classes and call into their superclass with super. The constructor functions automatically check the thisargument and if it is not the current class’s type (such as when called without new), it will create a new one on-the-fly.

class Animal
  def constructor(@name) ->

  def eat() "$(@name) eats"

class GreatApe extends Animal
  // no constructor, Animal's is automatically called
  def eat(food="fruit") super.eat() & " a " & food

class Gorilla extends GreatApe
  def constructor(@name, @favorite-food)
    // important to call the super constructor.
    super(@name)

  def eat() super.eat(@favorite-food)

class Chimp extends GreatApe
  def eat() super.eat("banana")

let bobo = Chimp("Bobo") // new is not required on GorillaScript-made classes
assert bobo.eat() == "Bobo eats a banana"

let toko = Gorilla("Toko", "cherry")
assert toko.eat() == "Toko eats a cherry"

// set a method on the Gorilla constructor
Gorilla::barrel := # @name & " throws a barrel!"

assert toko.barrel() == "Toko throws a barrel!"

Regular Expressions

edit

In GorillaScript, Regular Expressions borrow the string syntax simply prefixed with r and postfixed with any RegExp flags deemed necessary. This is unlike JavaScript where / is used to mark Regular Expressions.

Also, if triple-quoted strings (e.g. r"""reg"""g) are used, all spaces are ignored as well as any# hash comments

r"l".test "apple"
let regex = r"""
  This is a large regex, $name
  And all the space is ignored # and this is ignored, too!
  """gim

Promises

edit

GorillaScript provides mechanisms for Promises/A+-compliant Promises to be used and joined and manipulated together.

Promises provide a whole slew of asynchronous-capable syntax to make dealing with “callback hell” just a little easier. Since JavaScript fundamentals and frameworks (node.js, for example) tend to be very async-friendly, this is especially helpful.

Promises have the benefit over simple callbacks in that they are well-defined by the Promises/A+ spec, have easy-to-follow resolve/reject rules, and can be combined together shockingly easily.

Piggy-backing on the yield syntax, since yield can be used as an expression and not just a statement, information can be sent back to the iterator every time a yield occurs. GorillaScript takes advantage of this with the promise! keyword, which converts a generator function into a Promise factory or allows an arbitrary block of code to turn into a Promise. To achieve this, all that needs to be done is that other Promises need to yield until complete.

let make-promise = promise! #(filename)*
  // here, read-file returns a Promise
  let text = yield read-file(filename)
  return text.to-upper-case()

let promise = make-promise()
  .then(on-success, on-failure)

It practically reads like synchronous code, and it is guaranteed to execute in the same order as if it were synchronous. The promise! can also be used with a body of code instead of just a function:

let do-stuff(filename)
  let my-promise = promise!
    let text = yield read-file(filename)
    return text.to-upper-case()
  
  my-promise.then(on-success, on-failure)

Optional typing

edit

On parameters, the format param as Type may be used. These will be checked at runtime and the type inference engine will be aware of them.

The types StringNumberBoolean, and Function will check against typeof, not with instanceof, nullvoid, and undefined (alias for void) are also seen as valid types.

Arrays can be made using the syntax of [] for an array of any type or [Type] for a specific array. The contents will be checked at runtime.

Objects can be made using the standard object syntax with all values being types, e.g. {x: Number, y: String}

Functions with specific parameters or return value can be defined with -> for any parameter, any return. -> Type for a specific return value, Type -> Type for a single-argument with a specific return value, or (Type, Type) -> Type for two arguments with a specific return value.

Type unions can be made with |, e.g. Number|String. Order is irrelevant.

Functions are also permitted to bear a return type.

let increment(x as Number) x + 1
let greet(x as String|Number) "Hello, $x"
let run(x as -> Number) x()
let get-number() as Number -> num
let join(x as [String]) x.join ", "
let use-object(o as {x: Number, y: Number}) o.x + o.y

Types can also be placed on let statements, to help with the type inference engine.

let x as Number = f()

Curried functions

edit

Currying is a technique of transforming a function which takes multiple arguments in such a way that it can be called as a chain of functions. GorillaScript provides automatic currying of functions.

let add(a, b, c)^
  a + b + c

assert add(1, 2, 3) == 6 // same as before
let add-one = add 1
assert add-one(2, 3) == 6
let add-two = add 2
assert add-two(1, 3) == 6
let add-one-and-two = add-one 2 // or add 1, 2
assert add-one-and-two(3) == 6

Simply by putting the caret (^) after the function parameters, functions are automatically curried. Curried functions tend to find their use in functional-style programming and when coupled with the compose operators (<< and >>)

let sort-by(key, array)^
  array.sort #(a, b) a[key] <=> b[key]

let sort-by-id = sort-by \id
let sort-by-name = sort-by \name

let items =
  * id: 0
    name: "Dog"
  * id: 1
    name: "Car"
  * id: 2
    name: "Robot"
  * id: 3
    name: "Guitar"

let items-by-name = sort-by-name items
let items-by-id = sort-by-id items

Generics

edit

GorillaScript supports reified generic classes and functions, which are similar to .NET's generics or C++'s templates.

If a function is called with generic arguments, it will get converted like this:

func<String>("hello")
// turns into
func.generic(String)("hello")

How the function handles the .generic call can vary, but GorillaScript's classes are built to handle it.

class MyClass<T>
  def constructor(@value as T) ->
  
  def get-value() as T
    @value

let wrapped-string = MyClass<String>("hello")
let wrapped-number = MyClass<Number>(1234)
let wrapped-any = MyClass({})

In the previous case, MyClass<String>MyClass<Number>, and MyClass (same as MyClass<null>) all refer to three independent classes which have different semantics.

The MyClass<null above refers to absolutely any type being accepted, whether it's a String,Objectvoid, or some custom object.

Below is an example of a simple function (rather than a class) with generic arguments:

let func<T>(value as T) value

assert func<String>("hello") == "hello"
assert func<Number>(1234) == 1234
assert func(true) == true // no type specified, so any type is allowed
assert func(null) == null

Macros

edit

Macros are a way to write some code that will be used to write other code. They are similar to functions, except that they are evaluated during compile-time rather than run-time. This means that the macros themselves won't appear in the resultant JavaScript source code, as they will have been executed as-necessary and not need to exist when the JavaScript itself is executed.

Macro names can be normal identifiers, custom symbols, or a mixture of the two.

Any macros will be just as capable as any built-in GorillaScript syntax construct, as nearly all of GorillaScript's syntax are actually defined as macros

Support

edit

GNOME has JavaScript bindings with Gjs and GorillaScript supports this.

As long as you have gjs installed locally, there are three ways to run Gjs with GorillaScript:

  • REPL: On running gorilla --gjs or gjs-gorilla, a REPL opens that pipes its JavaScript code to gjs
  • Running a .gs file: On running gorilla --gjs myfile.gs or gjs-gorilla myfile.gs, it will compile the code of myfile.gs and pipe its results to gjs all at once.
  • Compile and run with gjs. On compilation of any .gs file as per usual with gorilla -c and run the result .js file with gjs. This is the standard method for distribution of code.

Aside from being written in GorillaScript rather than JavaScript, it will then function as any other Gjs script

Here is the standard Hello World Gjs code converted to GorillaScript:

#!/usr/bin/env gjs-gorilla

let {Gtk, GLib} = imports.gi

// Initialize the gtk
Gtk.init null, 0

let mwindow = new Gtk.Window type: Gtk.WindowType.TOPLEVEL
let label = new Gtk.Label label: "Hello World"

// Set the window title
mwindow.title := "Hello World!"
mwindow.connect "destroy", # Gtk.main_quit()

// Add the label
mwindow.add label

// Show the widgets
label.show()
mwindow.show()

Gtk.main()

Build support

edit

The recommended method to automatically build GorillaScript[1] files is through Grunt. It is easy to add GorillaScript support with the grunt-gorillaplugin.

Here is a sample case that demonstrates the compilation of three .gs files into a single .js file:

grunt.initConfig({
  gorilla: {
    dist: {
      options: {
        sourceMap: true
      }
      files: {
        "lib/code.js": ["src/one.gs", "src/two.gs", "src/three.gs"]
      }
    }
  }
});

The code extract below will compile all files in src/ to lib/:

grunt.initConfig({
  gorilla: {
    dist: {
      options: {
        sourceMap: true
      },
      files: [{
        expand: true,
        cwd: 'src/',
        src: ['**/*.gs'],
        dest: 'lib/',
        ext: '.js'
      }]
    }
  }
});

Coverage

edit

GorillaScript has built-in coverage instrumentation, that can be picked up by any tool that already supports JSCoverage, including Mocha.

To achieve this, the --coverage option must be passed on the command line or coverage: true in the Grunt task.

Browser support

edit

There is a browser-specific version of the GorillaScript compiler asextras/gorillascript.js. By including this it will run any <script type="text/gorillascript"> tags, with or without src="filename.gs". It compiles and runs each tag in-order.

Caveat: this will not run synchronously like normal <script> tags, so it is recommended to wait for an onload event or something similar.[1]

Once extras/gorillascript.js is included, the global GorillaScript object will be available.

Any compiled GorillaScript file should work in all browsers, including older browsers. Certain features like getters and setters may not work in engines that do not support them, and this will cause a runtime exception on defining such properties rather than a compile-time error.

Some examples:

  1. ^ a b c "ckknight/gorillascript". GitHub. Retrieved 2015-09-14.