This file documents what each keyword in the Jou language does.
The result of foo and bar is True if foo and bar are both True, and otherwise False.
Both foo and bar must be bools, and bar is not evaluated at all if foo evaluates to False.
Use array_count(array) to get the number of elements in an array as int.
The number of elements in an array is always known at compile time, and in fact,
the array is not evaluated when the program runs.
For example:
import "stdlib/io.jou"
def main() -> int:
array: int[10]
printf("%d\n", array_count(array)) # Output: 10
return 0Another way to get the size of an array is to do sizeof(array) / sizeof(array[0]).
For example, in the above example, this would calculate 40 / 4,
because the array is just 10 ints next to each other
and each int is 4 bytes.
Unlike the sizeof trick, array_count(array) will fail to compile
if you accidentally call it on a pointer:
def main() -> int:
x = 123
n = array_count(&x) # Error: array_count must be called on an array, not int*Use array_end(array) to get a pointer just beyond the last element of an array.
This is same as &array[array_count(array)].
For example:
import "stdlib/io.jou"
def main() -> int:
array = [1, 2, 3, 4]
printf("%d\n", array_end(array)[-1]) # Output: 4
return 0The most common use for array_end() is looping through an array with pointers:
import "stdlib/io.jou"
def main() -> int:
array = [1, 2, 3, 4]
# Output: 1
# Output: 2
# Output: 3
# Output: 4
for p = &array[0]; p < array_end(array); p++:
printf("%d\n", *p)
return 0The as keyword does an explicit cast. See the documentation on casts.
The assert some_condition statement crashes the program with an error message if some_condition is False.
For details, see the documentation on assertions.
This is the built-in Boolean type.
The break keyword stops the innermost while or for loop it's in.
For example:
import "stdlib/io.jou"
def main() -> int:
# Output: Before 0
# Output: After 0
# Output: Before 1
# Output: After 1
# Output: Before 2
# Output: Stop!!!
for i = 0; i < 10; i++:
printf("Before %d\n", i)
if i == 2:
printf("Stop!!!\n", i)
break
printf("After %d\n", i)
return 0This is another name for uint8.
This keyword can only be used in match statements. See match.md.
Used to define a class. See classes.md.
The const FOO: SomeType = value syntax is used to create a name for a compile-time constant.
This is similar to global variables except that consts cannot be changed.
By convention, constants are usually named with UPPERCASE.
For example:
import "stdlib/io.jou"
const MESSAGE: byte* = "Hello World!"
def main() -> int:
puts(MESSAGE) # Output: Hello World!
return 0The values of const constants can be used in various other places too,
such as array sizes and other const constants.
For example:
import "stdlib/io.jou"
const THE_ACTUAL_SIZE: int = 123
const SIZE: int = THE_ACTUAL_SIZE
def main() -> int:
array: int[SIZE]
# Output: The array has room for 123 ints.
printf("The array has room for %d ints.\n", array_count(array))
return 0A const statement can be decorated with @public so that it can be imported into other files:
@public
const MAX_NUMBER_OF_THINGS: int = 100See also: global
The continue keyword skips the rest of the body of the innermost while or for loop it's in.
Note that the last part of a for loop (typically i++ or similar) is not skipped.
For example:
import "stdlib/io.jou"
def main() -> int:
# Output: Hello 0
# Output: Hello 1
# Output: Hello 3
# Output: Hello 4
for i = 0; i < 5; i++:
if i == 2:
continue
printf("Hello %d\n", i)
return 0Unlike most other keywords in Jou, this keyword has two meanings.
However, they are easy to distinguish:
declare global means a different thing than declare followed by anything else.
The first, most common use for declare is to declare functions.
This means telling the compiler that a function exists without defining it.
For example, in the example below, the declare statement means that
there is a function puts(), and it takes a string.
The puts() function is actually defined in C's standard library,
and that's why the code compiles and runs even though it doesn't define puts().
# No import statements!
declare puts(string: byte*) -> int
def main() -> int:
puts("Hello") # Output: Hello
return 0The last argument of a function can be literally ... when declaring.
This means that the function is variadic; that is,
it accepts zero or more arguments of basically any type where you wrote the ....
This is how printf() is declared in stdlib/io.jou.
A declare statement can be decorated with @public so that it can be imported into other files.
For many more examples of declaring functions, look at stdlib/io.jou or other stdlib files.
The second, rarely needed way to use declare is declare global.
It tells the compiler that a global variable exists without defining it.
For example, on Linux, stdlib/io.jou does this
to access the stdin, stdout and stderr variables defined in C's standard library:
declare global stdin: FILE*
declare global stdout: FILE*
declare global stderr: FILE*A declare global statement can be decorated with @public, but this is rarely needed.
See also: def, global, None, noreturn
The def keyword defines a function, or if placed inside a class, it defines a method.
For example:
import "stdlib/io.jou"
def print_twice(string: byte*) -> None:
puts(string)
puts(string)
def main() -> int:
# Output: Hello
# Output: Hello
print_twice("Hello")
return 0Compared to e.g. Python, Jou's function and method definitions are quite simple. For example, there are no keyword-only arguments, positional-only arguments or default values: just argument names and their types.
It is currently not possible to define a variadic function like printf(),
but it is possible to declare a variadic function.
See also: declare, None, noreturn
This is the built-in double type.
The elif keyword is similar to else if in languages like C. For example, consider the following:
if foo:
...
else:
if bar:
...
else:
if baz:
...
else:
...With elif, this can be written much more cleanly:
if foo:
...
elif bar:
...
elif baz:
...
else:
...This keyword is used with if statements and it does what any programmer would expect.
It is also used in the ternary expression:
foo if condition else bar evaluates to foo or bar depending on the condition.
Use embed_binary_file("filename") to include the contents a file into the executable.
This evaluates to an array of byte that is the file's content.
The file name is relative to the location of the Jou file, but you can use ..,
just like with import statements that start with a dot.
For example:
import "stdlib/io.jou"
global readme = embed_binary_file("../README.md")
def main() -> int:
# Output: Jou programming language
for i = 2; i < 26; i++:
putchar(readme[i])
putchar('\n')
return 0The resulting array is usually not a valid string,
because it does not have a zero byte (\0) at the end.
For example, don't do puts(readme) or printf("%s", readme) in the above example,
because that may print extra junk after the file content.
Instead, use sizeof or array_count to get the file size:
import "stdlib/io.jou"
global meme = embed_binary_file("images/64bit-meme.jpg")
def main() -> int:
printf("%d bytes\n", sizeof(meme)) # Output: 59939 bytes
return 0You should probably make a global variable for files larger than a few kilobytes, because otherwise the file content will consume stack space and you may get a stack overflow.
Files larger than 2147483647 bytes (about 2 gigabytes) are currently not supported. Please create an issue on GitHub if you want to embed a larger file. Also, empty files are not supported, because arrays cannot be empty in Jou.
See also: sizeof, array_count, embed_text_file
This is just like embed_binary_file(), except that:
- a zero byte is always added to the end
- it is a compiler error if the file contains a zero byte
\r\n(also known as CRLF) in the file content is replaced with\n.
For example:
import "stdlib/io.jou"
global file_content = embed_text_file("../tests/data/hellohellohello.txt")
def main() -> int:
# Output: File content is hellohellohello
printf("File content is %s\n", file_content)
return 0This wouldn't work as is with embed_binary_file(),
because %s expects a string with a zero byte to mark the end.
See also: embed_binary_file
Used to define an enum. See enums.md.
Use enum_count(SomeEnum) to get the number of members in an enum as int.
For example:
import "stdlib/io.jou"
import "stdlib/mem.jou"
enum MouseButton:
Left
Right
def main() -> int:
states: bool[enum_count(MouseButton)]
memset(states, 0, sizeof(states)) # set all to False
states[MouseButton.Right as int] = True
if states[MouseButton.Right as int]:
printf("Right-click!\n") # Output: Right-click!
return 0Unlike a simple states = [False, False], or states: bool[2] followed by the memset(),
the above example won't write beyond the end of the states array
if someone adds support for a Middle mouse button in the future.
This is a constant of type bool represented by a zero byte in memory.
This means that if you initialize some memory to zero and interpret it as a bool,
you get False.
This is the built-in float type.
The for keyword is used to do for loops.
A for loop looks like for init; cond; incr: followed by a body, where:
initis a statement that runs before the loop starts (typicallyi = 0)condis an expression that must evaluate to abool, checked when each iteration begins so thatFalsestops the loopincris a statement that runs at the end of each iteration, even if continue was used.
Note that there must be semicolons between the three things.
Basically, the following two loops do the same thing:
init
while cond:
body
incr
for init; cond; incr:
bodyHowever, this is not quite true if continue is used.
Each of init, cond and incr are optional.
If cond is omitted, it defaults to True.
As an extreme example, for ;;: creates an infinite loop,
but while True: is much more readable and hence recommended.
Jou does not have a for thing in collection: loop,
because different kinds of collections (list, array, string, custom data structure, ...)
would need to be handled differently,
and Jou is a simple language without much hidden magic.
See also: while, break, continue
TODO: not documented yet, sorry :(
The global keyword is used to create a global variable. For example:
import "stdlib/io.jou"
global x: int
def print_x() -> None:
printf("%d\n", x)
def main() -> int:
print_x() # Output: 0
x++
print_x() # Output: 1
return 0Note that unlike in Python,
you don't need to use global inside a function to modify the global variable.
By default, global variables are always initialized to zero memory.
For example, numbers are initialized to zero, booleans are initialized to False and pointers are initialized to NULL.
It is possible to specify a different initial value:
import "stdlib/io.jou"
global x: int = 1234
def main() -> int:
printf("%d\n", x) # Output: 1234
return 0If you specify a value, specifying a type is not required:
import "stdlib/io.jou"
global x = 1234
def main() -> int:
printf("%d\n", x) # Output: 1234
return 0By default, global variables are private to a file, just like functions.
You can use @public if you really want to create a public global variable:
@public
global my_thingy: intSee also: const
The if keyword has two uses: it can be used in if statements and ternary expressions.
An if statement looks like if some_condition: followed by indented code.
The condition must be a bool.
After the if statement, there may be zero or more elif parts
and then an optional else part.
These work in the obvious way.
A ternary expression looks like foo if condition else bar.
The condition must be a bool.
If the condition is True, then foo is evaluated and that is the result of the ternary expression.
If the condition is False, then bar is evaluated and that is the result of the ternary expression.
For example:
import "stdlib/io.jou"
def main() -> int:
# Output: 7 is odd
n = 7
printf("%d is %s\n", n, "even" if n % 2 == 0 else "odd")
return 0Note that condition is evaluated first even though it appears in the middle.
The import keyword is used to access things defined with @public in another file.
See imports.md.
These are built-in signed integer types.
TODO: not documented yet, sorry :(
The match statement is basically a way to write long if/elif/else chains nicely.
See match.md.
The None keyword can be used only after -> in a function definition or declaration
to indicate that the function does not return a value.
It has no other uses.
For example:
import "stdlib/io.jou"
def foo() -> None:
printf("Hello\n")
def main() -> int:
foo()
return 0If you want to say that a value may be missing,
you can use e.g. NULL or -1 depending on the data type.
See also: NULL, void, noreturn, declare, def
The noreturn keyword can be used only after -> in a function definition or declaration.
It means that the function never returns at all, not with a value or without a value.
Use None instead if you want to say that a function does not return a value.
Basically, noreturn should only be used for functions that
do an infinite loop or stop the whole program.
One use case for noreturn is error handling functions that stop the program.
For example, consider the following code.
It gives a compiler warning on the if f == NULL line:
import "stdlib/io.jou"
import "stdlib/process.jou"
def fail(message: byte*) -> None:
fprintf(get_stderr(), "Well, it seems like we %s :(\n", message)
exit(1)
def main() -> int:
f = fopen("thingy.txt", "r")
if f == NULL: # Warning: function 'main' doesn't seem to return a value in all cases
fail("cannot open file") # Output: Well, it seems like we cannot open file :(
else:
fclose(f)
return 0If you change the return type of fail() from -> None to -> noreturn,
the warning goes away,
because then the compiler knows that it doesn't need to worry about what happens after calling fail().
See also: None, return, declare, def
The result of not foo is True if foo is False, and False if foo is True.
Type type of foo must be bool.
NULL is a special pointer that is used as a special "missing" value.
Jou's NULL constant has type void*,
so it converts implicitly to any other pointer type.
For example, the strstr() function declared in stdlib/str.jou
finds a substring from a string, and returns a pointer to the substring it finds,
or NULL for not found:
import "stdlib/io.jou"
import "stdlib/str.jou"
def main() -> int:
string = "Hello World"
if strstr(string, "Test") == NULL:
printf("does not contain Test\n") # Output: does not contain Test
return 0The NULL pointer is represented in memory as zero bytes.
This means that if you initialize some memory to zero and interpret it as a pointer,
you get NULL.
Accessing a NULL pointer produces UB, and typically crashes the program.
The result of foo or bar is True if either foo or bar is True, or both are True,
and False if neither is True.
Both foo and bar must be bools, and bar is not evaluated at all if foo evaluates to True.
The pass statement does nothing, just like in Python.
For example:
import "stdlib/io.jou"
def main() -> int:
x = 7
if x > 10:
printf("Way too big\n")
elif x < 0:
printf("Way too small\n")
elif x == 5:
pass # Just right, no need to print a message
else:
printf("Close, but not quite...\n") # Output: Close, but not quite...
return 0Without the pass, you would get a compiler error,
because the next line after elif whatever: must be indented, but the else line is not indented.
A comment or blank line is not enough, because the compiler ignores comments and blank lines.
In the above example, you could instead write elif x != 5,
but that's not always possible.
For example, pass is often useful with match statements.
The return keyword works just like you would expect:
it stops the function it's in,
and in functions that are not defined with -> None, it must be followed by a return value.
For example:
import "stdlib/io.jou"
def do_thing_maybe(thing: byte*) -> None:
if thing[0] == 'f':
return # Do nothing if it starts with 'f'
printf("Hello! %s\n", thing)
def main() -> int:
do_thing_maybe("foo") # no output from here
do_thing_maybe("bar") # Output: Hello! bar
do_thing_maybe("baz") # Output: Hello! baz
return 0The self keyword can be used only inside a method,
and it is a pointer to the instance (or the instance itself) whose method is being called.
See the documentation about methods for details.
Computes size of object as int
TODO: not documented properly yet, sorry :(
This is a constant of type bool represented by a 1 byte in memory.
TODO: not documented yet, sorry :(
These are built-in unsigned integer types.
TODO: not documented yet, sorry :(
This keyword can be used only to specify the void pointer type by writing void*.
Use None when a function does not return a value.
This works just like in other programming languages. See the documentation on loops.
See also: for, break, continue
This keyword can be used only to specify a function in a match statement.