Jou has a match
statement that is meant to be used instead of long if
/elif
chains.
For example, consider the following code:
import "stdlib/io.jou"
def main() -> int:
number = 4
if number == 0:
printf("Zero\n")
elif number == 1:
printf("One\n")
elif number == 2:
printf("Two\n")
elif number == 3:
printf("Three\n")
elif number == 4:
printf("Four\n") # Output: Four
elif number == 5:
printf("Five\n")
else:
printf("Many\n")
return 0
This can be written with a match
statement:
import "stdlib/io.jou"
def main() -> int:
number = 4
match number:
case 0:
printf("Zero\n")
case 1:
printf("One\n")
case 2:
printf("Two\n")
case 3:
printf("Three\n")
case 4:
printf("Four\n") # Output: Four
case 5:
printf("Five\n")
case _:
printf("Many\n")
return 0
The case _
is special syntax that means "anything else".
It may be omitted if you don't want to do anything for other values.
Match statements can only be used with integer types (int
in this example) and enums.
Jou doesn't have very many different kinds of types, so this covers most use cases.
In Jou, strings are pointers.
They cannot be compared with ==
,
because two different strings could point to different memory locations that contain the same text.
Instead, stdlib/str.jou contains the function strcmp(string1, string2)
,
which returns 0 if the strings are equal, and a nonzero value if they are not equal.
For example:
import "stdlib/io.jou"
import "stdlib/str.jou"
def main() -> int:
s1 = "Hello"
s2_full = "xHello"
s2 = &s2_full[1]
printf("s1=%s s2=%s\n", s1, s2) # Output: s1=Hello s2=Hello
if s1 == s2:
printf("Same pointer\n")
else:
printf("Not the same pointer\n") # Output: Not the same pointer
if strcmp(s1, s2) == 0:
printf("Contains the same text\n") # Output: Contains the same text
else:
printf("Contains different text\n")
return 0
To support matching strings, you can specify with strcmp
in a match statement.
This causes the match statement to call strcmp(string1, string2)
when comparing instead of ==
.
For example:
import "stdlib/io.jou"
import "stdlib/str.jou"
def main() -> int:
s = "Bar"
match s with strcmp:
case "Foo": # this calls strcmp(s, "Foo")
printf("It's foo\n")
case "Bar":
printf("It's bar\n") # Output: It's bar
case "Baz":
printf("It's baz\n")
return 0
Instead of strcmp
, you can specify the name of any function
that takes two arguments and returns an integer.
Return value zero means "equal", and anything else means "not equal".
This way match ... with
can be extended to compare any objects in any way you want,
if you write an appropriate comparing function.
You can use |
to match multiple possible values. For example:
import "stdlib/io.jou"
def main() -> int:
character = 'x'
match character:
case '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9':
printf("It's a number!\n")
case _:
printf("Not a number\n") # Output: Not a number
return 0
Sometimes it's more convenient to write the combined cases on separate lines, like this:
import "stdlib/io.jou"
def main() -> int:
character = 'x'
match character:
case (
'0'
| '1'
| '2'
| '3'
| '4'
| '5'
| '6'
| '7'
| '8'
| '9'
):
printf("It's a number!\n")
case _:
printf("Not a number\n") # Output: Not a number
return 0
This also applies to match ... with
.
Matching enums with match
statements works mostly like you would expect,
but the compiler knows which enum members each case
handles and acts accordingly.
For example, you will get an error if you don't match all enum members:
import "stdlib/io.jou"
enum Thingy:
Foo
Bar
Baz
def main() -> int:
thing = Thingy.Bar
match thing: # Error: enum member Thingy.Baz not handled in match statement
case Thingy.Foo:
printf("It's foo\n")
case Thingy.Bar:
printf("It's bar\n")
return 0
To fix the error, you can add a case Thingy.Baz
with a pass
statement.
The pass
statement does nothing, just like in Python.
import "stdlib/io.jou"
enum Thingy:
Foo
Bar
Baz
def main() -> int:
thing = Thingy.Bar
match thing:
case Thingy.Foo:
printf("It's foo\n")
case Thingy.Bar:
printf("It's bar\n") # Output: It's bar
case Thingy.Baz:
pass
return 0
You can also add case _: pass
if you want ignore all other possible enum members.
Consider the following code:
import "stdlib/io.jou"
enum Thingy:
Foo
Bar
Baz
def thingy_to_string(t: Thingy) -> byte*:
match t:
case Thingy.Foo:
return "It's a foo"
case Thingy.Bar:
return "Barbar"
case Thingy.Baz:
return "Bazzz"
# We never get here, because we return in all cases
def main() -> int:
printf("%s\n", thingy_to_string(Thingy.Baz)) # Output: Bazzz
return 0
This code looks pretty and works well.
The thingy_to_string()
function always returns a value,
because the match
statement handles all possible cases... or does it?
How about thingy_to_string(7 as Thingy)
?
After all, an enum can hold any int
value.
Jou's solution is that you simply shouldn't do thingy_to_string(7 as Thingy)
.
Specifically, a match
statement over an enum without case _
invokes Undefined Behavior
if the given value is not a valid member of the enum.
If you need to support invalid enum values in the thingy_to_string()
function,
you can simply add a case _
to catch them:
import "stdlib/io.jou"
enum Thingy:
Foo
Bar
Baz
def thingy_to_string(t: Thingy) -> byte*:
match t:
case Thingy.Foo:
return "It's a foo"
case Thingy.Bar:
return "Barbar"
case Thingy.Baz:
return "Bazzz"
case _:
return "Invalid"
def main() -> int:
printf("%s\n", thingy_to_string(7 as Thingy)) # Output: Invalid
return 0