InnCant

InnCant

InnCant is a simple scripting language designed to express “favorite roll formulas” by specifying die rolls and basic operations and control structures to convert those rolls to meaningful results.

Overview

InnCant is a simple, basic subset of InnScript. It lacks the full rich object model that InnScript provides, the richer syntax, and complex runtime architecture. It is instead designed to represent “favorite rolls formulas” - roll dice, and optionally do something based on the results.

InnCant gets its name from “InnScript Cant”, where cant is “language peculiar to a specified group or profession” (with “Thieves Cant” being the most famous example). It also is a pun on the word incantation.

It is based on dice specifications - see Dice Specification for full details of what is possible, but essentially common dice “formula” such as “3d6” will roll 3 six sided dice.

InnCant can also be extended to expose global variables and functions defined in InnScript, or other contextual information (for example, custom roll macros use fragments of InnCant, passing in the current and past die roll values as variables)

One important thing about InnCant is that it is designed to be free of side effects. The same InnCant fragment can be evaluated multiple times with no changes. This is especially true when using InnCant to roll dice - the result of one roll may determine what is rolled in the future, and so the results of the first roll shouldn’t change when the second roll happens. For example:

if 1d20 ≥ TARGET then
    "Hit for" & 1d8
else
    "Miss"
end

When the d20 is rolled, it is compared against a target value, and if it equal to or higher, it rolls damage (a d8). But when we try to evaluate the damage, we don’t want TARGET to have changed and causing this to actually miss. By default, InnCant will prevent this. However, if you interact with external InnScript, there is a chance this might be an issue. If, for example, after being hit we increase the target value, this can cause a problem:

if 1d20 ≥ TARGET then
    TARGET = TARGET + 5
    "Hit for" & 1d8
else
    "Miss"
end

Essentially, every time new dice are rolled, the fragment will be re-evaulated.

Values

InnCant is roughly based around JSON in terms of the values it can work with:

  • Boolean values (true and false)
  • Undefined values (null)
  • Integers
  • Lists of other values
  • Frames, a mapping between a collection of constant strings and another value

For example:

%% This is a boolean
var FLAG = true

%% This is a string
var GREETING = "Hello"

%% This is an integer
var X = 10

%% This is a list (with a nested list)
var LIST = [1, "a", true, [3, 4]]

%% The first value in the list
LIST[1]
%% The last value in the list
LIST[-1]


%% This is a frame
var FRAME = {a: 10, b: 20, c: "The letter C"}

%% The value 10
FRAME.a

Note that unlike JavaScript, arrays are “1 based” (i.e, the first value is at index 1, not index 0). If a negative value is passed in, that returns the value relative to the end of the list, with -1 being the last element of the list. If the index is beyond either end of the list, the value null is returned.

Also note that values are constant values (not references to mutable objects like JavaScript). As a result, you can not assign a new value to a list index or frame slot (you will need to create a new list/index).

Lists also support distributing values with scalar operators. For example:

var LIST = [1,2,3]
LIST + 1

Will create a new list with 1 added to every value in the list (i.e., [2,3,4]).

Lists also support the following functions:

  • list.count() : The number of elements in the list (as well as the number of characters in a string and the number of slots in a frame)
  • list.sort() : A sorted list
  • list.reversed() : The list reversed
  • list.max() : The maximum value in the list
  • list.min() : The minimum value in the list
  • list.sum() : The sum total of the list

Expressions

InnCant support a variety of operators, including common and a few less common ones:

  • +, -: Addition, subtraction
  • ×/*, /, %: Multiplication, division, modulo (remainder)
  • &: Concatenate
  • //\, /\/: minimum, maximum (which are also and/or for boolean values)
  • ==, /!=, <, /<=, >, />=: Equal, not equal, less than, less than or equal to, greater than, greater than or equal to

Technically, in InnCant (and InnScript) statements are also expressions, so it is possible to include statement constructs as part of an expression - this is most useful for if-then-else statements that produce a value based on the branch evaluated. So, for example:

var TEST = true
var VALUE = (if TEST then 2d6 else 1d4 end)
VALUE + 3

Will result in whatever 2d6+3 rolls.

Statements

InnCant has basic statements for simple flow control, including conditional test and looping constructs (all of which are, as mentioned above, also valid expressions). These include:

  • var <NAME> = <value> : Used to set a temporary variable to a value
  • if <test> then <truevalue> end : If the ‘test’ expression is true, then the ‘truevalue’ expression is evaluated (otherwise the statement has the value of null)
  • if <test> then <truevalue> else <falsevalue> end : If the ‘test’ expression is true, then the ‘truevalue’ expression is evaluated, otherwise the ‘falsevalue’ is evaluated.
  • while <test> do <body> end: If the test expression is true, execute the body expression. Repeat until the test expression returns false. The value of the statement is the last value that body evaluated to (or null if it never was executed).
  • repeat <body> until <test>: Execute the body expression. Repeat doing this until the test expression is false. The value of the statement is the last value of body
  • for <NAME> = <initialvalue> [down] to <endvalue> [by <stepvalue>] do <body> end: Set the variable NAME to the initial value. So long as the initial value is less than or equal to the endvalue (or greater than or equal to it if down is included), execute the body expression. Each time, the value is incremented by stepvalue (or 1 if not specified).
  • for <NAME> in <expr> do <body> end. Assumes that expr is a list, we evaluate body for each element in the list, setting the variable NAME to that value.