Documentation for João v2.2.0


1 - Introduction

João is a programming language. You can use it to tell your computer to do stuff.

There is a lot of software on the internet can declare that, but João has a few gimmicks: It is lightweight and made to be easy to learn. It uses a novel directory-oriented manner of representing object-orientation that's tolerable to experts and palatable to new programmers who are tired of the concept being confused by the syntax and strange mechanics OOP often has in other languages.

Initially developed to suit my personal taste in programming languages, I have come to realize that other people may have some interest in learning it themselves, so I have written for you, kind reader, this documentation. If you are new to programming, I strongly recommend reading everything mostly in order and taking some time to play with new concepts before trying to learn new ones. An associated guide, more suited for first-time programmers, will be published at a later time.

There are bugs in this language; it is not complete and it never will be. Please report any bug or undesirable behaviour on the associated Github repository of this project, especially if it clashes with the information presented in this documentation.

1.1 - Installation & Use

The current only official software that can run João programs is an interpreter located on Github. Its source code may be viewed there, and it may be downloaded there as well. The official releases ship out with a 64-bit pre-compiled binary for Windows and a Linux binary created on a Linux Mint system.

Using the binary is simple, but may be confusing to those new to command-line-only software. To use, create a text file containing your script, such as a simple hello world program:

/main()
{
	print("Hello, world!");
}

Then, open the command line and call the executable with the location of the file as an operand:

C:\joao_folder>Joao.exe your_file.txt
João will then attempt to run the program, hopefully printing a friendly "Hello, world!" into the command line before exiting.

1.2 - Building João

João uses Meson, a build system. I'm not willing to put the entire documentation for that here, but you will likely need Meson if you wish to compile João yourself.

To compile, clone this repository with Git:

git clone https://github.com/Altoids1/Joao.git
			

After that, go into the directory and, assuming you have Meson installed, execute this command in your terminal:

meson setup --buildtype release release
			

This will create a folder named "release" within the directory, with everything Meson needs in order to compile an optimized build of João.

Enter the "release" directory and execute this command to compile & test João:

meson test
			

If everything went well, all tests should be either passing or an expected failure. Within the "release" directory is now a self-contained binary (called "joao" on Linux and "joao.exe" on Windows) which you can now interact with.

1.2.1 - Emscripten

João has tenuous support for running as a WASM executable under Emscripten. To create a Meson folder capable of compiling to the target, place this text file into the João directory (with the proper paths to your local copy of the emscripten repo), and run the following command in the João directory:

meson setup --cross-file cross_wasm.txt wasm
			

When compiled as normal, this should generate some JS and WASM, plus an example HTML page.

2 - The Language

Several concepts are necessary to understand in order to operate any programming language, including this one. Here they are, starting with elements likely familiar to anyone that has used a good calculator, and working upwards in complexity and downwards in usefulness.

2.1 - Variables and Types

Computers need to remember things. As with most languages, this is resolved by the use of Variables.

In João, variables are things that store Values. A Value is the dynamic type in João which is capable of storing data of a particular Type. There are several types in João:

2.1.1 - Strings

Strings have the usual meaning: a sequence of characters, like the word “apple.” Each character is any eight-bit number, and so strings may contain characters with any numeric value, but are typically assumed to contain ASCII text.

Escape Sequences allow a string to contain special characters. For instance, if you wanted to actually have a " character within a string, it would need to be escaped so that it isn't perceived as being the end of the string. Strings in João can contain the following C-like escape sequences:

Escape Character Meaning
\nNewline
\tTab character
\\The backslash character itself
\"Double quote
\'Single quote

Strings and numbers are different things. A comparison like 10 == “10” is always false, because 10 is an integer and “10” is a string. When in doubt, typecast it out for clarity and confidence.

2.1.2 - Objects

Objects are the bread and butter of João. They use the directory system to organize groups of code and variables which can be used together to form some greater, more complex thing.

Since their concept is a bit abstract, lets start with a concrete example. Lets say you want to make a video game, and ergo might want something that can store the state of a particular player. You may want to, on a player-by-player basis, store things like their health and position on the map. Here's an object-oriented way of storing such data:

/player
{
	Number health = 100; ## The player's health, out of 100 points

	Number x = 0; ## The x-coordinate of this player
	Number y = 0; ## y-coordinate
	Number z = 0; ## z-coordinate
}

