Raises

x7's namesake and one of its most standout features. Here's how to use them.

Instructions used in this chapter

SymbolNameBrief
rraiseRaise.
eexceptTry to run a given block, falling back to a different one if it raises.
ssuppressIgnore raises in a certain block.
mmaskMask raises in a block, protecting them from one layer of being caught.
!invertLogical NOT for raising.
<less thanCompare two values, expecting first < second.
Gnot less thanCompare two values, expecting first >= second.
=equal toCompare two values, expecting first == second.
/not equal toCompare two values, expecting first != second.
>greater thanCompare two values, expecting first > second.
Lnot greater thanCompare two values, expecting first <= second.
lliftRun separate blocks on the same arguments and push both the results.

Intro

Raises happen all the time in x7 for all sorts of reasons. Some of them will be familiar from other languages, while others may seem strange because of x7's exclusive use of raises as control flow.

Here are some examples of cases that raise:

  • Type errors, like trying to add a pair to a number
  • Taking more values from the stack than it has
  • Division by zero
  • Reading an unassigned variable
  • Explicitly raising with r
  • Failed comparisons

When an instruction raises and it isn't caught by anything, a message is written to stderr explaining where the raise occurred and why it happened. a screenshot of a terminal. a header states "error: instruction raised", and below it is the x7 code "1 0D", with the "D" highlighted in red. a message next to the code states "division by zero".

Rewinding

Catching raises works slightly differently to catching exceptions in other languages. See this example in Ruby:

x = 0
begin
    x += 1
    1 / 0
rescue
end
puts x

The output of this program is 1, because the x is incremented before the exception from dividing by zero is raised. However, the analogous program in x7 acts differently:

0s1+1 0D
> 0

The s instruction suppresses exceptions just like our empty rescue block in Ruby does, so nothing is raised. But the value on the stack remained 0 without being incremented! This is because almost every instruction in x7 that catches raises performs something called rewinding. If a block ends up raising, all the effects of that block are undone, making it as if it had never run at all.

The only exception is the ! (invert) instruction, which preserves any changes made when it catches a raise.

Handling

In this chapter, we will focus on the most fundamental ways to manipulate raises, but please note that these are not the only instructions that are aware of raises; as the language is designed around them, there are many instructions that treat them differently.

The e (except) instruction, seen already in chapter 2, implements a familiar "try/except" pattern. It takes two blocks and executes the first one, executing the second block if and only if the first one raises.

e1}2`e2 1 3 7r}2`
> 1 2

If the second block raises as well, it is not caught.

The s (suppress) instruction is like e without a second block. It simply catches all raises within it and ignores them.

s2r
> <empty>

The ! instruction catches raises as well, but it does not rewind:

!1r
> 1

If its block finishes without raising at all, ! raises.

!1 2 3`
> <raises>

Note that although ! does not itself rewind, its effects can still be rewound by other instructions outside it.

s!2r`r
> <empty>

Masking

x7 does not give different instructions different "types" of raise, so it may seem as though there is no way to tell them apart and that all raises can only be caught by the innermost handler. For example, if we use s around a block to catch certain raises, how do we allow other raises to go through it to be affected by other handling instructions higher up?

This is the problem that masks solve. Masking an instruction makes it impossible to catch. Anything that would "catch" it simply removes the mask. Masks are created by the m instruction, which catches any raise, adds one layer of masking to it, and reraises it. Here's an example:

smr
> <raises>

Normally, the raise from r would be caught by the s, but the mask "protects" it and allows it to go through. The effect wears off after one layer:

ssmr
> <empty>

However, you can also stack masks so they apply for more layers:

ssmmr
> <raises>

You can use different levels of masking to "pick" which handler the raise should be affected by.

eeemr}}}0`1`2
> 1

Conditions

x7 does not use booleans like most languages do. The only form of condition is "whether or not a certain block raises". As such, comparisons act like assertions:

1 2<
> <empty>
2 1<
> <raises>

The operators = (equal), / (not equal), < (less than), G (not less than), > (greater than), and L (not greater than) simply check that their respective condititions hold for the top two values on the stack and raise if they don't. For these so-called "conditional blocks", !, e, and l act like boolean NOT, boolean OR, and boolean AND respectively. Additionally, s and e can be used to replicate if and if-else conditionals.