Teramithic
Teramithic is an esoteric programming language by User:Digital Hunter. The name is intended to evoke a terrible, almost mythical arithmetic system, one that seems minimalist and leads to expressions of gargantuan size. And to add to the list of User:Digital Hunter's languages with names ending in "ic".
Teramithic was meant to be purely functional, with most side effects being "forgotten" outside of any given expression. It in its early stages may also have been influenced somewhat by Scheme, which carries over somewhat in certain programming techniques.
Language overview
Programs in Teramithic are made up of a series of blocks and expressions. Blocks contain, in sequence, expressions or other blocks. Expressions enclose logical statements comprised of variable names and various evaluation and comparison operators. Blocks that contain a single expression can be rewritten as just that expression (otherwise, it'd just be blocks all the way down). Blocks can be "empty", but expressions cannot. The entirety of the program is a single block, so it can be rewritten as just that block's constituents for convenience and clarity. A block will evaluate to the value of the final block (or expression) inside of it. Teramithic supports complex numbers as its primary type, and a special "false" form; anything other than false is a truthy.
If a program terminates, then its "output" is just what its value would have been had it been treated as a block.
Reserved characters
The characters ( ) < > [ ] / \  ^ = { }
are reserved, and cannot be used as a part of a variable name.
Variable names may contain any of the other ASCII characters from the 33 to 126 block, but cannot be #
or @
.
Whitespace is completely ignored.
Blocks and flow control
Within a block, the characters [ ]
enclose blocks, and ( )
enclose expressions.
Within a block, blocks and expressions are evaluated from left to right.
Expressions, and therefore blocks, can have either a truthy or a false value. If a block is evaluated to have a truthy value, the block after the next block is stricken from the order of execution. If a block is evaluated to false, the next block is stricken from the order of execution.
Anywhere within the code, {
, }
, and anything enclosed within matching { }
are treated equivalently to whitespace.
Expressions, variables, and functions
The simplest object that can appear within an expression is a variable.
Excepting the two special variables #
and @
(to be discussed later), a variable name when referenced for the first time must be defined.
How exactly this works will also be discussed later, but less "later".
The next tier up are statements created by combining variables with the characters 
, /
, and ^
.
These statements may also be enclosed within ( )
, and can be used in the exact same places as variable names inside of other statements.
The three evaluation operators have their usual meanings of subtraction, division, and exponentiation, respectively, and are interpreted in the order exponentiation, division, subtraction, and from left to right.
Parenthesised statements are evaluated only as soon as doing so is necessary.

and /
may appear at the beginning of statements, and behave as if preceded by an additive identity (zero) or multiplicative identity (one), respectively.
The arithmeticexpression variety of statements can be combined with comparison operators =
and <
to form what are typically called equations and inequalities.
These comparison operators have their usual meanings of "is equal to" and "is less than", respectively, and evaluate to truthy if their condition is met (false otherwise).
Because the complex numbers cannot be ordered, <
compares absolute magnitudes.
Multiple arithmeticexpression variety statements can be "chained together" with comparison operators, and in this case an entire chain is truthy if and only if every individual pairing is truthy (each comparison operator contributes a comparison of its two adjacent arithmeticexpression variety statements).
The highest "level" within a statement is constructed with any of the above and \
s.
These \
are boolean NANDs, which operate by the following truth table.
(value A)  (value B)  (value A)\ (value B)


truthy  truthy  false 
truthy  false  truthy 
false  truthy  truthy 
false  false  truthy 
\
s group from right to left in the absence of parentheses (but things are still evaluated from left to right).
Parentheses ( )
, as described, will act to "group" items when within expressions, serving a different purpose than they do inside of blocks.
Similarly, [ ]
take on a new meaning inside of expressions, evaluating to the complex conjugate of whatever value is enclosed.
In this light, ( )
can be seen as an identity operator instead of a "grouper", but these are functionally the same.
Variables
A variable is created when a variable name is referenced for the first time.
Immediately following the variable name must be an expression.
A value for that variable is then selected that will not make the expression false.
Note that this does make the language nondeterministic.
For example, if the variable 1
is already defined over this expression (how to do this will be described soon) as 1, the expression
(x(x = 1)  1)
evaluates to 0, as the variable x
was given the only value that would make the expression x = 1
valid.
However, the expression
(x(x = 1 / x)  1)
could equal either 0 or 2.
Note as well that a "clarifying" expression need not have the potential to be false; (x(1))
is a perfectly valid way of generating a "completely random" complex number (though in practice this is simply not possible).
As a mnemonic, the expression that defines a variable may be read as a "such that ..." clause.
Once a variable is defined, its value remains tied to that name for the rest of the expression it is contained within. Variables cannot be reassigned (as following an alreadydefined variable name with parentheses will cause an error), but are limited in scope to only the expression they are in.
The only two variables that need not be defined in this way are #
and @
, also known as "wildcard" and "input", respectively.
When a Teramithic program is run, it is fed a single special complex number, as well as an optional sequence of complex numbers.
The individual complex number becomes the wildcard, and every #
is treated as predefined with this value.
Each time a @
is met, it behaves as the next notyetused member of the input sequence.
If there are more inputs than executed @
s, nothing detrimental happens.
When @
runs out of input numbers to be, @
will instead act as the "false" special form.
If ever a truthy value from comparison chains or NANDs is expected to have a numerical value, it takes that of the wildcard (is treated equivalently to #
).
Functions
Earlier it was assumed that a variable name must be followed by an expression enclosed within ( )
when used for the first time.
However, a variable name may be followed by a special form constructed with [ ]
and >
s to define a function.
This special form has syntax of, enclosed within [ ]
, a sequence of variable names separated with >
and followed with a true block (which can, as usual, be a single expression).
Each of the listed variables will be defined over the entirety of the final block.
For example,
a function with one "parameter" [ x> (x)] a function with two parameters [ a> b> [ (a < b) (b  a) (a  b)]] an alternative way to get a constant is to use a function with no parameters [ (#  #)]
.
Note that the "variable name" of the topmost function is afunctionwithone"parameter"
.
When a variable name that is defined with a function is encountered in an expression, it will also expect to be followed by exactly the number of expressions (technically blocks, but in practice this is unwieldy) necessary to "fill" the placeholder variables at the start of its definition. This applies as well to the first time a function is named.
Using functions, one may effectively define variables with scope across an entire block. For example, using a snippet from earlier, the following is a program that gives either 0 or 2 (assuming nonzero wildcard).
(f[ 1> (x(x = 1 / x)  1)] (# / #))
Of course, this is a fairly simple example for which other methods (such as defining 1
as a variable) would be better.
In general, however, it is a good idea to get familiar with techniques such as this.
Examples
Generally, the two "best" wildcards to use are 1 and Euler's number e. 1 is relatively straightforward for use, and allows for the construction of any algebraic number. Euler's number is a number of great importance, and many transcendental functions can be written using it. Additionally, π (pi), another commonlyused transcendental number, can be defined using e (as can 1 and therefore any other algebraic number). Examples will take wildcard to be Euler's number as this is more generally applicable, unless stated otherwise.
Examples that are functions will be provided in validprogram form, unless stated otherwise.
Truthmachine
Returns 0 if input is zero. Loops forever if input is one. Returns "false" for any other input.
(f[n>[ (n=n) (n) [ (n=n/n) (f(n)) (n\n)]] ](@))
Note that this program does not use the wildcard at all.
Ackermann function
Uses wildcard value 1. Takes two inputs for m and n, computes A(m,n).
(a[m>n>[ (m=m) (n(#)) [ (n=n) (a(m#)(#)) (a(m#)(a(m)(n#)))]] ](@)(@))
Check if a positive real number is an integer
(?[n>[ (n=n) (n) [ (n<#/#) (#\#) (?(n#/#))]] ](@))
Fibonacci numbers
Finds the nth member of the Fibonacci sequence, given F(0) = 0, F(1) = 1, F(n) = F(n  1) + F(n  2). Takes an input to be n. Implements the recursive definition, so expects natural number inputs for expected results.
(f[n>[ (n<(##)/#) (n) (f(n#/#)(f(n#/##/#)))] ](@))
A slightly more verbose program may use Binet's formula.
(f[1> ((p(#\p1/p=1\1<p)^n(n=@)(1/p)^n)/(1pp)) ](#/#))
Factorial
Takes an input and determines its factorial.
(f[n>1>[ (n<1) (1) (f(n1)/(/n))] ](@)(#/#))
Something using a technique resembling tail recursion can be written as well.
(f[n>1> (r[t>c>[ (c=1) (/t) (r(t/c)(c1)(1))] ](1)(n)(1)) ](@)(#/#))
"Get" and "put" functions
Implements "arrays" as reciprocals of numbers of the form S(0)^p(0) * S(1)^p(1) * ... * S(n)^p(n) * ...
for members of Sylvester's sequence S(n) and arbitrary whole number powers p(n) (which are the entries of the simulated array).
As Sylvester's sequence grows incredibly rapidly, these functions exist purely to demonstrate the possibility of more complex datatypes than complex numbers alone.
get[ array> index> {accepts an "array" and an index from which to extract the "return value"} (vars[ syl> 1> {establishes predefined variables} (divide loop[ big> count> [ {main loop to find the exponent by counting how many times "big" can be divided by the appropriate factor} (check int[ x> [ {a function that determines whether its argument is a whole number} (x=x) {if the number is zero,} (#) {then it is.} [ {otherwise,} (x<1) {if it is less than one,} (#\#) {it is not.} (checkint(x1))]] {if x is greater than one, it is an integer iff x1 is an integer} ](big)) {this check is applied to "big"} (divideloop(big/syl)(count1)) {if "big" is still whole, it can be divided again (and the count goes up)} (count)] {otherwise, it is time to escape. "count" starts out at one and is decremented with each iteration, and "big" was divided one too many times. The negative of "count"'s present value is the number of powers of that factor that were in "big" originally. This is the final value of the "get" function} ](/array)(1)) {the loop is run with the reciprocal of "array" as "big", and "count" is given initial value one as mentioned above} ](syl[ n> 1> [ {the "vars" helper function establishes the "index"th Sylvester number} (n=n) {if the index is zero,} ((11)) {the desired factor is two.} ((s(s=syl(n1)(1))1)/(/s)(1))]{otherwise, it will be one more than the product of the previous Sylvester number and its decrement} ](index)(#/#)) {this Sylvester's sequence generator takes the index and one to work} (#/#)) {the "vars" helper also takes a one to make calculations easier} ] {the "get" function when used will expect two following values}
put[ array> index> thing> {accepts an "array", an index, and an item to be inserted returns the altered "array"} (vars[ syl> 1> {establishes predefined variables} (divide loop[ big> [ {main loop to "remove" the value presently at the index} (check int[ x> [ {a function to check for when the appropriate Sylvester number has been divided enough from "big"} (x=x) {if a number is zero, } (#) {it is an integer.} [ {if it is not zero, } (x<1) {but less than one, } (#\#) {it is not an integer.} (checkint(x1))]] {if x is greater than one, x is an integer iff x1 is an integer} ](big)) {this function is applied to "big"} (divideloop(big/syl)) {if "big" is still an integer, it can be divided again} (/big/syl)] {otherwise, it is time to escape. "big" has to be reciprocaled, as "array" was originally reciprocaled to enter the loop. An additional division is done because the loop divided an extra time, leading to the reciprocal having an extra factor} ](/array) / syl^thing) {the loop is done using the reciprocal of the "array" and "thing" is inserted as a negative power of the appropriate Sylvester number. This is the final value of the function} ](syl[ n> 1> [ {the "vars" helper function establishes the "index"th Sylvester number} (n=n) {if the index is zero,} ((11)) {the desired factor is two.} ((s(s=syl(n1)(1))1)/(/s)(1))]{otherwise, it will be one more than the product of the previous Sylvester number and its decrement} ](index)(#/#)) {this Sylvester's sequence generator takes the index and one to work} (#/#)) {the "vars" helper also takes a one to make calculations easier} ] {the "put" function when used will expect three following values}