/main()
{
	Object my_player = /player/New();
	print("My player has " .. my_player.health .. " HP!"); ## Prints 'My player has 100 HP!'
}

There's several steps in the creation of an object:

  1. The object must have a class that is defined in a class definition, a braced block containing the class' name and a list of its properties: variables that belong to any object created from that class.
  2. Once the class is defined, an object of that class can be created anywhere by doing class_name/New(), with class_name being whatever the directory name of the class is. New() is a function inherent to all classes, which creates an Object with that class as its class.

Further, classes can "inherit" from other classes via the directory tree, like so:

/player
{
	Number health = 100;

	Number x = 0;
	Number y = 0;
	Number z = 0;
}

/player/red
{
	String team = "Red";
}

/player/blue
{
	String team = "Blue";
}

/main()
{
	Object redmann = /player/red/New();
	Object blumann = /player/blue/New();
	
	print(blumann.team .. " team hates " .. redmann.team .. " team!"); ## Prints 'Blue team hates Red team!'
}

Unlike some other languages, the classes (and functions) of João can be declared in any particular order, regardless of how they inherit from one another. The inheritence scheme is only calculated after all code has been read by the interpreter.

2.2 - Arithmetic and other Operations

João can be used to carry out typical math operations. The syntax for such mimics what one may see on a fancier calculator, phrases like 1 + 5 for addition or 2 * 3.5 for multiplication. Exponentiation is expressed using the caret symbol, as in 2 ^ 10.

Assignment is also an operation, and one necessary in order to store data in a variable. It is typically written in the form x = value, where x is the name of the variable and value is the value.

Operations that go beyond typical mathematics also exist. For instance, there is an operator for concatenating two strings together:

/main()
{
	print("Pineapple " .. "pen"); ## Prints 'Pineapple pen'. Note the explicitly-stated space character.
}
When the concatenation operator is used with things that are not strings, those things are coerced into being strings; 10 .. 1 becomes the String "101".

Sometimes, you may want to manipulate the bits of an Integer or Double manually; flipping them on and off like a set of switches. The typical bitwise operations are all implemented and available for this purpose. Using bitwise on doubles works, but the results may be slightly machine-dependent.

There are also operators for determining that a system of truths are correct, like asserting that a variable is less than seven, or that two statements of fact are true at the same time. They behave as you'd expect:

/main()
{
	Value x = 5;
	print(x < 7 && 2 >= 1); ## Prints 'true', since both x is less than seven and 2 is greater than or equal to one.
}

The order of operations behaves somewhat as you'd expect, while being somewhat expanded to accommodate the expanded set of operations. When multiple operations are used in the same expression, they are carried out in the following order, top to bottom:

Name Operators Associativity
Grouping & Function Calls()None, left
Exponentiation^Right
Unary Operators- ! ~ #Right
Multiplication & Division* / // %Left
Addition & Subtraction+ -Left
Concatenation..Left
Bitwise Operators& ~ | >> <<Left
Comparison Operators< <= > >= !=Left
Logical Operators&& || ~~Left
Assignment Operators= += -= *= /= %=None
Assignment is treated uniquely in João. Only one assignment may occur in a given statement and, unlike other operations, it cannot occur as any expression. The phrase (1 + (x = 2)) is not valid João, but x = 2 + 1; as a statement is valid.

2.3 - Iteration and Flow Control

Most programs need to be able to execute certain code only if a particular condition is met, i.e. a video game character should only be able to shoot their gun when they are not dead or out of ammo. This behaviour is implemented through the if, elseif, and else keywords.

/gun
{
	Number ammo = 6;
}
/player
{
	Number health = 100;
	Object gun = /gun/New();
}
/main()
{
	Object gamer = /player/New();
	if(gamer.health <= 0)
	{
		print("You are dead! You can't shoot!");
	}
	elseif(gamer.gun.ammo == 0)
	{
		print("*click*");
	}
	else
	{
		print("The gamer fires their gun!!");
	}
}

There are also keywords for handling doing a repetitive process many times, avoiding having to write the same lines of code perhaps one million times. Such behaviour is achieved through the for and while keywords.

There are two varieties of for-loop in João. The first is the explicit, C-like variety. It has three statements that describe its behaviour:

  1. an initializer,
  2. a condition to test against (making the program exit the loop if the condition resolves to false), and
  3. an increment line to run at the end of every loop around.

