RTensor formula calculator (v2.3) documentation

By dkl9, written 2022-231, revised 2023-230 (3 revisions)

RTensor is the sixth-best formula calculator in the world. (I wrote that slogan a long while ago. Don't ask what the five better ones are.)

§ Concepts


All valid code in the RTensor language gets parsed into an abstract syntax tree (AST). The AST consists of nodes, which may or may not reference subnodes, making them "branch nodes" (as e.g. operations and function calls) and "leaf nodes" (as primitives) respectively.

ASTs are common to many languages, and you don't really need to know about them for basic RTensor use.

§ Tensor

Consider the following progression:

All items in that progression qualify as tensors of various ranks; ranks of tensors continue beyond 2 as far as you want, tho ranks from 0 to 2 occur most commonly.

§ Interface

The most developed interface for RTensor is the webpage, which consists of some documentative content, followed by a space for alternating lines of input and output.

§ Running code

To run a line of RTensor code, type it (or copy it) into the current input line and press Enter. You can also use the up and down arrow keys to navigate thru the current session's history of earlier lines. Whilst not in the RTensor language, you can combine multiple lines of code into one input by joining them with semicolons (;).

§ Successful output

When no error occurs whilst processing the input code, RTensor will write as output the code's return value. Preceding this return value, RTensor indicates a variable name (an underscore followed by a serial number) to which it saved that return value. If the input code consists of multiple semicolon-separated statements, RTensor will evaluate all of the statements, but will only present the last one's return value, and the serial variable will only receive the final statement's return value.

RTensor also silently saves the final return value to the variable _ (underscore), but leaves its previous value if an error occurred.

§ Extra output

Some code (such as syntactic functions) produces extra output, in varying forms, before the main output.

§ Error messages

When an error occurs whilst parsing or evaluating the input code, RTensor will display a relevant error message. If the error starts with a PascalCase error type, that means it came directly from JavaScript, and should be regarded as a bug.

If the error occurs when evaluating, RTensor will not execute code after the part that triggered the error; errors propagate up thru a program.

For some code (such as 3+), RTensor successfully parses and evaluates it, but notices that not all of the code corresponds to part of a valid AST. In addition to typical output, RTensor leaves the message "possible syntax error", which almost always means "actual syntax error".

§ Primitives

§ Numeric literals

A numeric literal directly represents a real number (a scalar), and consists of one or both of an integer component and a fractional component. Each component consists of digits (in base ten). The fractional component, if present, must begin with a dot (.). Valid numeric literals include 0, 389, 3.14159, and .367.

For now, you can use . to mean NaN, but this may change.

§ Identifiers

An identifier consists of a sequence of letters, digits, underscores, or (unlike most other languages) apostrophes (akin to prime symbols), and does not start with a digit. An identifier, when interpreted to evaluating code, references a variable, which can hold a value of any type. Shadowing/function scope may affect what variable an identifier references.

§ Operators

Operators combine one or two subexpressions (making the operation unary or binary, respectively) and describe a computation based on the subexpressions. Unary operators go before their subexpression. Binary operators go between their subexpressions.

Each operator has a precedence, which may group it more tightly or loosely than other operators, and each precedence has an associativity. Unary operators all have tighter precedence than binary operators, regardless of their relative precedence as binary operators. You can use parentheses to group operations differently from what their precedence and associativity would normally imply.

In operator descriptions, a and b represent subexpressions and their results.

§ Basic operators

When used on a scalar and a tensor, the result comes from the tensor, with each element replaced by the result of the operation between the scalar and each scalar in the tensor. For example, 3 * ((7, 2), (-1, 3), (-8, -2)) returns [[21, 6], [-3, 9], [-24, -6]].

When used on two tensors, the result comes from the elementwise combination of the two tensors, or an error if the tensors have different structures.

§ Appending

a, b gives an output which depends on the relative rank of a and b:

These semantics let you chain the appending operator (as in a, b, c, d) to build a many-element tensor.

§ Concatenating

a .. b gives an output which depends on the ranks of a and b:

When using the concatenation operator, carefully distinguish it from a decimal point; e.g. 3..5 will result in an error, unlike the valid options 3.5 and 3 .. 5.

.. a encapsulates a as a one-element tensor, effectively incrementing the rank without adding any new data.

§ Maplet

a => b constructs and returns an anonymous function. a may consist of a comma-separated list, each of which labels a separate argument for the function; it gets tranlated into a pattern, but syntactically forms as an expression. b must consist of some expression, possibly using variables defined in a.

For example, x, y => x + y makes addition into a two-input function.

§ Assignment

The assignment operation, a = b, saves the value from b into the location described by a, and returns the new value of a (or something closely related to a, by undocumented semantics). a may consist of an identifier — in which case, b replaces the value already stored in that variable. a may have a function call or index suffix (the two have the same form):

§ Precedence ranking

From tightest to loosest, L = left-associative, R = right-associative:

  1. logarithms, R
  2. exponentiation, radicals, R
  3. multiplication, division, L
  4. addition, subtraction, concatenation, L
  5. equality, comparison, L
  6. appending, L
  7. maplet, R
  8. assignment, R

§ Indexing

An expression suffixed with a parenthesised expression (v(i)), or the equivalent bracketed form (v[i]), forms an indexing expression (or a function call, depending on v); if v refers to a tensor, the index expression returns the ith element of v (a tensor of rank at most one less than that of v). Indexing in RTensor starts at 1 and not 0. Attempts to access v(i) where i exceeds the length of v, i does not equal an integer, or i subceeds 1 will cause an error.

The i component of the indexing expression can include extra comma-separated indices, but RTensor currently ignores all indices after the first.

§ Function calls

An expression suffixed with a parenthesised expression-list (f(a, b, c, ...)) forms a function call (or an indexing expression, depending on f); if f refers to a function, the function call returns the value f provides when evaluated with the given argument list. The argument list may contain any number of arguments, including 0 (i.e. f()).

Whilst , normally acts as an operator, the argument-list syntax suppresses this behaviour. In an argument list, , acts as an operator iff it occurs in parentheses, as in the first argument of f((a, b), c). Operators with precedence looser than that of , (i.e. => and =) will not work at all unless parenthesised.

§ Evaluation process

When evaluating a function, RTensor first evaluates all the arguments, then searches for a pattern (going in descending priority) that matches the arguments. If RTensor could not find a matching pattern, an error occurs. Upon finding a matching pattern, RTensor evaluates the expression or internal function corresponding to that pattern in the function's definition, using the argument-name correspondences given from the pattern to make local variables for that expression.

§ Syntactic function calls

If a function is called with bracket syntax (f[a, b, c, ...]) rather than the parenthetical syntax, the arguments will be given to the function as expressions (syntax), not values; they will be left unevaluated. Whether this is useful depends on the function. Typically, a function is only usable with either evaluated arguments (the more common case) or syntactic arguments.

§ Function definitions

Functions, as objects in RTensor (or function definitions), consist of a list (arranged by priority) of pattern-expression pairs, or cases. Some functions (those predefined by the code which sets up RTensor) have, in place of an expression defined from RTensor, a function in the language implementing RTensor.

§ Pattern matching

A pattern consists of a list of zero or more parameters. A parameter consists of either an identifier or some other expression:

When checking if a pattern matches a list of arguments, the arguments get aligned to the parameters; if the number of arguments differs from the number of parameters, the pattern does not match without any further checks.

To make a parameter that matches only values which equal an existing variable, wrap the variable identifier in an identity (such as x + 0 or x * 1).

RTensor currently does not provide pattern-matching mechanisms more complex than equality checks against constant values; you must do so yourself with explicit checks.

§ Anonymous functions

RTensor provides both an assignment-based syntax and an anonymous maplet-based syntax for defining functions. The former works with (named) variables, but the latter constructs a function without saving it to a variable (anonymous). A maplet operation takes its left operand as a pattern and its right operand as an unevaluated sub-expression, constructing and returning a single-case function, usable in the same contexts as any other function.

In that single case's expression, one can use variables defined in the scope which defines the anonymous function, creating a sort of closure.

If the left operand has commas, RTensor will break it down into a list of patterns, allowing for more than one parameter, rather than a single pattern.

§ Currying

Using closures, one can make multi-argument anonymous functions a different way: make a function that returns another function, as a partially-evaluated form of the desired multi-argument function — for example, x => (y => x + y) instead of x, y => x + y. RTensor does not have a special syntax to facilitate the requisite chained method calls, so one would use the curried version not as f(a, b) but f(a)(b).

§ Built-in functions

In descriptions of functions, identifiers in parentheses or brackets (as appropriate) correspond to arguments for valid ways to call a function. The exact identifier used indicates the expected type:

Some functions work on a tensor, but only look one rank deep, using the tensor in place of a vector. In this case, the parameter uses t for the identifier.

§ Numeric/trigonometric functions

All functions in this list that accept a real number (so not random) will accept, in place of that number, a tensor, and will perform the same operation on each number in the tensor, returning an equivalently-structured tensor.

§ Tensor functions

§ Control flow

§ Other built-in functions

§ Non-function built-ins

If you want e, use ^1.