Exceptionally is a toy language I invented for this challenge. It is inspired by, implemented in, and transpiled to Whython (pxeger's modified version of Python 3 with an added exception-handling operator).
The language
A program in Exceptionally contains of a series of lines, each consisting of a command (or multiple commands connected with the Rescue operator; see below under Exceptions). The lines are executed one by one. When the instruction pointer reaches the end of the program, it wraps around to the beginning. The program thus forms an infinite loop; the only way to break out of the loop is by causing an error. Every Exceptionally program either does not terminate or terminates with an exception.
Each command consists of a symbol, optionally followed by an argument. The argument can be an integer literal, a string literal, or a variable name.
Most commands modify the value of a register, initially set to 0. A command without an explicit argument uses the value of the register as its argument. For example, the command *3
multiplies the register by 3, while the command *
multiplies the register by itself.
Commands
Here is the full list of commands:
{
: Load (copy value into register)
}
: Store (copy register into variable)
+
: Add (value to register)
-
: Subtract (value from register)
*
: Multiply (register by value)
/
: Divide (register by value--floating point division)
%
: Mod (register by value)
^
: Pow (take register to the power of value--result may be floating point)
:
: Item (at given index in register)
[
: Slice From (given index to end of register)
]
: Slice To (given index from beginning of register)
@
: Find (value's index in register)
#
: Count (occurrences of value in register)
|
: Split (register by value)
$
: Join (register on value)
<
: Print (value)
>
: Input (line of stdin into variable)
=
: Equal (assert that register is equal to value)
!
: Skip (skip execution of the given number of lines)
\
: Func (apply the given function to the register)
The Skip command !
provides simple control flow. Think of it as a goto, relative to the current line.
The Func command \
takes a string as its argument and does one of several things:
\"int"
: Cast the register to an integer
\"str"
: Cast the register to a string
\"ord"
: Convert a single-character register value to its character code
\"chr"
: Convert an integer register value to the corresponding character
\"elems"
: Convert a string or list register value to a list of its elements
\"len"
: Get the length of the register
\"sum"
: Sum/concatenate the register
\"range"
: Get the range from 0 to the register's value (exclusive)
\"wrap"
: Wrap the register's value in a singleton list
\"inv"
: Reverse the register's value, or negate it if it's a number
Exceptions
Most commands are capable of triggering an exception in some way. For example, dividing by 0 will cause an exception, as will an out-of-bounds index, as will trying to add a string and a number. These exceptions are the only way to end the program, but they don't have to end the program. They can be caught using the Rescue operator ?
, borrowed from Whython.
A command may be followed by ?
and an additional command. If the first command succeeds, execution continues to the next line. If the first command causes an exception, the second command is executed instead. A line can contain any number of commands chained with ?
. If the last command is reached and it also causes an exception, then the program halts.
For example, consider the line /x ? <"Division by zero"
. The command /x
attempts to divide the register by the value of x
. If x
is zero, this operation will trigger an exception, in which case the second command <"Division by zero"
is executed, printing an error message (and leaving the value of the register unchanged).
The ?
operator is the only conditional construct in Exceptionally. Different commands can be used to trigger exceptions under specific circumstances, and !
can be used to jump to different points in the program depending on the results. For example, in =5 ? !4
, the =
command raises an exception if the register does not equal 5; in this case the !
command is executed, skipping the next four lines. Or again, the following two lines:
-5
/ ? !4
will skip four lines if the register equals 5: -5
subtracts 5 from the register, and /
divides it by itself, resulting in 1 if it is nonzero or an exception if it is zero.
Miscellaneous
Exceptionally has comments that start with '
and go until the next newline.
Whitespace is generally unimportant in Exceptionally. This program to square an input number and halt:
>
\"int"
*
<
/0
could also be written as >\"int"*</0
. Newlines that end comments, and whitespace in strings, are the only significant whitespace.
The transpiler
Here is the Exceptionally transpiler, written in Whython:
import re
import sys
COMMANDS = {
"{": "reg := %s", # Load
"}": "%s := reg", # Store
"+": "reg := reg + %s", # Add
"-": "reg := reg - %s", # Sub
"*": "reg := reg * %s", # Mul
"/": "reg := reg / %s", # Div
"%": "reg := reg %% %s", # Mod
"^": "reg := reg ** %s", # Pow
":": "reg := reg[%s]", # Item
"[": "reg := reg[%s:]", # SliceFrom
"]": "reg := reg[:%s]", # SliceTo
"@": "reg := reg.index(%s)", # Find
"#": "reg := reg.count(%s)", # Count
"|": "reg := reg.split(%s)", # Split
"$": "reg := %s.join(reg)", # Join
"<": "print(%s)", # Print
">": "%s := input()", # Input
"=": "1 / (reg == %s)", # Equal
"!": "ip := ip + %s", # Skip
"\\": "reg := FUNCS[%s](reg)", # Func
}
PROGRAM_TEMPLATE = """program = %s
FUNCS = {
"int": int,
"str": str,
"chr": chr,
"ord": ord,
"elems": list,
"len": len,
"sum": lambda x: sum(x) ? sum(x, []) ? "".join(x),
"range": lambda x: list(range(x)),
"wrap": lambda x: [x],
"inv": lambda x: x[::-1] ? -x,
}
ip = 0
reg = 0
while True:
eval(program[ip])
ip = (ip + 1) %% len(program)"""
def transpile(code):
transpiled_lines = []
code = code.lstrip()
if code[0] == "?":
raise SyntaxError(f"Program cannot begin with {code[0]}")
while code:
if m := re.match("'.*", code):
code = code[m.end():].lstrip()
continue
if code[0] == "?":
# Continuing a previous line
operator = code[0]
transpiled_lines[-1] += f" {operator} "
code = code[1:].lstrip()
if not code:
raise SyntaxError(f"Program cannot end with {operator}")
else:
# Start of a new line
transpiled_lines.append("")
if code[0] in COMMANDS:
command = code[0]
code = code[1:].lstrip()
# Parse the command's argument
if m := re.match(r"\w+", code):
# Name or integer literal
argument = m.group()
argument = (argument.lstrip("0") if int(argument) else "0") ? f"'{argument}'"
code = code[m.end():].lstrip()
elif m := re.match(r'"[^"]*"', code):
# String literal
argument = repr(m.group())
code = code[m.end():].lstrip()
else:
# No argument, defaults to register
argument = "'reg'"
argument = eval(argument) ? f"eval({argument})"
translation = "(" + COMMANDS[command] % argument + ")"
transpiled_lines[-1] += translation
else:
raise SyntaxError(f"Expected command, found: {code[0]}")
return PROGRAM_TEMPLATE % transpiled_lines
Attempt This Online!
You can run the transpiler for yourself at Attempt This Online. Put your Exceptionally code in the Input box and click Execute; the program will output the transpiled Whython code.
Here is a version that immediately executes the transpiled code: Attempt This Online.
Example program
Here is a commented program that outputs the orbit of an input number under the Collatz function. (This is the program used in the second ATO link above.)
' Read initial number from stdin
>
' Convert to an integer
\"int"
' Store a copy in n
}n
' Output
<
' Minus 1
-1
' Divide by itself; if n is 1, (n-1)/(n-1) is division by zero and the program halts
/
' Otherwise, keep going; load n back into the register
{n
' Mod 2
%2
' Is this equal to 0? If not, skip the next three lines
=0 ? !3
' Load n
{n
' Divide by 2
/2
' Skip the next three lines
!3
' Load n
{n
' Multiply by 3
*3
' Add 1
+1
' Skip the next line (that is, skip the first line when the program loops)
!1
cat
converting to a Brainfuck program is valid but boring? \$\endgroup\$