a compile-to-JavaScript language designed to empower the user while attempting to prevent some common errors.
JavaScript has a lot of warts in the language and while the ECMAScript community is doing a good job with its upcoming revisions, it’s still left with a lot of the old cruft. Also, for those who want to code for older browsers, all the shiny new features that the newer versions of ECMAScript provide are for all intents and purposes unreachable.
Some of the features that are available in GorillaScript that are not (or are otherwise unwieldy) in vanilla JavaScript are:
==
and !=
perform type coercion behind the scenes and can be the cause of some subtle bugs.
// JavaScript, all the following are true
1 == "1"
0 != ""
3 == "03"
[] == ""
[] == 0
[] == ![]
[] == false
null != false
null == undefined
These could all be fixed by adding an extra equal sign, but GorillaScript makes ==
and !=
strict by default. If one really wants the unstrict equality operators, one can use ~=
and !~=
, but it is not recommended except for comparing against null or undefined, which has the nice postfix operator ?
.
+
operator (and +=
)+
can mean one of two things in JavaScript, addition or string concatenation. There is no way to know at compile-time what the consequences of using +
is unless one is 100% certain what each type is.
x + y
Is addition unless both x
and both y
are numbers, booleans, undefined, or null, with any mixed variation. If either is not one of those, at which point it performs string concatenation.
// JavaScript
1 + 2 === 3 // as expected
"hello, " + "world" === "hello, world" // as expected
"hello, " + 123 === "hello, 123" // sure, I can accept this
"1" + 2 === "12"
1 + "2" === "12"
// and for some oddities
false + false === 0
false + true === 1
true + true === 2
null + null === 0
isNaN(undefined + undefined)
[] + [] === ""
{} + {} === "[object Object][object Object]"
true + [] === "true"
new Date() + 1 === "Tue Jan 29 2013 20:25:58 GMT-0800 (PST)1" // or something like it
new Date() - 1 === 1359519958072 // or some other number
var foo = {
toString: function () { return 5; }
valueOf: function () { return "foo"; }
};
foo.toString() + 1 === 6
foo + 1 === "foo1"
GorillaScript solves this by splitting the operator into two: both +
for addition and &
for string concatenation.
// GorillaScript
1 + 2 == 3
"hello, " & "world" == "hello, world"
"hello, " & 123 == "hello, 123" // concatenation with numbers still works perfectly fine
1 & 2 == "12" // despite both being numbers, & always makes a string.
1 + "2" // TypeError
"1" + 2 // TypeError
"1" + "2" // TypeError
false + false // TypeError
null + null // TypeError
void + void // TypeError
[] + [] // TypeError
{} + {} // TypeError
new Date() + 1 // TypeError
new Date().getTime() + 1 == 1359519958072 // or some other number
As can be seen, the operators which don’t fit the proper types exactly fail immediately, allowing one to catch bugs as early as possible rather than allowing them to permeate through one’s programs.
Don’t worry about losing the bitwise and operator &
. It is now called bitand
.
All GorillaScript code is wrapped in an immediately-invoked function expression (IIFE) which has the declaration of "use strict"
. This ensures that on the engines that support it, strict semantics will be followed, meaning fewer bugs in the long run.
All operators check the types of their operands to assure that there will be no improper inputs and that any errors that do occur are caught as early as possible. This is highly similar to the custom "use restrict"
mode.
==
and !=
do not check the types, since they are already strict by default (in GorillaScript).<
, >
, <=
, >=
are restricted to primitive String
s and Number
s, but never mixing the two.+
only works on primitive Number
s.&
, the new string concatenation operator, works on primitive String
s and Number
s.-
, *
, /
, \
(floor division), %
, %%
(divisible-by), ^
(exponentiation, not bitwise xor), bitand
(instead of &
), bitor
(instead of |
), bitxor
(instead of ^
), bitnot
(instead of ~
), bitlshift
(instead of <<
), bitrshift
(instead of >>
), biturshift
(instead of >>>
), -
(unary negate), +
(unary coerce-to-number), and all their respective assignment operators (-=
, +=
, etc.) all only work on primitive Number
s.No other operators’ types are checked.
If one really wishes to work in an environment where the operands’ types are not checked, one can always prepend the operator with ~
, so there is a ~*
operator which performs multiplication without checking. It is recommended to instead parse input data into conforming types before performing operations on them.
Thankfully, GorillaScript is able to tell which types most values are, so in the general case, there should be little to no runtime type checking occurring.
GorillaScript uses two separate tokens for declaration as compared to assignment. Also, instead of JavaScript’s var
keyword, GorillaScript uses let
. Unless one specifies let mutable
, the local cannot be reset to any other value. This can prevent some errors and often helps with the clarity of one’s code.
Note: An object assigned to a local using let
can still be mutated, such as pushing values into an Array
. The reference to the array cannot be mutated, though.
Also, no undeclared variables can be altered, preventing unexpected global pollution (and typos).
let x = 5
x := 6 // Error
let mutable y = 5
y := 6 // perfectly fine
z := 6 // never declared, this is an error
As you may have noticed, there are two different operators for declaration =
as compared to assignment :=
. This is to clarify the difference. In an ideal program, having as little mutable state as possible is best, so if :=
jumps out, that’s a good thing.
Separate from the let
statement, const
allows you to specify that a value is a specific literal value. Any references in-code will be replaced by the value, allowing for pragma-like control over your code.
const DEBUG = false
DEBUG and assert(some-expensive-check())
Constants do not need to be in CONST_CASE
, though it is recommended.
There are also a few constant-like values provided:
__FILE__
__LINE__
__COLUMN__
__DATEMSEC__
__VERSION__
"version"
key from the closest package.json
.print __FILE__
print __LINE__
print __COLUMN__
print new Date(__DATEMSEC__)
MyProject.version := __VERSION__ // would work if we weren't in the browser
Also, if you’d like to have namespaced constants such as in other languages’ enum
, you can specify an object (or array) as the value. Any unknown access into these objects will result in a compile-time error rather than a value of undefined
const FRUIT_TYPES = {
apple: 1
banana: 2
cherry: 3
}
print FRUIT_TYPES.apple // 1
print FRUIT_TYPES.banana // 2
print FRUIT_TYPES.cherry // 3
// print FRUIT_TYPES.date // would error if uncommented
You don't have to use numbers as the values. In fact, you can use it as a way to compile-time check constant string values:
const FRUIT_TYPES = {
"apple"
"banana"
"cherry"
}
print FRUIT_TYPES.apple // "apple"
print FRUIT_TYPES.banana // "banana"
print FRUIT_TYPES.cherry // "cherry"
// print FRUIT_TYPES.date // would error if uncommented
Instead of using braces to dictate code blocks, GorillaScript opts for whitespace indentation as a way to mark blocks. Although this may be jarring at first and one may be skeptical, any good programmer properly indents his or her code to have a consistent whitespace anyway. GorillaScript does not dictate how many spaces or tabs are used, as long as it is consistent within any given block.
if hello
if loudly
"HELLO!"
else
"hi"
else
"Goodbye."
You may have also noticed the lack of semicolons. The parser is able to tell when the end of a statement is without them, so they are unnecessary.
Many of the operators have changed to provide more clarity or to free up the usage of certain symbols.
===
- ==
!==
- !=
==
- ~=
!=
- !~=
!x
- not x
+
- +
for addition, &
for string concatenation, type-checked&
- bitand
, type-checked|
- bitor
, type-checked^
- bitxor
, type-checked~x
- bitnot x
, type-checked<<
- bitlshift
, type-checked>>
- bitrshift
, type-checked>>>
- biturshift
, type-checked--x
- x -= 1
, type-checked++x
- x += 1
, type-checkedx--
- post-dec! x
, not recommended except for advanced casesx++
- post-inc! x
, not recommended except for advanced cases&&
- and
||
- or
, can no longer be used with and
unless one group is in parentheses.x ? y : z
- if x then y else z
key in obj
- obj haskey key
, reversed arguments. Can use not haskey
obj instanceof constructor
- Can also use not instanceof
delete x.y
- Returns the value of x.y
as well as deleting. Does not work on global variables anymore, use delete GLOBAL.x
Kept the same:
<
- type-checked<=
- type-checked>
- type-checked>=
- type-checked-
- type-checked*
- type-checked/
- type-checked%
- type-checked-x
- type-checked+x
- type-checkedx[y]
typeof x
throw x
- Can now be used as an expression like x or throw y
Added:
typeof! x
- displays the constructor name of the object, typeof! {} == "Object"
, typeof! [] == "Array"
throw? x
- Only throws x
if x
is not null or undefined.x ^ y
- Same as Math.pow(x, y)
x \ y
- Same as Math.floor(x / y)
xor
- For logical completeness with and
and or
x and= y
- Same as if x then x := y
x or= y
- Same as if not x then x := y
x in y
- Does x
exist in array y
. Can use not in
. Highly efficient if y
is a literal array.x ownskey y
- Does x
own the property named y
. Can use not ownskey
x <=> y
- if x == y, 0. if x < y, -1. otherwise, 1.x %% y
- Is x divisible by y
? Same as x % y == 0
.x min y
- Choose the lower number or lexicographically lesser stringx min= y
- If y
is less than x
, set x
to y
.x max y
- Choose the higher number or lexicographically greater stringx max= y
- If y
is greater than x
, set x
to y
.x?
- Is x
null
or undefined
?x ? y
- If x
is null
or undefined
, then y
, otherwise keep the value of x
. y
may not be executed.x ?= y
- If x
is null
or undefined
, then set x
to y
. y
may not be executed.x[y] ownsor= z
- If x
does not own the key y
, then set x[y]
to z
. z
will not be executed if x
already owns y
. Handy for caching.is-array! x
- True if x
is an Array (and not just an Array-like object). Works on arrays from a different context.is-object! x
- True if x
is an Object and not null. Works on objects from a different context.x to y
- Create an array from x
to inclusive y
.x til y
- Create an array from x
until exclusive y
.array by step
- Take every step
th value from the array. If step
is less than 0, go in reverse.x to y by step
- Create an array from x
to inclusive y
, stepping by step
.x til y by step
- Create an array from x
until exclusive y
, stepping by step
.x instanceofsome y
- Iterate over array and check with instanceof
. Highly efficient if y
is a literal array.x is y
- Works like the ECMAScript6 Object.is, which is like GorillaScript’s ==
, but differentiating between 0
and -0
and properly comparing NaN is NaN
. Not recommended to use unless you know you’re working with numbers.x isnt y
- Same as not (x is y)
x << y
- Compose x
and y
, like #(...args) x(y(...args))
x >> y
- Compose y
and x
, like #(...args) y(x(...args))
x |> f
- Pipe x
into f
, like f(x)
f <| x
- Pipe x
into f
, like f(x)
x <<< y
- Import all properties from y
into x
, like for k, v of y; x[k] := v
x >>> y
- Import all properties from x
into y
, like for k, v of x; y[k] := v
There are two ways to specify functions, one directly using let
, and one as an anonymous function. Also, unlike JavaScript, the last expression is automatically returned (unless tagging the function with !
). Functions can be called optionally without parentheses, as long as it is unambiguous.
let increment(x)
x + 1
assert increment(0) == 1
assert (increment 1) == 2
assert 3 == increment 2
let run(callback)
callback()
assert run(# "hello") == "hello"
assert (run # "there") == "there"
assert run(#
"you") == "you"
assert run(#
let x = "guys"
"good " & x) == "good guys"
// this syntax also works
let f() Math.random()
console.log f()
The outer this
can also be captured by appending @
to the head of the function declaration, creating a “bound” function. This is similar to ECMAScript 5’s Function.prototype.bind
, but more efficient since a hidden _this
variable is used rather than an extra function call.
let func()
let inner()@
this
assert func() == this
Inside double-quoted strings ("like this"
), not single-quoted strings ('like this'
), one can specify string interpolations using the $
symbol, followed by an identifier or a parenthetical expression.
let hello(name)
"Hello, $name"
assert hello("World") == "Hello, World"
assert hello("Universe") == "Hello, Universe"
// or
let greet(names)
"Hello, $(names.join ', ')"
assert greet(["World", "Universe"]) == "Hello, World, Universe"
To specify an optional parameter, one simply need to specify = value
in the function parameter list.
let hello(name = "World")
"Hello, $name"
assert hello() == "Hello, World"
assert hello("Universe") == "Hello, Universe"
If a value is passed in that is null or undefined, it will be automatically turned into the default value.
Instead of using JavaScript’s atrociously broken arguments
special, one can specify a spread parameter by prefixing ...
. Only one can occur in a function parameter list, but it can be at any position.
let hello(...names)
if names.length == 0
"No one is here"
else
"Hello, $(names.join ', ')"
hello() == "No one is here"
hello("World") == "Hello, World"
hello("World", "Universe") == "Hello, World, Universe"
And so that callers don’t feel bad about themselves, you can call with spread as well.
let f(a, b, c) [a, b, c]
let items = [1, 2]
f(0, ...f(...items, 3), 4)
Although completely optional to use if you prefer using camelScript
-style identifiers, one can now specify identifiers with dashes, such as my-name
or gorillas-are-awesome
. They are turned into myName
and gorillasAreAwesome
, respectively.
let gorillas-are-awesome = "Yes, they are."
All numbers can have arbitrary underscores in the middle of them, which can be used for thousands separators or bitwise n-bit separators. (1_234_567
or 0x1234_5678_90ab_cdef
)
Decimal numbers can have inline-comments appended to them after an underscore. (1000_ms
)
Octals use the format 0o12345670
instead of 01234567
, to help with clarity.
Binary numbers are available with the format 0b10101010
.
Arbitrary-radix numbers are available by specifying a decimal number between 2 and 36, r
, and the number. (4r01230123
, 36rjdhremn
)
let time = 10_000_ms
let hex = 0x1234_5678
let octal = 0o070
let binary = 0b1010010101
let radix = 36rNFfdH45
let float = 123_456.789_012
Non-decimals do support floating point unlike JavaScript, though that is a lesser-used feature.
Aside from the already-seen string interpolation, there are also triple-quoted strings, which allow for multi-line and are indentation-friendly.
let name = "Jimmy"
let string = """
Hello there.
I have a story to tell you, $name.
I can't think of it right now, though.
"""
The indentation is stripped (but not line 2’s, since that's deliberate), and interpolation is done for $name
, and the first and last newlines are removed. This all occurs at compile-time, so your result code will be as fast as possible.
There are also string like '''this'''
which do not have interpolation, if you wish to use it.
There is also a short syntax for single-word strings that also convert dashed-names
to camelCase
just as normal identifiers do.
assert "Jimmy" == \Jimmy
assert object.some-key == object[\some-key]
assert "someKey" == \some-key
GorillaScript includes all the escape sequences you may be familiar with in JavaScript, such as "\0"
, "\t"
, "\v"
, and many more. You needn’t worry about some non-standard escape codes such as "\v"
, as GorillaScript compiles to the lowest common denominator.
It also includes the \u
unicode escape sequence which takes 4 hex characters trailing it and \x
which takes 2 hex characters trailing it.
Unlike JavaScript, GorillaScript can properly handle unicode code points greater than 0xFFFF
, by having the \u{}
syntax. Inside the braces, one can put between 1 and 6 hex characters as long as the representation doesn't exceed 0x10FFFF
. This will be split up into two UTF-16 code points if exceeding 0xFFFF
.
let escapes = "\b\f\r\n\t\v\0\1\2\3\4\5\6\7"
let hex = "\xe9"
let unicode = "\u1d25"
let large-unicode = "\u{20bb7}"
assert large-unicode.length == 2 // takes up 2 UTF-16 characters, one Unicode code point
Although the standard JavaScript-style syntaxes work, there are a few other ways to specify objects and arrays.
let list = [1, 2, 3]
let other-list = [...list, 4, 5, 6] // now contains [1, 2, 3, 4, 5, 6]
// another way to specify an array
let items =
* "Apples"
* "Bananas"
* "Cherries"
let obj = {
list // same as list: list
sum: 6
f()
"result" // same as f: # "result"
}
let great-apes =
bonobos:
awesomeness: "pretty cool"
population: 40_000
humans:
awesomeness: "let's not say anything bad about these guys"
population: 7_000_000_000
gorillas:
awesomeness: "clearly the best"
population: 100_000
let special = {
[1 + 2]: "three"
"key$i": "interpolated key"
class: "JavaScript would fail on the 'class' key."
}
To specify the prototype of an object:
let parent = { hello: \there }
let child = { extends parent
value: 1
}
assert child.hello == \there
assert child.value == 1
Map
and Set
are available in the upcoming ECMAScript 6 and in some existing JavaScript engines. There are also shims to provide support in engines that do not support it out-of-the-box.
GorillaScript will automatically provide a shim if used, but it is recommended to use either a native implementation or a more-efficient shim.
Map
is similar to Object
, except its keys are not required to be String
s, but can be any type. Also, access and assignment is done through method calls rather than raw dot-access.
Set
is a collection of unique values, similar to a Map
where all values are true
.
GorillaScript has syntax for declaring both of these as literals, just as one can do with Array
s or Object
s.
let obj = {}
let other = {}
let map = %{
[obj]: 1
[other]: "hello"
}
assert map.get(obj) == 1
assert map.get(other) == "hello"
map.delete other
assert map.has obj
assert not map.has other
map.set other, "there"
assert map.has other
assert map.get(other) == "there"
let set = %[obj, other]
assert set.has(obj)
assert set.has(other)
set.delete(other)
assert not set.has(other)
set.add other
assert set.has(other)
set.add other // does nothing, already in the set.
To correlate with the if
statement, there is also an unless
statement which works as its exact opposite.
if hates-bananas
"You monster."
else unless loves-gorillas
"How could you?"
else if likes-the-gorillaz
"Fire comes out of the monkey's head."
else
"Well, at least you love gorillas and don't hate bananas."
GorillaScript provides many different looping constructs, all fitted for their own purposes.
Normal while loop, same as JavaScript:
let mutable i = 0
while i < 10
console.log i
i += 1
Opposite of a while loop, until
:
let mutable i = 0
until i >= 10
console.log i
i += 1
Better version of the above, acts like JavaScript’s for(;;)
let mutable i = 0
while i < 10, i += 1
console.log i
Even better version:
for i in 0 til 10
console.log i
Or if you want to go in reverse,
for i in 9 to 0 by -1
console.log i
Or by twos:
for i in 0 til 10 by 2
console.log i
You don’t have to use literal numbers, they can be any expression in GorillaScript. You can also use to
, til
, and by
to make arrays outside of loops.
The difference between til
and to
is that til
goes up until it hits the end, but to
includes the end.
To iterate over an array,
for food in ["Apples", "Bananas", "Cherries"]
console.log food
If you want its index,
for food, index in ["Apples", "Bananas", "Cherries"]
console.log "$index: $food"
You can also get the total length of the array, if you need it:
for value, index, length in some-array
f()
To iterate an array in reverse (slightly more efficient):
for value, index in some-array by -1
// index goes from some-array.length - 1 down to 0.
console.log value
To iterate only a part of the array:
for value in some-array[2 to 5]
console.log value
It works similarly to some-array.slice(2, 6). You can slice outside of loops as well.
To iterate over objects, you can use of
instead of in
:
for key, value of some-object
console.log key, value
GorillaScript automatically runs an Object.prototype.hasOwnProperty check on the key. To avoid this, use:
for key, value ofall some-object
console.log key, value
Any loop can be an expression simply by returning it or assigning it to a variable. This will create an array.
let squares = for value in 0 to 10
value ^ 2
// squares now contains [0, 1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
Single-line loops can be specified as so:
let squares = for value in 0 to 10; value ^ 2
There are also reducing loops which work by placing one of the reducers (first
, every
, some
, filter
, reduce
) on the for
or while
loop.
let all-good = for every item in array; item.is-good()
let has-bad = for some item in array; item.is-bad()
let best-value = for first item in array
if item.is-best()
item.value
let only-good = for filter item in array; item.is-good()
let sum = for reduce value in [0, 1, 2, 3, 4], current = 0
current + value
These work on any for
or while
loop.
A common bug in JavaScript is when one creates a function inside a loop that refers to the current index or value or some other variable that changes each loop iteration. GorillaScript solves this problem by wrapping any for loop that creates a function in another function, so the following works by seemingly lexically-scoping the inside of the for loop rather than abiding by JavaScript’s normal function scoping.
let funcs = []
for i in 0 til 10
funcs.push # i
funcs[0]() == 0
funcs[5]() == 5
With no extra work on the developer’s part.
As mentioned briefly earlier, one can use the to
, til
, and by
syntaxes to slice on arrays.
let array = [\a, \b, \c, \d, \e]
assert-arr array[1 to 3], [\a, \b, \c]
assert-arr array[1 to 5 by 2], [\a, \c, \e]
assert-arr array[5 to 1 by -2], [\e, \c, \a]
assert-arr array by -1, [\e, \d, \c, \b, \a]
assert-arr array[0 to -1], [\a, \b, \c, \d, \e]
assert-arr array[0 to Infinity], [\a, \b, \c, \d, \e]
The negative values work just as they do in .slice
, unlike normal accessing
GorillaScript can’t wrap every single access in a check to see if the child is a negative value (as Python or some other languages do), due to efficiency, but it does provide a nice syntax to make it easier.
let array = [\a, \b, \c, \d, \e]
assert array[* - 1] == \e
assert array[* - 2] == \d
assert array[* \ 2] == \c // halfway point in the array
When a standalone *
is encountered in an index, it is converted to the current array’s .length
.
Cascades are a way to repeatedly send messages to an object that would otherwise not have a fluent interface (i.e. returning this
).
let array = [\a, \b, \c, \d, \e]
..push \f
..reverse()
..sort()
assert array.length == 6
Cascades can also be nested as such:
document.query-selector \h1
..style
..color := \red
..font-size := "200%"
..inner-HTML := "Hello, world!"
Unlike JavaScript, for
loops, while
loops, try
blocks, and if
constructs can be used as expressions.
let array = for i in 0 to 10; i
let trial = try
throw Error()
catch e
"Caught something"
assert trial == "Caught something"
let check = if youre-amazing
"It's true"
else
"Not amazing."
If one wishes to check a value against null or undefined, the existential operator (?
) can be used.
let exists = value?
It can also be used for access soaking
let inner = value?.which.might?.not.exist
Turns into
let inner = if value?
let _ref = value.which.might
if _ref?
_ref.not.exist
It can also be used for function checking
let result = f?()
Turns into
let result = if typeof f == \function
f()
in
operatorTo correlate with array iteration, in
checks if a value is in an array, in a similar way to array.indexOf(value) != -1
would.
assert \hello in [\hi, \there, \hello]
To check if an object contains a key, see the haskey
operator.
haskey
and ownskey
Instead of using JavaScript’s in
, haskey
is used to verify a key’s existence on an object. Also, ownskey
is also available to check if a key exists on an object without prototype-checking.
let parent = { alpha: \bravo }
let child = { extends parent, charlie: \delta }
assert parent haskey \alpha
assert parent ownskey \alpha
assert parent not haskey \charlie
assert parent not ownskey \charlie
assert child haskey \alpha
assert child not ownskey \alpha
assert child haskey \charlie
assert child ownskey \charlie
Sometimes you may have an object where you want to access its key but only in the case of the object owning the key as a property.
assert if parent ownskey key
parent[key]
// functionally equivalent to
assert parent![key]
Or, for a known key:
assert if parent ownskey \key
parent.key
assert parent!.key
In JavaScript, if you wish to specify the this
argument passed to a function, one must use either .call
or .apply
. GorillaScript provides the @
syntax:
let f() this
let obj = {}
assert obj == f@ obj
assert obj == f@(obj)
Array.prototype.slice@ arguments, 1
It transparently converts to .call
or .apply
(whichever is more appropriate), using the first argument as its this
.
ECMAScript 5 supplies Function.prototype.bind
as a way to bind functions to a specific this (and specify arguments). To do a similar binding in GorillaScript, one need only use the familiar @
syntax with access.
let obj = {
f: # this
}
let bound = obj@.f
assert bound() == obj
let unbound = obj.f
assert unbound() == window
GorillaScript provides a way to make classical-style classes. JavaScript does not have classes normally, so GorillaScript’s creation is slightly hackish, but works for the general case.
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!"
Classes can extend
other classes and call into their superclass with super
. The constructor functions automatically check the this
argument 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.
GorillaScript, like ECMAScript 6, provides a destructuring declaration.
let [x, y] = [1, 2]
assert x == 1
assert y == 2
let {a, b: c} = {a: 3, b: 4}
assert a == 3
assert c == 4
These can be nested like so:
let [a, {b, c: [d]}] = get-data()
And the spread operator (...
) can be used once per array destructure:
let [value, ...rest] = array
Like JavaScript, GorillaScript provides switch
. The only major exception is that JavaScript is fallthrough-by-default, and GorillaScript is break-by-default. GorillaScript can also specify multiple values to check at once instead of having multiple cases. switch
can also be used as an expression.
switch value
case 0, 1, 2
"small"
case 3, 4, 5
fallthrough // in the last position of the case, causes the case to fall through to the next case.
case 6, 7, 8
"large"
default
"unknown"
Unlike JavaScript, GorillaScript provides a topicless switch
, which works by checking if each case is truthy rather than comparing against a value. fallthrough
works the same way as a normal switch statement. This does not generate a JavaScript switch
statement, as it is extremely inefficient to use switch
in JavaScript without constant case
checks.
switch
case is-good()
"good"
case is-bad()
"bad"
default
"neutral"
Unlike JavaScript, GorillaScript requires a default
block as part of the switch
. If not specified, then it will cause an error if it is ever reached. Granted, if you cover all possible situations in your case
s, this is not a problem. Also, you can always just specify default; void
in order for nothing to occur.
switch value
case 0, 1
"low"
case 2, 3
"high"
// no default
Try-catch also works similarly to JavaScript, except that catches can have type-checks and the notable exception of the else
statement, for when no error was caught, but occurs before the finally
statement.
try
something-dangerous()
catch e as SpecificError
handle-error(e)
catch e
uh-oh()
else
whew()
finally
cleanup()
At least one of catch
, else
, or finally
must be used, but so can all three.
Unlike JavaScript, Regular Expressions borrow the string syntax simply prefixed with r
and postfixed with any RegExp flags deemed necessary. 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
If one doesn’t wish to use simple string concatenation with string interpolation, %
can be prefixed to any double-quoted string to return an array instead which can then be interpolated in a custom manner. All even-numbered strings are guaranteed to be source literals and all odd-numbered values (might not be strings) are interpolated input.
The following is an example of automatic HTML escaping, but the same concept could be applied to SQL strings or practically any string with unsafe input.
class SafeHTML
def constructor(@text as String) ->
def to-string() @text
let to-HTML = do
let escapes = {
"&": "&"
"<": "<"
">": ">"
'"': """
"'": "'"
}
let replacer(x) escapes[x]
let regex = r"[&<>""']"g
let escape(text) text.replace(regex, replacer)
#(arr)
(for x, i in arr
if i %% 2 or x instanceof SafeHTML
x
else
escape String(x)).join ""
assert "<h1>normal</h1>" == to-HTML %"<h1>normal</h1>"
let evil-name = "<\"bob\" the 'great' & powerful>"
assert "<"bob" the 'great' & powerful>" == to-HTML %"$evil-name"
assert "<span><"bob" the 'great' & powerful></span>" == to-HTML %"<span>$evil-name</span>"
assert "<span><\"bob\" the 'great' & powerful></span>" == to-HTML %"<span>$(SafeHTML evil-name)</span>"
Iterators are an ECMAScript 6 feature that have been in Mozilla’s JavaScript since 1.7. GorillaScript can both produce and consume such iterators. The API used is specified in the TC39 March 2013 notes
for value, index from some-iterable
console.log value
Turns into the following code:
let _iter = some-iterable.iterator()
let mutable index = 0
try
while true, index += 1
let _item = _iter.next()
if _item.done
break
let value = _item.value
console.log value
finally
try
_iter.close()
catch e
void
Which means that any object that implements the iterator
method acts as an iterable. That return value merely needs to implement the next
method.
If Array.prototype
were to implement iterator
, which the ECMAScript 6 draft is recommending, one could iterate over Array
s, Set
s, Map
s, but one can iterate over any custom type now or by simply adding an Array.prototype.iterator
method.
Production of iterators is easy as well in GorillaScript. You need merely append the *
to your function declaration and use the yield
(or yield*
) expression.
let fib()*
let mutable a = 0
let mutable b = 1
while true
yield b
let tmp = a
a := b
b += tmp
Produces an iterable which returns the infinite sequence of fibonacci numbers. The resultant code looks something along the lines of:
let fib()
let mutable a = 0
let mutable b = 1
let mutable _state = 0
{
iterator: # this
next: #
while true
switch _state
case 0
_state := 1
return { -done, value: b }
case 1
let tmp = a
a := b
b += tmp
_state := 0
}
The state machine is made for you and any GorillaScript construct can be used inside a generator function.
One could then easily then use the fib iterator:
for value from fib()
console.log value
if value > 4000000
break
One can also use yield
in an expression position, not just return
as a way to receive information from custom iterators. This isn’t affected by a for-from
loop, but some libraries and patterns can rely on it.
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, all one needs to do is yield
other Promises 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's guaranteed to execute in the same order as if it were synchronous.
You can also use promise!
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)
to-promise!
To facilitate the easy use of Promises, there is also a to-promise!
macro that converts a node.js-style function call into a self-fulfilled Promise, as long as that function takes a callback as its last argument with the signature function (error, result)
.
let do-stuff(filename)
let my-promise = promise!
let p = to-promise! fs.read-file filename, "utf8"
let text = yield p
return text.to-upper-case()
my-promise.then(on-success, on-failure)
from-promise!
Along with to-promise!
is from-promise!
, which converts a Promise into a function which takes a node.js-style callback with the signature function (error, result)
.
let do-stuff(filename)
let my-promise = promise!
let p = to-promise! fs.read-file filename, "utf8"
let text = yield p
return text.to-upper-case()
let node-func = from-promise! my-promise
node-func #(err, value)
if err?
handle-error err
else
handle-success value
It is recommended to use promises over node.js-style callbacks in nearly all cases, due to the composability of code.
fulfilled!
and rejected!
As a convenience, you can use fulfilled! value
to produce an already-fulfilled promise or rejected! reason
to produce an already-rejected promise.
delay!
In case you wish to have a Promise that fulfills after a known amount of time, delay!
is available.
let take-a-while = promise! #()*
for i in 0 til 10
calculate(i)
yield delay! 100_ms
You can also give delay!
a value to be fulfilled with:
let promise = delay! 1000_ms, "hello!"
promise.then #(value)
assert value == "hello!"
some-promise!
If you want to easily create several Promises and get a result when any Promise within either fulfills or rejects, some-promise!
is perfect for the task.
let read-file-or-timeout(filename)
some-promise! [
read-file filename
delay! 1000_ms
]
Which very nicely returns a Promise that either reads a file or times out after waiting too long. The same could be done for fetching pages from the internet or waiting on another process to complete.
every-promise!
If you wish to execute several Promises in parallel and do something after they all complete (or any of them fail), every-promise!
is there for you.
let read-many-files(filenames)
let file-promises = []
for filename in filenames
file-promises.push read-file filename
every-promise! file-promises
Piggy-backing on the Promise system, GorillaScript provides the promisefor
looping construct that allows for asynchronous execution. It can be used either inside of other Promises or in any normal function, it will return a Promise in both cases.
If you need to run a loop serially (as opposed to in-parallel), you should simply make a promise!
and use the standard looping construct.
let loop = promisefor(3) filename in filenames
let text = yield read-file filename
return text.to-upper-case()
loop.then on-success, on-error
The previous code will run the body of the loop in parallel up to 3 at a time, in-order, until the loop is exhausted. The result will be an array, also in-order, regardless of the individual loop bodies' completion time.
If you wish to use an async-friendly syntax without resorting to Promises, there are several syntax constructs to allow for this.
It is recommended to use the Promise-based syntax over this, as it is more composable. In it, you can use all the standard language constructs such as loops, conditionals, etc. and GorillaScript will convert it all nicely for you. The following examples require some mangling of your code and manual callback invocation to provide the illusion of top-to-bottom code execution.
async err, text <- fs.read-file "somefile.txt", "utf8"
throw? err
console.log text
No indentation necessary, every line following the async call will be part of the implicitly-constructed callback. The resultant code looks like:
fs.read-file "somefile.txt", "utf8", #(err, text)@
throw? err
console.log text
Although that may not seem too useful with only one block of indentation, when dealing with complex database access or multiple sources of input, it is very easy to become overwhelmed.
In the case where you don’t want to throw the error, but instead need to pass it to a callback (which takes the error as the first argument), you have one of two options:
let run(callback)
async err, text <- fs.read-file "somefile.txt", "utf8"
if err?
return callback err
callback null, do-something(text)
Or you can use
let run(callback)
async! callback, text <- fs.read-file "somefile.txt", "utf8"
callback null, do-something(text)
Which is functionally equivalent.
Often, one may use a library to manage asynchronous handling of loops and iteration over arrays, but that is all built right into GorillaScript. Every loop construct you’ve seen works seamlessly by prepending async
and specifying the next
callback.
The next
callback expects 0 to 2 arguments, if it receives a non-null
-or-undefined
value as its first argument, it will halt the loop immediately, as an error or other break has occurred. If it receives a second argument, it will append that value to an array, which may be optionally requested by the developer.
asyncfor
loops can specify their level of parallelism manually (defaulting to 1
, which runs serially). If parallelism is set to 0
, it runs completely parallel.
// read two files at a time
asyncfor(2) err, array <- next, filename in ["a.txt", "b.txt", "c.txt", "d.txt"]
async err, text <- fs.read-file filename, "utf8"
if err?
// if an error occurs, it propagates up and no more files will be read, and post-async execution
// will occur immediately.
return next err
next null, { filename, text }
throw? err
console.log array // array will now be filled with objects like { filename: "a.txt", text: "lots of text here" }
Which turns into a device which looks something like:
let _array = ["a.txt", "b.txt", "c.txt", "d.txt"]
__async-result 2, array.length,
#(i, next)@
let filename = array[i]
fs.read-file filename, "utf8", #(err, text)@
if err?
return next err
next null, { filename, text }
#(err, array)@
throw? err
console.log array
There is also asyncwhile
and asyncuntil
, which work similarly to their normal constructs but require the next
argument to inform the code that its execution has completed one way or another.
If you wish to use conditionals which may or may not have asynchronous code inside their bodies, asyncif
or asyncunless
is necessary.
asyncif text <- next, some-boolean()
async err, text <- fs.read-file filename, "utf8"
throw? err
next(text)
// else is optional, will automatically call next() if not provided
console.log text
Which turns into something like:
let next(text)
console.log text
if some-boolean()
fs.read-file filename, "utf8", #(err, text)@
throw? err
next(text)
else
next()
Occasionally, one needs to return a value such as false
in an event handler, while using an asynchronous command. This is where the returning
statement comes in. It works just like return
, only it runs at the end of its block rather than where it is placed.
register-event #(filename)
returning false
async err, text <- fs.read-file filename, "utf8"
throw? err
console.log text
Which turns into:
register-event #(filename)
fs.read-file filename, "utf8", #(err, text)
throw? err
console.log
return false
On parameters, one can put types in the format param as Type
. These will be checked at runtime and the type inference engine will be aware of them.
The types String
, Number
, Boolean
, and Function
will check against typeof
, not with instanceof
.
null
, void
, 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.
One can also place a return type on functions, which is not checked at runtime, but is used by the type inference engine.
Some examples:
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()
In functional programming, it is often handy to use one of the built-in operators as a function, and GorillaScript provides this capability.
let add = (+) // same as #(x, y) x + y
assert add(5, 6) == 11
let square = (^ 2) // same as #(x) x ^ 2
assert square(10) == 100
let double = (2 *) // same as #(x) 2 * x
assert double(5) == 10
let invert = (not) // same as #(x) not x
assert invert(true) == false
assert invert(false) == true
assert 10 == [1, 2, 3, 4].reduce (+)
Any binary operator can be used this way, and any unary operator can be used as long as it does not share the same token as a binary operator.
One can also use the same syntax for accesses and method calls
let get-length = (.length) // same as #(x) x.length
assert get-length("hello") == 5
let to-hex = (.to-string(16)) // same as #(x) x.to-string(16)
assert to-hex(255) == "ff"
Getter and setter support is up to the engine to support, and is therefore not recommended to be used in the general case. Even Internet Explorer 8 only provides support if the object is a DOM Element, so I cannot recommend using them on a broad scale unless you control the JavaScript engine (such as in node.js).
There are two ways to define getters and setters:
let obj =
_x: 0
get x: # @_x
set x: #(value)! @_x := value
_y: 0
property y:
get: # @_y
set: #(value)! @_y := value
configurable: true
enumerable: true
When using the get
and set
pair, they must be defined next to each other (order is irrelevant). One can also supply only get
or only set
.
When using the property
syntax, the value is the same as one calling Object.defineProperty
, in fact, that is all that is happening behind the scenes.
If Object.defineProperty
is not available, then it will try to sanely fallback, but it might not be successful. If specifying value
instead of get
/set
, it will always work, but the configurable
/enumerable
/writable
attributes are not guaranteed.
Note: this might throw an Exception
at runtime if specifying get
/set
and the engine does not support getters/setters. If always using value
, this will not fail, even if Object.defineProperty
is not defined.
break
/continue
JavaScript provides a way to add a label to a block and then be able to break
or continue
(if it is a loop) on that block.
Although it is not recommended practice, as one is probably better served by rethinking the algorithm, GorillaScript does provide the capability.
let mutable sum = 0
label! outer for i in 0 to 10
for j in 0 to 10
if i == j
continue outer
sum += i
As you can see, the outer loop has the label outer
on it and the continue
also references that same label, meaning it will continue on the outer loop rather than the inner.
GLOBAL
identifierSince JavaScript can be run outside the browser now, one cannot rely on window
always being the global object, so GorillaScript provides the GLOBAL
identifier for just this case, which will be window
or global
or the outermost this
, whichever one works best. It is UPPER-CASE and therefore loud for a reason: try to avoid globals when possible.
assert Math == GLOBAL.Math
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 a nice way to automatically curry 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 work extremely well in functional-style programming and are extremely handy 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
It is not recommended to use currying with default arguments (e.g. #(x = 0)^
) or spread arguments (e.g. #(...args)
), since the curry helper checks a function's .length
to determine how many arguments before calling the actual function, and it may be non-obvious.
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 is converted as so:
func<String>("hello")
// turns into
func.generic(String)("hello")
How the function handles the .generic
call can vary, but GorillaScript's classes handle it elegantly:
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.
You may be wondering about MyClass<null>
, but it simply refers to absolutely any type being accepted, whether it's a String
, Object
, void
, or some custom object.
Type-checking works as expected and T
(or whatever type you call it) is available at runtime to refer to the bound generic type.
Here 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
It is recommended that you name your generic argument T
or have it start with the letter T
, as in TKey
or TResult
.
GNOME now has JavaScript bindings with Gjs and GorillaScript supports this to the best of its ability.
As long as you have gjs installed locally, there are three ways to run Gjs with GorillaScript:
REPL: If you run gorilla --gjs or gjs-gorilla, a REPL opens that pipes its JavaScript code to gjs
Running a .gs file: If you run 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. You can compile any .gs file as per usual with gorilla -c and run the result .js file with gjs. You'll want to do this before distributing any code anyway.
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()
The recommended way to automatically build GorillaScript files is through Grunt. It is easy to add GorillaScript support with the grunt-gorilla plugin.
Here is a very simple example of how to compile 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"]
}
}
}
});
And to compile all your files in src/ to lib/:
grunt.initConfig({
gorilla: {
dist: {
options: {
sourceMap: true
},
files: [{
expand: true,
cwd: 'src/',
src: ['**/*.gs'],
dest: 'lib/',
ext: '.js'
}]
}
}
});
GorillaScript has built-in coverage instrumentation, that can be picked up by any tool that already supports JSCoverage, including Mocha.
All you have to do is pass in the --coverage option on the command line or coverage: true
in the Grunt task.
Although not recommended for production code, there is a browser-specific version of the GorillaScript compiler as extras/gorillascript.js. Once this is included on your page, 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.
Once extras/gorillascript.js is included, the global GorillaScript
object will be available, allowing you to use GorillaScript.compile("code", { options: "here" })
and several other commands.
In fact, this is exactly how this page is designed. Most of the JavaScript (including in the code examples) is compiled on-the-fly, and the Try it out environment is handled in this very way.
If define
and define.amd
are properly defined, they will be used instead of setting window.Gorilla
, as a proper module definition
Note: 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, but this will cause a runtime exception on defining such properties rather than a compile-time error.
If you would like to contribute to this list, please file an issue.
Note: This is probably the most complex section in this documentation.
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.
Due to GorillaScript's flexible syntax, there are five types of macro syntaxes.
All the macros you can write are just as capable as any built-in GorillaScript syntax construct, as nearly all of GorillaScript's syntax are actually defined as macros.
Say you want an operator that evaluates whether or not an expression is a positive number:
macro operator unary +?
// the following idents are available:
// op (which will always be "+?" in this case)
// node (which will be the node to the right of this unary prefix macro)
// we'll use an inline AST expression rather than building the nodes ourself:
// the '$' before 'node' means we will interpolate it, just like with strings.
ASTE $node > 0
assert(+?500)
assert(not +?(-500))
assert(+?f())
Say you have a set (from type theory) and it would be nice to have a 'union' operator instead of relying on methods:
macro operator binary union, ∪
// the following idents are available:
// op (which will either be "union" or "∪", i.e. "\u222a")
// left (which will be the node to the left of the binary operator)
// right (which will be the node to the right of the binary operator)
ASTE $left.union($right)
assert(alpha union bravo union charlie)
assert(alpha ∪ bravo ∪ charlie ∪ delta)
Mostly used when paired with a binary operator, I'll reuse the previous example
macro operator assign union=, ∪=
// the following idents are available:
// op (which will either be "union=" or "∪=", i.e. "\u222a=")
// left (which will be the node to the left of the assign operator)
// right (which will be the node to the right of the assign operator)
// here we need to potentially cache parts of the left-hand-side, since we're referencing it twice.
// this will convert a()[b()] to tmp1[tmp2].
@maybe-cache-access left, #(set-left, left)
ASTE $set-left := $left.union($right)
let mutable alpha = some-set()
alpha union= bravo
alpha ∪= charlie
get-obj()[key()] union= delta()
Unlike the previous operators, this has a syntax that looks like any ordinary function call, with the exception that the name could also be or contain a symbol.
Here is an example of what you shouldn't do in a macro:
macro square(value)
ASTE $value * $value
assert(square(3) == 9)
let mutable i = 0
let f()
i += 1
assert(square(f()) == 2) // what? 2 isn't a square, but f() was called twice, uh-oh.
square(Math.random()) // two different random numbers multiplied together.
Here is how the previous macro should have been written:
macro square(value)
@maybe-cache value, #(set-value, value)
ASTE $set-value * $value
assert(square(3) == 9)
let mutable i = 0
let f()
i += 1
assert(square(f()) == 1) // huzzah, 1 * 1 == 1
assert(square(f()) == 4) // huzzah, 2 * 2 == 4
square(Math.random()) // the same random number squared with itself.
There are practically no restrictions on how you use the arguments passed to your macros, so you have to be aware of edge cases and cache when necessary.
Another nice trick about macros is that not all of the arguments have to be evaluated, for example:
macro get-or-add(cache, key, value)
@maybe-cache cache, #(set-cache, cache)
@maybe-cache key, #(set-key, key)
let tmp = @tmp() // we need a temporary variable to store things into.
AST
let mutable $tmp = $set-cache[$set-key]
if $tmp == void
$cache[$key] := $tmp := $value
$tmp
let some-cache = {}
let x = get-or-add some-cache, key, something-expensive()
let y = get-or-add some-cache, key, something-expensive() // something-expensive() is never executed
Even though it appears that something-expensive()
would be executed twice, it is only executed the first time, even without wrapping it in a function or anything like that (as would be necessary without macros).
This is where the real magic sets in. By allowing custom syntax, you can design any syntax construct that your mind can think up. In fact, nearly all GorillaScript syntax uses this, from the lowly if
statement to the prestigous try-catch
.
macro timer!
// either an indented Body or a single-line Statement are accepted
syntax body as (Body | Statement)
// the following idents are available:
// macro-name (which will always be "timer!" for this macro)
// body (which will contain the Body or Statement)
let time = @tmp \time
AST
let mutable $time = new Date().get-time()
$body
new Date().get-time() - $time
// other syntaxes could be specified here
let time = timer! something-expensive()
let other-time = timer!
various()
expensive()
calls()
Or if you’re pining for a when
statement separate from if
,
macro when
syntax test as Logic, "then", body as Expression
// the following idents are available:
// macro-name (which will always be "when" for this macro)
// test
// body
ASTE if $test then $body
syntax test as Logic, body as Body
// same idents as before, since they are named the same.
AST
if $test
$body
when is-hungry() or is-bored() then eat()
when is-angry()
calm-down()
The syntaxes are defined by a comma-separated list of names (with the type of node attached) or literal strings.
The built-in sequences are as-follows:
Expression
: All normal expressions, including calling other macros. (Does not include assignments)
Logic
: Normal expressions except for immediate calls of other macros (but they can occur inside or within parenthesesAssignment
: An assignment expression, e.g. a := b
ExpressionOrAssignment
: an Expression or AssignmentStatement
: Similar to ExpressionOrAssignment, except parsed as a statement rather than an expression.Body
: An indented body of code.BodyNoEnd
: An indented body of code, without a trailing "end" in 'noindent'-mode. To be used if a single macro has multiple bodies, e.g. if
or try-catch
ExpressionOrAssignmentOrBody
: an Expression or Assignment or BodyGeneratorBody
: An indented body of code, marked as if it were inside a generator function. Handy if a generator function will be produced from the body (as in promise!
)GeneratorBodyNoEnd
: GeneratorBody without the trailing "end" in 'noindent'-mode.DedentedBody
: A body of code that unlike Body
, is not dedented. This allows for a macro to capture all code (within the same block) after it, such as the async
macros.Identifier
: A simple identifier, no complexities allowed.SimpleAssignable
: An assignable expression, such as a.b.c
.Parameter
: A parameter, as would exist within a function's parameter sequence.InvocationArguments
: A sequence of invocation arguments, may or may not include the parentheses. Follows all the same rules as regular invocation.ObjectLiteral
: An object literal, either closed with braces or unclosed.ArrayLiteral
: An array literal.ObjectKey
: An expression that could be used as an object key. May be wrapped in square brackets to represent a normal expression, otherwise it will be treated as a literal string.FunctionDeclaration
: A function, requiring at least an empty parameter list. Does not include the initial #
of an anonymous function, so can be used to create macros such as def key(param) do-something()
where (param) do-something()
is the FunctionDeclaration.Type
: A type such as String
, [Number]
(array of numbers), Generic<T>
, ->
(function).Literal strings (such as "then"
) can be used as well without issue. Spacing around all elements is implicit.
Note: It is important to end a macro with "end"
when using BodyNoEnd
or GeneratorBodyNoEnd
.
IRC is a way to chat with other GorillaScript developers, likely including the author (depending on) and other contributors. Feel free to join us at #gorillascript on irc.freenode.net.