Compilers: Jest Reference Manual (DRAFT)

Jest1 is a compiled, statically typed, procedural programming language based on JavaScript. In fact, just about any Jest program should successfully be executed by a standard JavaScript interpreter. Furthermore, the meaning of a valid Jest program is almost always given by its meaning as a JavaScript program. There are many (way too many!) sources for more information (and misinformation) on the syntax and semantics of JavaScript. Here are two sources that may be helpful (though, beware, neither were written with ease of navigation nor ease of reading in mind):

  1. ECMAScript 2015 Language Specification
  2. Mozilla Developer Network’s JavaScript reference

Programs

A Jest program consists of zero or more global declarations followed by zero or more statements. An example:

//declarations:
const WORD = 'Ha!';

/*< (String, Int): String >*/
function stringMultiply(s, n) {
    let t = '';
    let i = 0;
    while (i < n) {
        t += s;
        ++i;
    }
    return t;
}

let s = stringMultiply(WORD, 3);


// statements:
console.log(s);   // prints "Ha!Ha!Ha!"

Declarations (global and local)

The top-level of a Jest program begins with zero or more global declarations. Global declarations include regular (non-function) variable declarations and function definitions. In Jest, functions are not expressions and can only be defined at the top level. Function definition and global variable declarations can be interleaved.

A function definition consists of a type specification followed by the keyword function, a parenthesized list of zero or more formal parameters (comma separated identifiers) and a body surrounded by curly braces. The body of a function is basically the same as a compound statement: it consists of zero or more variable declarations followed by zero or more statements. (If a function has a non-void return type, then its body must have a return statement.) Example:

/*< (Int, Int): Int >*/
function sumSquares(a, b) {
    let y = a * a;
    let z = b * b;
    return y + z;
} 

(The only difference between a function body and a compound statement is that the scope of function’s body includes the formal parameters of the function.)

There are two forms of variable declarations:


Statements

Jest features most of the statements found in JavaScript:

  1. assignments, as in:

    i = i + 1;
    s = 'hello';
    s = s + ' world';
    a[i] = x + 5;

    and also assignment operators as in:

    product *= m;    // product = product * m
    s += ' world';   // s = s + ' world'
    ++i;             // i = i + 1
    --count;         // count = count - 1
    // ++ and -- are prefix, not postfix
    i++;             // syntax error!

    An important distinction between Jest and its older, more famous, crazier uncle is that assignments and assignment operators are statements but are not expressions so they cannot be embedded in other expressions. So the following is a syntax error:

    x = y = 0;       // error: no chained assignments; they are not expressions!
  2. conditionals, as in:

    if (a !== b) {   // {}s are required!
        console.log('NOT EQUAL!');
    } else {
        console.log('IS TOO!');
    }
    
    // else branches are optional
    if (a === b) {
        ++a;
        --b;
    }
    
    // else binds to nearest if because {}s are required
    if (b) {
        if (c) {
            x = 47;
    } 
    else {
      z = 53; // despite miserable formatting, else goes with the second if
         }}

    Jest does not support switch statements; use chains of if/else if instead.

  3. while loops and do-while loops, as in:

    while (i < n) {
        sum += i;
        ++i;
    }
    
    do {
        sum += i;
        ++i;
    } while (i < n); // note necessity of final semicolon

    As with if statements, curly braces are required.

    There are no for loops in Jest.

  4. break statements, as per usual in C-style languages:

    break;

    can be used to exit a loop immediately.

  5. continue statements, also as per usual in C-style languages:

    continue;

    can be used to jump back to the start of a loop.

  6. return statements, typical of most procedural languages as in:

    return e*37;   // from function that presumably returns numbers
    
    return;        // from procedure (function declared to have Void return type)

    Returns are only legal within function definitions. Procedures (functions declared to have void return type) need not have a return statement, but if they do they must be empty (i.e. return followed by a semicolon with no interceding expression). Returns are required within functions that are declared to have a return type.

  7. procedure calls, for example:

      selectionSort(a);

    Procedure calls are applications of functions that have Void return type (i.e., do not return anything).

  8. compound statements consist of zero or more statements placed within curly braces. Compound statements are the only Jest statements that are not terminated with a semicolon. (if, if-else, while and do-while statements are also compound.) Compound statements always define a new variable scope.


Expressions

