loopy is an older esoteric programming language that I made, inspired by some IOCCC entry I'd seen where the entire program was done in one big loop. It also "solves" the halting problem by not allowing programs to halt.
Comments are between single quotes. 'This is a comment.'
Whitespace forces tokens to be separate than otherwise wouldn't be, but otherwise doesn't matter; newlines are treated as whitespace.
Variable names can contain any ASCII letter, digit, or underscore, can't start with a digit, and are case-sensitive. Names of built-in functions can't be used as variable names.
String literals are surrounded by double quotation marks (""
) and can contain escapes starting with \
. \n
, \t
, \r
, \b
, \\
, \"
, and octal (but not hexadecimal) escapes are supported and mean the same thing as in C. \c
represents the ASCII escape character (hex 1b/oct 033) followed by [
(used for many terminal commands). If a backslash is followed by anything else, the backslash is ignored. String literals can contain unescaped newlines. If a string literal is over 65535 characters, the behavior is undefined.
A loopy program is a sequence of statements. Each statement has a single condition in brackets (not optional), followed by a single expression, which is evaluated if the condition is true; that is, [condition] expression
. This is essentially an if
statement, except that you can't have multiple statements in the body, and you can't nest them (although you can use &
and |
(and and or) to achieve a similar effect). There is no character to mark the end of the statement except for the starting [
of the next statement; if you try to separate statements with a semicolon, Bad Things will happen.
Statements are evaluated in order, starting from the beginning, and after the last statement, the program starts over from the first statement. That is, the entire program is enclosed in an implicit infinite loop. This is the only way to make a loop in this language; there are no explicit loops, gotos, recursion, etc.
There are two data types: numbers and arrays (also called lists). Numbers are all floating-point. Arrays can contain numbers, other arrays, or both. Arrays have a length that's determined when they're allocated, which can't be changed without allocating a new array; an array's length can be determined at runtime using the length
function. Arrays are treated as references; if the same array is assigned to multiple variables, updates in one variable are visible in the other variables as well.
Booleans are represented by the numbers 1.0
and 0.0
; when a number is interpreted as a boolean, anything other than 0.0
is treated as true. Strings are represented by arrays of numbers between 0 and 255 in whatever encoding the terminal uses; strings are terminated by the character 0 or by the end of the array, whichever comes first.
If an array is used where a number is expected, the array's length will be used instead.
Variables are all global, don't need to be declared, and can contain numbers or arrays (or both at different times). All variables start with the value 0.0
.
By default, loopy does not have garbage collection and requires manual memory management. Every time an array is allocated or a string literal expression is evaluated, memory is allocated that must be freed. (This means that a program like [1]""
will cause your computer to run out of memory.) Memory can be freed either by calling free
or by calling print
(which frees the string it prints after printing it). Using memory after freeing results in undefined behavior, as does using memory in the same expression that frees it.
There is also a garbage-collected interpreter but it has a couple issues. For one, the garbage collector only runs when free
is called (with any argument, including a number); this means that programs that just don't free any memory or only free memory with print
(including ones that work just fine without the garbage collector) will likely have memory leaks. For another, calling free inside a larger expression that uses an array can cause problems.
loopy expressions are fairly normal. They can contain variables, number and string literals, and operators, and precedence can be overridden with parentheses. The following lists the operators in order of precedence:
Assoc. | Operators | Meaning/notes |
---|---|---|
unary prefix | - function_name | Negation and built-in function call (see below) |
left-to-right | : | Get a value from an array; the array is on the left, index is on the right |
right-to-left | ^ | Power |
left-to-right | * / | Multiply, divide |
left-to-right | + - | Add, subtract |
left-to-right | ? ! < > <= >= | Compare. ? is equal, ! is not equal. All of these operate on numbers only.
|
unary prefix | !! | Not. I don't remember why it's two exclamation marks instead of one, but it is. |
left-to-right | & | | And, or. They use short-circuit evaluation (i.e., they only evaluate their second argument if necessary) and return one of their arguments (like in JavaScript). |
right-to-left | = | Assignment. Left side must either be a variable or a : expression. Returns the value assigned.
|
loopy has a few built-in functions. You cannot define your own functions. Each function takes a single argument, which doesn't actually need parentheses around it.
abs
sin
atan
2*atan(x/1+sqrt(1-x*x))
; acos(x) = π/2 - asin(x); pi = 4*atan(1)
.
sqrt
ln
ln(x)/ln(n)
.
exp
int
input
ar:0
, the second byte in ar:1
, and so on (where
ar
is the argument). If ar
gets full, reading stops and any
more characters get read on the next input. If a newline or
end-of-file is encountered before ar
gets full, a 0 will be
added after the last character read.
print
printkeep
print
, but doesn't free memory.
array
length
free
free(0)
runs the garbage collector in the garbage-collected version but does nothing in the non–garbage-collected version.)
islist
Interpreter written in C (source only; run make
to compile).
Keep in mind that there is undefined behavior that can in theory do anything; don't run untrusted programs in this interpreter, and definitely don't put it on a server where anyone can run programs on it.
The interpreter takes one or more files as arguments. If you specify multiple files, the interpreter will run them one after another if the program doesn't exit before that, which it always does (making this a completely useless feature).
The -s
option to the interpreter stops the program if it goes an entire loop without any of the conditions evaluating to true. This goes against the philosophy of the programming language, but it keeps the Control-C button from wearing out. (Note that programs that have side effects in their conditions might end too early with this option.)
This version has been modified slightly from the original that I made in 2007 to fix some bugs and make it compatible with more recent compilers. This version does not cause a bus error when encountering a semicolon in the source code, unlike the original version, because I was worried modern compilers would just optimize it out or something.