Its syntax looks as follows:

/main()
{
	for(Number i = 0; i < 100; i += 1)
	{
		if(i % 2 == 0) ## If i is even
		{
			print(i); ## Print i. Prints all of the even numbers less than 100.
		}
	}
}

The second variety of for-loop acts on table Objects, and anything that derives from the /table class. It is especially useful when you need to iterate over a table which has Strings as keys, instead of just being a contiguous array.

/main()
{
	Object tbl = {a = 1, b = 2, c = 3}; ## Constructs a Table
	for(key,value in tbl)
	{
		print(key,value); ## Prints, in some unknown order, the strings 'a   1', 'b   2', and 'c   3'.
	}
}

There is a particular order that this variety of for-loop goes in. First, it iterates in consecutive order all non-negative integer indices, 0 and up. When it runs out or hits a non-consecutive integer, it begins to iterate over all other elements in the table in an undefined order, depending on the internal hashtable in which those elements are stored. There is no currently-implemented way of iterating over the keys in any other ordering.

While-loops have the same behaviour as C while loops:

/main()
{
	Number x = 128;
	while(x != 1)
	{
		x /= 2;
		print(x); ## Prints all the powers of 2 between 1 and 128.
	}
}

2.4 - Tables

The table class implements associative arrays. An associative array is an array that can be indexed not only with integers, but also with strings. Moreover, unlike the properties of objects, tables have no fixed size; you can add as many elements as you want to a table dynamically. Tables are the main ad-hoc data structuring mechanism in João, and a powerful one, sidestepping the rigidity of the typical class system. We can use tables to represent ordinary arrays, databases, and even other tables, in a simple, uniform, and efficient way.

You can construct tables simply through a typical /New() call. As well, you can use brackets to index into it, to set values and acquire them later:

/main()
{
	Object tbl = /table/New();
	tbl[0] = "apple"; ## Tables are zero-indexed in João.
	print("I love " .. tbl[0] .. "s!"); ## Prints 'I love apples!'
	print("There are " .. #tbl .. " elements in tbl!"); ## Prints 'There are 1 elements in tbl!'
}
			

Like all objects, when a program has no references to a table left, João memory management deletes the table and frees the memory it was using for other purposes.

Beyond this functionality, the /table class has several methods, and there are several global functions which take tables as arguments, as described in #3.2.

2.5 - Exception Handling

It is very possible for a João program you write to come to an error condition, either as a result of imperfectly-written code on your part, or a failure of the computer the program is running on.

When this happens, João emits an Object of type /error, which is stored internally and transmitted up the stack until it is either caught in a try-catch block, or ascends beyond /main and causes the João executable itself to error out.

This /error Object holds two properties: /error.code, which stores an Integer that describes the category of error that has taken place, and /error.what, which is a human-readable String which attempts to explain the error.

The error code is just an integer. The following is a table the lists each error code that can be emitted from João and what it signifies:

Name Error Code Meaning
NoError0No error. This will eventually have special significance, but for now is a reserved error code value.
Unknown1An unknown error has taken place.
FailedTypecheck2A value failed to be of a certain type during a type-check.
FailedOperation3An operation failed because its operand(s) were of invalid type.
BadBreak4Signifies that a break statement held an integer value too large to make sense.
BadCall5There was an attempt to call something that isn't a function.
BadArgType6The arguments given to a native function were of the wrong type.
NotEnoughArgs7An insufficient number of arguments were given to a native function.
BadMemberAccess8There was an attempt to access a member which doesn't exist, or to do a member access on something that isn't an Object.
BadAccess9There was an attempt to access a variable which doesn't exist.

Error objects can also be constructed and thrown by the user, via the throw keyword:

/main()
{
	Object err = /error/New(420,"Illegal substance detected!");
	throw err; ## Presumably prints the above error into stdout/stderr and quits the program.
}

Sometimes it is not necessary for João to crash when an error takes place. In such a case, the programmer may use a try-catch block to "catch" the error as its propagating from any code executed within (or as a result of the code within) the try block:

/main()
{
	try
	{
		x = 5; ## Uh-oh! x has yet to be initialized anywhere!
	}
	catch(err)
	{
		print("Caught this error:",err.what);
	}
	print("I can resume without a problem! :-)");
}

The user, as with any other native class, can provide derived classes of /error to add additional functionality if necessary. Regardless, throw will only accept its input if it argument is an Object whose class derives from or is /error.

3 - The Standard Library

Several native functions come already in the scope of any João program, to allow for the use of common mathematical functions, I/O with the computer, and other functionality.

These functions are usually preferrable to any implementation made directly in João, as their body is typically written in compiled C++, making them faster, and are allowed access to things otherwise inaccessible by the João user.

3.1 - The Standard Standard Library

/print(...)

Receives any number of arguments and prints their values to stdout, converting each argument to a string following the same rules of tostring. The values are tab-separated in the input, and every call to print() ends its output with a newline character.

/input([len])

Receives character input from stdin, up to a limit of len characters if len is provided. Result is always of String type, unless no input is provided, in which case null is returned.

/tointeger(val)

Attempts to convert the value given into an integer. Throws an /error if the conversion is not sensibly possible or unimplemented.

/typeof(val)

Returns a string denoting the type of the given value, always and without error.

/isnull(val)

Returns a Boolean denoting whether the given value is null or not.

/classof(val)

If val is an Object, returns a string denoting the directory tree that describes that Object's class.

/pick(...)

Randomly chooses one of its arguments through an internal call to rand() and returns it.

3.2 - The String Library

/tostring(val)

Receives a value of any type and coerces it into a human-readable string. Any type in João can be coerced into being a string through this method, without error.

/replace(hay,needle,better_needle)

Replaces all instances of the substring needle within hay with the alternative String better_needle. Returns the resulting String.

3.3 - The Math Library

/abs(val)

Returns the absolute value. If a Boolean is passed, it returns the argument given.

/acos, /asin, /atan, /cos, /sin, /tan

These functions are the typical trigometric functions. They do not error on out-of-domain arguments, but instead return NaN or other signals, identically to the functions found in the C library.

/ceil(x)

Returns the ceiling of x. Simply returns the argument if it's a Boolean or Integer.

/floor(x)

Returns the floor of x. Simply returns the argument if it's a Boolean or Integer.

/log(x)

Returns the natural log of x, a Double value.

/log10(x)

Returns the log-base-10 of x.

/max(...)

Returns the largest value of all arguments given, according to João's > operator.

/min(...)

Returns the largest value of all arguments given, according to João's < operator.

/random([lower = 1, upper])

If no arguments are given, returns a random Double between 0 and 1. If one argument is given, returns an Integer between that argument and 1, inclusive. If two arguments are given, returns an integer between those two, inclusive.

/randomseed(seed)

Uses the given value to seed the random number generator. Seed can be of any type.

/sqrt(x)

Returns the square root of x.

/ult(a,b)

Returns the result of a < b, if a and b are perceived as unsigned integers. Both arguments must be Integers.

3.3 - The Table Library

/istable(val)

Returns a Boolean denoting whether the given value is an Object whose class inherits from (or is) /table.

/table/New([...])

Constructs a new /table Object, with the (optional) arguments being the initial elements of that table.

/table/implode([sep = ", ",start = 0, stop])

In the default case, returns a String which is the concatenation of all the array elements of the table, separated by commas and spaces. Passing a sep argument determines the separator used. Passing a start and stop determines what portion of the table to implode.

/table/pick()

Randomly picks an element, through an internal call to rand(), and returns that element.

/table/insert(index,value)

Inserts the value given at the index given into the table. If the index is numeric, integer indices above that value are incremented to avoid overlap.

/table/remove(index)

Removes the element at the index given, if it exists.

3.4 - The File Library

The /file type is used to read from and write to files on disk. It is not available in Safe Mode.

/file/open(filename)

Opens a handle to a file under the given filename, creating the file if it doesn't yet exist. If the /file Object already points to another file, /file/close() is internally called and the Object quietly points to this new file instead.

/file/lines()

Returns a table with its array part filled with the lines of this file. Returns null if this /file Object has no file open.

/file/write(val)

Writes the value given, after coercing it into a String, into the file. Returns a Boolean denoting if the file is still "good" and devoid of errors, a value of true indicating no error.

/file/close()

Closes the file of this /file Object, if it is open. Guarantees all written data will be flushed onto disk.