Jest features many of the expressions found in JavaScript:

  1. literals for null, Booleans, integers, floats and strings, as in:

    null
    
    true
    false
    
    32767
    -47
    
    3.1415
    2e20
    6.02e23
    
    "this is a string"
    'this is also a string'
  2. identifiers, as in:

    x
    aReallyLongIdentifierWith__AND__34_in_it
  3. unary operations, as in:

    -anIntVar   // negating an integer expression
    
    !aBoolVar   // taking the "not" of a Boolean expression
  4. binary operations, as in:

    // arithmetic:
    a + b
    a - b
    a * b
    a / b
    a % b
    
    // equality and inequality on all built-in non-array types:
    i === j   // triple equal required
    s !== t
    
    // comparison of numeric and string expressions:
    a < b
    a <= b
    a > b
    a >= b
    
    // composition of Boolean expressions:
    b && c
    b || c
    
    // string concatenation (+ is "overloaded" for strings)
    'hello' + 'nwheels'  // results in string "hellonwheels" 
  5. function calls, as in:

    factorial(3)
    sumOfSquares(a, b)
  6. array constructions, as in:

    ['this', 'array', 'has', '5', 'elements']

    Unlike in JavaScript, Jest arrays are homogeneous (every element in a given array must be of the same type).

  7. array subscripts, as in:

    a[34]
    ([2, 1, 9])[2]  // refers to the value 9
    multid[2][3]    // assume multid's type is something like [[String]]

    if a subscript is out of bounds (less than zero, or past the last element), the program should immediately fail with suitable error message.

  8. parenthesized expressions - any Jest expression can be placed within parentheses.

  9. type annotations - any Jest expression can be prefaced with a type. This can aid in readability and debugging. If the annotated type is incorrect, the compiler should report an error. Examples of typed expressions:

    /*<Int>*/ 34
    (/*<[Int]>*/ a)[15] 
    
    (/*<[{id:Int,names:[String]}]>*/ 
        [{id:3, names:[]}, {id:7, names:['Hermoine', 'Minerva', 'Ron']}])
  10. record constructions, such as:

    {a:7, b:9}
    {name: 'Smithers', canHack: true, id: 43}
    {primes: [47, 97, 107], nested: {a:7, b:9}}
    {name: firstName + lastName, age: mystery}
  11. member access, as in:

    {a:7,b:9}.a
    word.length
    car.starter.foo
    person[i].name
    bar.toString()
    console.log('Hello!');
    Math.random()
    myRecord.myFunction(44, 55)
  12. hash objects and hash access, as in:

    {}            // new, empty hash object
    tab['name']   // access entry of hash object tab that has key 'name'
    mp[k]         // access entry of hash object mp that has key which is value of k

Operator precedence

  1. parentheses
  2. member access and array subscripting
  3. function call
  4. unary operations (negation and not)
  5. multiplication, division and mod
  6. addition, subtraction and string concatenation
  7. comparison operations
  8. conjunction (&&)
  9. disjunction (||)
  10. type annotation

For example, the expression:

 /*<Bool>*/ - a + b * c [d] < e || ! f && g && (/*<#str>*/ h) === i + j

is valid and is equivalent to:

 /*<Bool>*/ ((((-a)+(b*(c[d]))) < e) || (((!f) && g) && ((/*<#str>*/ h)===(i+j))))

Lexical specifics

Jest is case sensitive (like JavaScript, and most programming languages).

Identifiers: An identifier is a sequence of one or more letters, digits, and underscores, starting with a letter.

Comments are much like C++, Java, and JavaScript. There are two kinds of comments:

  1. Single-line comments following // to the end of a line.
  2. Multi-line comments, like C, C++, and JavaScript, between /* and */. However, unlike those languages, multi-line comments may be nested.

Types

Probably the biggest difference between Jest and JavaScript (other than that the former is compiled) is that the former is strongly, statically typed. In order to allow most Jest programs to remain valid JavaScript programs, types are formed by combining identifiers and standard punctuation symbols between /*< and >*/.

Predefined type identifiers include:

Bool
Int
Float
String

Array types can be built from other types using the brackets array-type constructor. For example:

  [[Int]]

indicates an array of integer arrays.

Record types can be built from other types using the braces object-type constructor around comma-separated identifier-type pairs. For example:

  {name:String, age:Int}
  

Function types can be built from other types using parentheses around a comma-separated list of types followed by a colon and then the return type. Examples:

  (Int, Int): Int     // takes two ints and returns an int
  (): Void            // procedure that takes no arguments
  ([String]): String  // takes an array of strings and returns a string

The type identifier Void is used to indicate procedures (functions that have no return value).

The namespace for types is entirely separate from normal identifiers. This is possible because names of types only appear between the type begin (/*<) and end (>*/) markers. Predefined types are not reserved words. For example, the following is legal (though not recommended):

let /*<Int>*/ Int;

Block scope

Jest is block scoped. (Recent versions of JavaScript - ECMAScript 2015 and beyond - also off block scoping, but older versions were function scoped.) This means that identifiers (i.e., constants and variables, including functions, are visible within the block that are defined). Non-empty Jest programs always have a top-level block. Within that block, it may have arbitrarily many other (and often nested blocks). Blocks are defined in two ways: In the bodies of functions and in the bodies of compound statements (most notably conditionals and loops), but also explicit {} blocks). To emphasize block scope, in Jest, for any block, all declarations must proceed all statements. (This is more like C than JavaScript). There is no var construct in Jest.

Example:


let n = 3;
let b = false;

/*< (): Void >*/
function f() {
    let m = n + 1;   // m local to this function, accesses outer scope n
    let b = "hello"; // warning that b masks outer b, but different type here is ok
}
let b = true;        // error: b already defined in this block
n = m;               // error: no m defined in outer scope
let x = "bye";       // error: no declarations allowed after statements begin in given block

  1. The name “Jest” is an acronym: “Javascript Experiment: Static Types”. A previous version was called “js_” (“JS Flat”) which itself was an extension of “js–” (“JavaScript minus minus”).↩︎