The jack-games/ files are copyrighted. The other files are public domain.
The Jack programming language is described in the book The Elements of Computing Systems and on the website www.nand2tetris.org. There is a set of video on Youtube talking about it.
This document is describing a Jack compiler written in Jack which outputs C code.
At the beginning it was itself bootstrapped with a simple Python3 Jack compiler.
The author or authors of this Document dedicate any and all copyright interest
in this Document to the public domain.
Elon Musk asks "is life a video game" ?
In a blues song (the roots of Hard Rock), AC/DC says "she's got the Jack" and "she's a dirty woman".
Could the roots of our Universe be coded in Jack programming language by our Creator ?
Jack is easy to learn but it is designed to be yet useful.
The Jack language was design for teaching the basis of computer construction.
Differences from the original Jack specification are :
The range of decimal constant number and integer variable is unspecified. The size of the integer is at least the same size as the size of a memory address. (It could be from 8 to 512 bits).
The callback extension allows to call a callback
method
from a Callback object.
The inline assembly comment extension allows to insert target language code
between //#
and /*# #*/
comments.
There is additional classes to allow interaction with the operating system (files and directories...).
With the -hack
option the public domain jack compiler is
disabling these differences.
Differences from the official first edition of Hack Java VMEmulator are :
The \
in strings literal does not escape \
and "
.
The official JackCompiler does.
The while
and if
conditions are coherent. In the official JackCompiler the
while
condition is true if it is equal to -1, but the
if
condition is true if it is not equal to 0.
The public domain jack compiler treats both conditions like the if
.
The static and local variables are not initialized to 0 at start up. The VMEmulator seems to do it.
Source code :
class Main {
function void main()
{
do Output.printString("Hello World!");
do Output.println();
return;
}
}
With Debian or Ubuntu, you need to install a valid OpenGL development environment, using:
sudo apt install build-essential git libx11-dev libgl1-mesa-dev libglu1-mesa-dev
Then clone this repository :
git clone https://github.com/public-domain/jack.git
Build the compiler :
cd jack; make
Create an empty directory structure using
mkdir -p hello/lib
Save the text source code in a file named hello/Main.jack
using your
favorite plain text editor.
Copy the JackOS and the C run-time from the public domain jack compiler source code :
cp -r lib/std/ hello/lib/std; cp -r lib/c/ hello/lib/c/
Translate it to C using
./jack.run -hack hello
Make an executable using
cc -o hello.run hello.c -lX11 -lGL -lGLU
Run it
./hello.run
Done!
A Jack program is composed of one or more files that each describe a single class. Each class is compiled separately then they are all linked to create a single executable file.
A class name begins with a capital letter and is saved in a text file with
the same name with the .jack
extension appended.
All the .jack
files for a project are located in a single source
directory, possibly with sub-directories.
A class name, variable name or subroutine name (identifiers) always begin with
a letter or a _
and next is composed of letters, _
or digits.
The identifiers are case sensitive.
Name.jack
:
/** class definition */
class Name { // Name is the name of the class
static int static_variable_name; // static or field declarations
field int field_variable_name; // in any order, type or quantity
static char static_variable_name2;
field char field_variable_name2;
static boolean static_variable_name3;
field boolean field_variable_name3;
static Name static_variable_name4;
field Name field_variable_name4;
// ...
/* subroutines declarations */
// method, constructor or function in any order and quantity
// a constructor always returns an object of type of its class.
// subroutines can have any quantity of parametres.
constuctor Name constructorName(int parameter1, String parameter2) {
var int local_variable_name; // local variable in any type
var Array local_variable_name2; // or quantity
// ...
// statements
let local_variable_name2 = null;
return this;
}
// a function doesn't have the `this` object
function int functionName(char parameter1) {
var int local_variable_name;
var Name object;
var boolean condition;
// ...
// statements
let condition = false;
if (~condition) {
let local_variable_name = 1 + 2;
} else {
let local_variable_name = 1 * 2;
}
let object = Name.constructorName(1, "Hello");
do object.methodName(true, Array.new(3));
do object.dispose();
return 0;
}
// a method has an object `this` of its class
method void methodName(boolean parameter1, Array parameter2) {
var int local_variable_name;
var boolean condition, local_var_2, local_var_3;
var Name an_object;
// ...
// statements
let an_object = this;
let condition = true;
let local_variable_name = 3 & 1;
while (condition) {
do Name.functionName(65);
do methodName(false, null);
let parameter2[0] = 7 - 1;
if (local_variable_name > 0) {
let parameter2[1] = 7 - (3 / 2);
}
if (local_variable_name > 0) {
let parameter2[1] = 1 | 2;
}
if (local_variable_name = 0) {
return;
}
let condition = ~condition;
}
return;
}
}
static
kind variables are living during all the program execution and
have a class scope.
field
kind variables have the life of an object have an object scope.
var
kind variables and parameter variables have the life of
subroutine execution and a scope of the local subroutine.
Every kind of variable is accessed only by its identifier. You don not need to add class identifier for static variable. And you cannot access fields of object. If you need to access field, you need to write getter and setter.
Each variable has a type that can be a primitive or a object type of a class.
The primitives types are int
2's complement integer, boolean
that can be
true
or false
, and char
an UTF-16 Unicode character.
A constant can be assigned to a variable.
Integer constant are always positive.
The unary minus -
is used to get negative numbers.
There is only decimal constant. No hex, octal or character constants.
On 16bit platforms the biggest integer constant is 32767, on 32bit
2147483647, on 64bit 9223372036854775807.
To get the most negative number you must use these biggest constants
and add 1
to them.
String constant are beginning and ending with "
and can't contain "
or
newline (ascii character 10 in decimal). There is no escape
sequences (\n \\ \t \"
are not producing the same result as in other
programming languages).
The null
constant is a null reference to an object
(equivalent to 0
or false
).
Boolean true
is -1
and false
is 0
.
Variables are weakly typed you can assign any type of variable to any type of other.
That code is perfectly valid:
var int variable;
var Array arr;
let variable = 2000;
let arr = variable;
let arr[1] = 3; // now memory located at address 2001 contains the value 3
constructor
are automatically allocating the memory for an object of type
of their class. Inside the constructor this
is a reference to the
current newly created object. A constructor must end with return this;
statement and its return type must be the one of its class.
Constructors are called using the
let obj = ClassName.constructorName(arguments);
syntax.
The dispose()
method is generally used in counterpart of constructors to
free memory used by the object.
function
subroutines are called using the
let data = ClassName.functionName(arguments);
syntax.
method
subroutines are called using the
let data = obj.methodName(arguments);
syntax outside their class and
let data = methodName(arguments);
syntax inside their class.
Methods have the this
reference to the current object of type of their class.
The last statement of subroutines is always a return
statement.
Note that the compiler has no way to find the return type of a subroutine
which is not inside its class scope. So in fact void
subroutines return
0
.
let
syntax is
let variable = expression;
or
let variable[expression] = expression;
.
do
syntax is
do ClassName.functionName(arguments);
or
do obj.methodName(arguments);
.
if
syntax is
if (expression) {
statements; // executed when expression evaluates to true
} else {
statements; // executed when expression evaluates to false
}
The else { statements; }
is an optional clause.
while
syntax is
while (expression) {
statements; // executed in loop until expression evaluates to false
}
return
syntax is
return expression;
for non void subroutines
or return;
for void
subroutines.
An expression can be:
- A constant (
1234
or"Hello World"
ortrue
/false
ornull
) - A variable name
- The
this
keyword - A
variable_name[expression]
- A subroutine call
- A
~expression
// bit-wise Boolean INVERT on int and logical NOT on others - A
-expression
// unary minus - A
(expression)
// expression enclosed in parenthesis - Or :
expression + expression // integer addition
expression - expression // integer 2's complement subtraction
expression * expression // integer multiplication
expression / expression // integer division
expression & expression // bit-wise AND on int and logical AND on others
expression | expression // bit-wise OR on int and logical OR on others
expression > expression // is greater than
expression = expression // is equal
expression < expression // is less than
There is no priority of operators. You must always enclose sub-expressions
in parenthesis. Result of let variable = 2 * 4 + 5;
is undetermined,
you must use let variable = (2 * 4) + 5;
.
//
is introducing a single line comment, the text after //
is
ignored till the end of line.
/*
is introducing a multi line is comment, the text after /*
is
ignored till */
is encountered.
Special /**
is introducing an API documentation comment.
Extension /*#
is introducing an inline assembly source code and runs till
#*/
is encountered. /*#
is followed by one to four characters that
indicate the type of target assembly language.
Extension //#
acts the same as /*#
except it is single line.
WARNING: These extensions are only supported by the public domain jack compiler described in this document.
The callback
keyword extension allows to call a method referenced
by an object. It is useful for creating generic algorithms independent
of types.
WARNING: This extension is only supported by the public domain jack compiler described in this document. Do not use it for the original Hack platform.
This extension is a cheap replacement for virtual
C++ functions and
Java non-static, non-final, non-private methods.
The syntax is:
class MyCallback {
// the class must begin with a `int` field named "callback"
field int callback;
// There can be only one callback per class.
// the callback is called by invoke()
// it must return int and have two int parameters
method int callback(int a, int b) {
// do something with a and b.
// a and b can be assigned to variable of a class type
// to do something useful by calling then a method.
var Array array;
let array = a;
if (array) {
do array.dispose();
}
return 0;
}
constructor MyCallback new() {
// the compiler automatically assigns the callback method to the
// callback field of the object
return this;
}
method void dispose() {
do Memory.deAlloc(this);
return;
}
method int invoke(int a, int b) {
return callback(a, b); // the magic is here.
// it calls the callback referred
// by the callback field in
// the `this` reference
}
}
class MyOtherCallback {
field int callback;
constructor MyOtherCallback new() {
return this;
}
method int callback(int a, int b) {
return 0; // do nothing
}
// we dont need the invoke and dispose methods since we
// never use them in this class
}
class Main {
function void Main() {
var MyCallback cb;
let cb = MyCallback.new();
do cb.invoke(0, 0); // calls MyCallback.callback()
do cb.dispose();
let cb = MyOtherCallback.new();
do cb.invoke(0, 0); // calls MyOtherCallback.callback()
// without the extension it would have
// called MyCallback.callback() again
do cb.dispose();
return;
}
}
See the book.
See the book.
See the book.
See the book.
Screen resolution is 512 x 256 pixels.
The screen memory starts at memory location 16384 and ends at 24575.
Pixels are single bit black and white packed in 16bit words. There is 32 16bit words by line of screen.
See the book for more.
The current pressed key Unicode value is located at memory address 24576.
There is special key values:
- Newline is 128.
- Backspace is 129
- left arrow is 130
- up arrow is 131
- right arrow is 132
- down arrow is 133
...
See the book for more.
See the book.
See the book.
Manage files and directories.
constructor File new(String path, boolean writing)
path: path of the file or directory.
writing: true if you want write to the file.
return: an object of type File.
method String getName()
return: the path.
method boolean isdir()
return: true if path is a directory.
method boolean open()
return: true if the file has been successfuly opened.
method int readByte()
return: a single byte read from the file or -1 if the
end of file has been reached.
method String readLine(String buffer)
return: a line from the file or `null` if end of file has
been reached.
method int seek(int position)
Move the current byte position in the file
for writing or reading.
return: position if successful.
method int writeByte(int data)
return: 1 if a single byte "data" has been writen to the file.
method int writeString(String s)
return: the length of the "s" String successfuly writen to
the file.
method boolean remove()
return: true if the file or directory has been deleted.
method boolean mkdir()
return: true if the directory has been created successfuly.
method Buffer list()
return: a Buffer containing the list of files or directories
in the "path" directory.
Usage :
var Buffer dir;
var File file;
var int i, c;
var String d;
let file = File.new("./", false);
let dir = File.list();
if (~dir) { return; }
let i = 0;
while (i < dir.getSize()) {
let d = dir.getAt(i);
do Output.printString(d);
do Output.println();
do d.dispose();
let i = i + 1;
}
do dir.dispose();
do file.dispose();
let file = File.new("hello.txt", true);
do file.writeString("Hello World!");
do file.writeByte(10); // newline
do file.dispose();
let file = File.new("hello.txt", false);
let c = file.readByte();
while (c > 0) {
do Output.printChar(c);
let c = file.readByte();
}
do file.dispose();
let file = File.new("hello.txt", false);
do file.remove();
do file.dispose();
An Array that grows automatically.
constructor Buffer new(int initial, Callback dispose_cb)
initial: preallocated size of the internal Array.
dispose_cb: a Callback to free elements when the Buffer is
disposed or "null" if the elements don't need
to be unallocated.
return: a Buffer object with preallocated "initial"
elements space.
method Array getAt(int index)
return : the element at postion "index".
method int append(Array data)
return: the index of the element "data" appended to the
internal Array.
method int getSize()
return : the number of elements in the buffer.
keyword: one of
class constructor function method callback field static var int char boolean void true false null this let do if else while return
symbol: one of
{ } ( ) [ ] . , ; + - * / & | < > = ~
digit: one of
0 1 2 3 4 5 6 7 8 9
letter: one of
a b c d e f g h i j k l m n o p q r s t u v w x y z A B C D E F G H I J K L M N O P Q R S T U V W X Y Z
integerConstant:
digit digit_opt
stringConstant:
" unicodeExceptNewlineAndDoublequote_opt "
letterOrUnderscore:
_
letter
digitOrLetterOrUnderscore:
letterOrUnderscore
digit
identifier:
letterOrUnderscore digitOrLetterOrUnderscore_opt
classDec:
class className { classVarDecList_opt subroutineDecList_opt }
varNameList:
varName
varNameList, varName
classVarKind:
static
field
classVarDecList:
classVarDec
classVarDecList classVarDec
classVarDec:
classVarKind type varNameList ;
field int callback ;
type:
int
char
boolean
className
subroutineKind:
constructor
function
method
subroutineType:
type
void
subroutineDecList:
subroutineDec
subroutineDecList subroutineDec
subroutineDec:
subroutineKind subroutineType subroutineName ( parameterList_opt ) subroutineBody
parameterList:
type varName
parameterList, type varName
subroutineBody:
{ varDecList_opt statements }
varDecList:
varDec
varDecList varDec
varDec:
var type varNameList ;
varNameList:
varName
varNameList , varName
className:
identifier
subroutineName:
identifier
callback
varName:
identifier
statements:
statement
statements statement
statement:
letStatement
ifStatement
whileStatement
doSatement
returnStatement
letStatement:
let varName indexExpression_opt = expression ;
indexExpression:
[ expression ]
ifStatement:
if ( expression ) { statements } elseStatement_opt
elseStatement:
else { statements }
whileStatement:
while ( expression ) { statements }
doStatement:
do subroutineCall ;
returnStatement:
return expression_opt;
return callbackCall;
expression:
term
expression op term
term:
integerConstant
stringConstant
keywordConstant
varName indexExpression_opt
subroutineCall
( expression )
unaryOp term
varOrClassName:
className
varName
subroutineCall:
subroutineName ( expressionList_opt )
varOrClassName . subroutineName ( expressionList_opt )
callbackCall:
callback ( expression , expression )
expressionList:
expression
expressionList , expression
op: one of
+ - * / & | < > =
unaryOp: one of
- ~
keywordConstant:
true
false
null
this