Inq Data Types

Value Types

Inq supports a number of types that are used to represent values. As well as any defined range, all Inq value types with the exception of boolean can take on the null value.

The value types have a ranking order to determine operand promotion when mixed types occur in an expression. The available value types are as follows:

Value Types In Rank Order, Highest to Lowest
Type Description Range/Size/Format
decimal:n Arbitrary precision signed decimal n is the precision. 0 means integer.
double Double-precision floating point 64-bit IEEE 754 (Note MIN_VALUE 2-1074 is used to represent null)
float Single-precision floating point 32-bit IEEE 754 (Note MIN_VALUE 2-149 is used to represent null)
long Long integer ±263-1
int Integer ±231-1
short Short integer ±215-1
char A single character 16-bit Unicode (Note Unicode '\uFFFF' is used to represent null)
byte Byte-length integer ±27-1
boolean A boolean value (true or false) true or false. Cannot be null
string A character string. java.lang.String
date A specific instant in time, with millisecond precision. Same as java.util.Date

Here are some example declarations:

int i = 3;
decimal:2 d = "10.51";
short s;

String Constants

String constants are enclosed in double quotes "like this". If the string is broken over more than one line then the line breaks are included, so this statement

writeln($catalog.system.out, "A
string
on
several
lines");

produces the output


A
string
on
several
lines

To remove the line breaks while avoiding the inconvenience of long lines, use the escape character '\' at the end of the line:

writeln($catalog.system.out, "A \
string \
on \
one \
line");

A string on one line

Conversely, to embed a new-line in a string the escape sequence \n can be used:

writeln($catalog.system.out, "A string on\ntwo lines");

A string on
two lines

There are a number of other characters that when preceeded by \ are substituted as follows:

String Escape Substitutions
Character Substitution
n The platform-specific line terminator
r A carriage return 0x0d
t The tab character 0x09
b The back-space character 0x08
f The form-feed character 0x0c
\ The escape character \
" A double-quote
' A single-quote. It is not necessary to escape this character in a string, however it is provided for character literals
Octal sequence two characters in the range 0-7 or three characters as the pattern [0-3][0-7][0-7] that are decoded to a single byte character in the range 0-255.
p The package of the parse module
e The function or service in which the string appears
i The URL from which the script was read
l The current line number

The unicode escape sequence \uXXXX where X is one of 0-9, a-f or A-F is supported but is substituted as the input stream is read and not as part of parsing. This means that \u000a becomes a hard new-line in the stream, which for Inq's definition of a string makes no difference.

Character Constants

A character constant is a character between single quotes (also known as the apostrophe), such as 'Q'. Exactly one character may be present unless the representation is an escape sequence or a unicode escape.

Character Escape Substitutions
Character Substitution
n A new line character 0x0a
r A carriage return 0x0d
t The tab character 0x09
b The back-space character 0x08
f The form-feed character 0x0c
\ The escape character \
" A double-quote
' A single-quote.
Octal sequence two characters in the range 0-7 or three characters as the pattern [0-3][0-7][0-7] that are decoded to a single byte character in the range 0-255.

Numeric Constants

Integer Constants

Integer constants can be expressed as decimal (the default), octal or hexadecimal with an optional type suffix:

<integer-literal> =
    <decimal-integer-literal>
    <hex-integer-literal>
    <octal-integer-literal>

<decimal-integer-literal> =
    <decimal-numeral> [<integer-type-suffix>]

<hex-integer-literal> =
        0 (x | X) <hex-numeral> [<integer-type-suffix>]

<octal-integer-literal> =
        <octal-numeral> [<integer-type-suffix>]

<integer-type-suffix> = ( l | L )

A decimal integer is the digit zero or the digits 1 to 9 followed by one or more of 0 to 9.

A hexadecimal integer is the leading character sequence 0x or 0X followed by one or more of the hexadecimal digits 0-9, A-F, a-f.

A octal integer is the leading character 0 followed by one or more of 0-7.

The data type is int unless the type suffix L or l is present, in which case the resulting value is a long.

Floating Point Constants

Floating point constants can be expressed in decimal comprising a whole number part, a decimal point and a fractional part. An exponent notation and type suffix are optional. If the literal does not include the decimal point then a suffix is required to force the data type to be one of the floating point representations.

<floating-point-literal> =
    <decimal-numeral> "." [<decimal-numeral>] [<exponent>] [ <suffix> ]
  | "." <decimal-numeral> [<exponent>] [ <suffix> ]
  | <decimal-numeral> <exponent> [ <suffix> ]
  | <decimal-numeral> [ <exponent> ] <suffix>

<exponent> =
    ("e" | "E") [ ( "+" | "-" ) ] <decimal-numeral>

<suffix> =
    ( "f" | "F" | "d" | "D" )

The suffices f or F and d or D indicate the float and double types respectively. If the suffix is optional and absent then float is assumed.

Examples of constants of type float:

2.0
2f
2e07
3e-4

Examples of constants of type double:

2.0d
2d
2e07d
3e-4d

Boolean Constants

The boolean constants are the tokens true and false.

Creating Constant Aliases

Constants may be aliased with an anonymous declaration. The following script does not create a variable of type double. Instead, it generates an error when assignment is attempted:

any d = 2.32d;
2.32
d = 0;
file:///C:\inqwell\dev\system.in
com.inqwell.any.AnyRuntimeException: Attempt to mutate const value
        at com.inqwell.any.AbstractValue.constViolation(AbstractValue.java:118)
        at com.inqwell.any.ConstDouble.copyFrom(ConstDouble.java:85)
        at com.inqwell.any.Assign.visitAnyDouble(Assign.java:143)
        at com.inqwell.any.ConstDouble.accept(ConstDouble.java:80)
        at com.inqwell.any.MutatingOperator.visitFunc(MutatingOperator.java:31)
        at com.inqwell.any.AbstractFunc.accept(AbstractFunc.java:90)
        at com.inqwell.any.Assign.doOperation(Assign.java:42)
        at com.inqwell.any.EvalExpr.exec(EvalExpr.java:224)
        at com.inqwell.any.AbstractFunc.execFunc(AbstractFunc.java:68)
        at com.inqwell.any.EvalExpr.evalFunc(EvalExpr.java:112)
        at com.inqwell.any.EvalExpr.evalFunc(EvalExpr.java:149)
        at com.inqwell.any.parser.Inq.main(Inq.java:514)
file:/C:/inqwell/dev/system.in <parser>(2)

The null Constant

The null constant is given by the expression null. When assigned to a variable, the variable takes on the null value for its type.

The null constant is the result of arithmetic or any Inq function that operates on the value types where one or more operands is null. For example, given the following:

int i = 56;
int j = null;

the following expressions will return the null constant:

i + j;
max(i, j);

Relation and Equality

Strict relationship involving null returns false. In the examples above, the expressions i < j and i > j both return false.

Values that are null are equal to one-another and the null constant. The following all return true:

i >= j;
i <= j;
j == null;

The isnull() Function

The isnull() function has the following syntax:

isnull(<expression> [, <expression>]);

It returns true if the single operand equals null or false otherwise. When present, the second operand is returned if the first is null, otherwise the first operand is returned.

Unresolved Operands

The term null may be used when describing a node reference whose path cannot be resolved, i.e. the target is not present in the node space. This is referred to as unresolved null and is handled differently from the null constant. The assignment, arithmetic and relational operators generate an error:

int i = 2;
2
i + j;
file:///C:\inqwell\dev\system.in
com.inqwell.any.AnyRuntimeException: Operand j could not be resolved
        at com.inqwell.any.OperatorVisitor.notResolved(OperatorVisitor.java:198)
        at com.inqwell.any.Add.handleNullOperands(Add.java:107)
        at com.inqwell.any.OperatorVisitor.doOperation(OperatorVisitor.java:41)
        at com.inqwell.any.EvalExpr.exec(EvalExpr.java:224)
        at com.inqwell.any.AbstractFunc.execFunc(AbstractFunc.java:68)
        at com.inqwell.any.EvalExpr.evalFunc(EvalExpr.java:112)
        at com.inqwell.any.EvalExpr.evalFunc(EvalExpr.java:149)
        at com.inqwell.any.parser.Inq.main(Inq.java:518)
file:/C:/inqwell/dev/system.in <parser>(2)

Other functions either generate an error or ignore unresolved null depending on their semantics. The reference entry for each function details its behaviour for unresolved null operands. [TODO]

The equals Constant

The equals constant is given by the expression equals. The equality operator == returns true when a value is combined with equals. For example:

2 == 3;
false
2 == equals;
true
equals == 2;
true

The equals constant is used when creating "don't care" values as part of an event filter used when subscribing to transaction creation events. Its use in any other operator, for example not equals != and the inequality operators <, <=, > and >= generates an error.

Fixed Precision Decimals

The decimal type supports fixed precision decimal numbers with round-half-up arithmetic. Values of this type can only be combined with other decimals (which may have a different precision), the integer types or strings that are convertible to numeric values.

Decimals cannot be combined with the floating point types because of their inherent inaccuracy of representation. The following script (whose output may vary on different platforms) illustrates this:

double dbl = 2.376;
2.375999927520752

decimal:2 dec2 = "2.376";
2.38

dec2 = dbl;
file:/e:/inqwell/doc/src/documentation/content/xdocs/primer/examples/fixedprecision.inq
java.lang.IllegalArgumentException: com.inqwell.any.ConstBigDecimal$CopyFrom Operation not supported on com.inqwell.any.AnyDouble
        at com.inqwell.any.AbstractVisitor.unsupportedOperation(AbstractVisitor.java:110)
        at com.inqwell.any.AbstractVisitor.visitAnyDouble(AbstractVisitor.java:60)
        at com.inqwell.any.ConstDouble.accept(ConstDouble.java:80)
        at com.inqwell.any.ConstBigDecimal$CopyFrom.copy(ConstBigDecimal.java:336)
        at com.inqwell.any.AnyBigDecimal.copyFrom(AnyBigDecimal.java:80)
        at com.inqwell.any.Assign.visitDecimal(Assign.java:154)
        at com.inqwell.any.ConstBigDecimal.accept(ConstBigDecimal.java:134)
        at com.inqwell.any.MutatingOperator.visitFunc(MutatingOperator.java:31)
        at com.inqwell.any.AbstractFunc.accept(AbstractFunc.java:90)
        at com.inqwell.any.Assign.doOperation(Assign.java:42)
        at com.inqwell.any.EvalExpr.exec(EvalExpr.java:224)
        at com.inqwell.any.AbstractFunc.execFunc(AbstractFunc.java:68)
        at com.inqwell.any.EvalExpr.evalFunc(EvalExpr.java:112)
        at com.inqwell.any.EvalExpr.evalFunc(EvalExpr.java:149)
        at com.inqwell.any.parser.Inq.main(Inq.java:514)
file:/e:/inqwell/doc/src/documentation/content/xdocs/primer/examples/fixedprecision.inq <parser>(5)

The precision of a decimal (the number of decimal places it holds) is defined at declaration time and cannot change thereafter. If a decimal is assigned a value of greater precision then that precision is lost according to round-half-up:

decimal:7 d7 = "3.1415927";
3.1415927
decimal:3 d3 = d7;
3.142

The following table summarises the scale of the result when decimals dx and dy of differing precisions x and y are combined in binary operators:

Combining Decimals of Differing Precision
Operator Precision of Result
dx + dy max(x, y)
dx - dy max(x, y)
dx * dy x + y
dx / dy x
dx ^^ n (dx to the power n) n * x (n must be an integer)
max(dx, dy) The scale of the maximum value
min(dx, dy) The scale of the minimum value

The behaviour of other mathematical functions, such as sum() and the other aggregates, is described in the reference entry for the function [TODO].

Inq expressions produce temporary results during evaluation, for example a new decimal, dr of scale x + y results from the expression

decimal:2 d2 = "1.84";
1.84
decimal:3 d3 = "2.273";
2.273
any dr = d2 * d3;
4.18232

If an expression gains precision during evaluation then script authoring should be such that this is not lost too soon by assignment of any temporaries to lower precision variables.

Compatibility Between Value Types

Inq allows different types to be combined in expressions without error. If mixed types are combined then the operand of lower rank is converted to that of the higher.

When one operand is assigned to another, no error occurs provided the magnitude of the right hand side is acceptable to the left. The example code in byteassign.inq demonstrates this:

  >inq -show -in byteassign.inq
  0
  127
  file:/C:/inqwell/dev/examples/byteassign.inq
  com.inqwell.any.AnyRuntimeException: Truncation occurred assigning integer value 128 to byte
          at com.inqwell.any.AssignerVisitor.rangeError(AssignerVisitor.java:28)
          at com.inqwell.any.AnyByte$CopyFrom.visitAnyInt(AnyByte.java:130)
          at com.inqwell.any.AnyInt.accept(AnyInt.java:76)
          at com.inqwell.any.AnyByte$CopyFrom.copy(AnyByte.java:117)
          at com.inqwell.any.AnyByte.copyFrom(AnyByte.java:79)
          at com.inqwell.any.Assign.visitAnyByte(Assign.java:77)
          at com.inqwell.any.AnyByte.accept(AnyByte.java:69)
          at com.inqwell.any.MutatingOperator.visitFunc(MutatingOperator.java:31)
          at com.inqwell.any.AbstractFunc.accept(AbstractFunc.java:82)
          at com.inqwell.any.Assign.doOperation(Assign.java:42)
          at com.inqwell.any.EvalExpr.exec(EvalExpr.java:215)
          at com.inqwell.any.EvalExpr.evalFunc(EvalExpr.java:109)
          at com.inqwell.any.EvalExpr.evalFunc(EvalExpr.java:140)
          at com.inqwell.any.parser.Inq.main(Inq.java:512)
  file:/C:/inqwell/dev/examples/byteassign.inq <parser>(4)

  file:/C:/inqwell/dev/examples/byteassign.inq
  com.inqwell.any.AnyRuntimeException: Truncation occurred assigning integer value -128 to byte
          at com.inqwell.any.AssignerVisitor.rangeError(AssignerVisitor.java:28)
          at com.inqwell.any.AnyByte$CopyFrom.visitAnyInt(AnyByte.java:130)
          at com.inqwell.any.AnyInt.accept(AnyInt.java:76)
          at com.inqwell.any.AnyByte$CopyFrom.copy(AnyByte.java:117)
          at com.inqwell.any.AnyByte.copyFrom(AnyByte.java:79)
          at com.inqwell.any.Assign.visitAnyByte(Assign.java:77)
          at com.inqwell.any.AnyByte.accept(AnyByte.java:69)
          at com.inqwell.any.MutatingOperator.visitFunc(MutatingOperator.java:31)
          at com.inqwell.any.AbstractFunc.accept(AbstractFunc.java:82)
          at com.inqwell.any.Assign.doOperation(Assign.java:42)
          at com.inqwell.any.EvalExpr.exec(EvalExpr.java:215)
          at com.inqwell.any.EvalExpr.evalFunc(EvalExpr.java:109)
          at com.inqwell.any.EvalExpr.evalFunc(EvalExpr.java:140)
          at com.inqwell.any.parser.Inq.main(Inq.java:512)
  file:/C:/inqwell/dev/examples/byteassign.inq <parser>(5)

  -127
  Inq done
  

Lines 4 and 5 generate an exception because the values 128 and -128 are out of range for a byte type.

Conversion to boolean

The boolean type is assignment compatible with all value types and takes on values according to the following rules:

Boolean Conversion
Numeric types false if zero or null, true otherwise
string false if the string is null or has zero length, true otherwise
date false if null, true otherwise

Conversion to boolean most often occurs implicitly, for example as

string s = "my aim";
if (s)
  writeln($catalog.system.out, "my aim is true");

Generally, when evaluating node paths where the result is not one of the above types (that is, the path resolves to one of the container types), implicit boolean conversion yields false when the path does not resolve:

if (x.y.z)
  "resolved";
else
  "did not resolve";

Combining strings With Value Types

The addition operator when used with strings performs concatenation, however the conversion rules between strings and numeric types mean that mixed-type operations involving strings are not commutative. Consider the following:

  >inq -show
  string s = "2";
  2
  int i = 2;
  2
  i + s;
  4
  s + i;
  22
  ^Z
  Inq done
  

Container Types

Inq provides several container types, by far the most important of which is the Map. The examples of variable declarations above have the look familiar to many languages, however its worth remembering what exactly these mean in Inq.

Recall from the discussion of node paths that without a specific prefix a path refers to the stack. We can see that declarations like those above result in new variables being placed on the stack by printing out the stack itself, as shown here:

int i = 3;
decimal:2 d = "10.51";
short s;
writeln($catalog.system.out, "The stack now looks like: " + .);

If we run this script it generates the output


The stack now looks like: {d=10.51, i=3, s=0}

In fact, if you run this script you will also see Inq has initialised the stack with the command line arguments, but these have been omitted for clarity.

The containment node space is Inq's way of subdividing the application and making associations between related items, either by sibling or parent-child relationships. If we wanted to create a collection of variables for later use as a group then we can declare them like this:

int myVars.i = 3;
decimal:2 myVars.d = "10.51";
short myVars.s;
writeln($catalog.system.out, "The stack now looks like: " + .);

The result is now


The stack now looks like: {myVars={d=10.51, i=3, s=0}}

The identifier myVars is a map containing the three variables i, d and s.

Note
Maps are not necessarily ordered, so the order their contents comes out may not be the same as the way they went in.

The use of a path containing two or more elements causes Inq to create any intervening maps necessary to generate the required structure, placing the declared item at the ultimate node.

Map Types

There are several map types supported by Inq, each with differing characteristics. When Inq creates maps implicitly as it does in variable declarations, the map type used is the same as that of the starting point, the stack in the above examples.

All the available map types support the same key-value association required of a containment hierarchy and in all cases the set of keys within a map must be unique. However they differ the following ways:

  • whether a map can be contained within multiple parent maps;
  • whether a map listens for events raised by its children and propagates them to its parent;
  • whether, in addition to access by key, a map supports vector access.

The Hierarchy Map

Maps that support a single parent at any one time are called hierarchy maps. Their data type is hmap. To be more precise, a hmap can only be contained by at most one parent hmap at any time. A hmap can be placed on the stack (for example when passed as an argument to function calls). As we will see in later examples, a containment hierarchy of hmaps is often a data structure built for a purpose relevant to the application. A hmap also passes events received from its children to its parent.

The hmap supports access by vector however there is no initial ordering of the nodes nor does vector access necessarily return the children in the same order they were added. To avoid unnecessary overheads, a hmap initialises itself for vector access when

  1. vector access occurs for the first time
  2. the hmap is sorted
  3. the hmap is reversed (even if it is empty at the time)

When vector access has been used children added subsequently are placed at the end of the vector. If the hmap is sorted and the map is a top-level node set container then new children are placed into the vector at the appropriate position for the current sort criteria.

The Simple Map

A simple map is the data type smap. A smap does not keep track of any parent(s) it may have and places no restrictions on how many other maps, whatever their type, may contain it. The smap does not solicit or propagate events, nor does it establish itself as the parent of any hmaps it contains. The smap is used for stack frames.

A smap does not support vector access and therefore cannot be sorted.

The Ordered Map

A ordered map, declared with the data type omap, has the same characteristics as a smap but with the addition of vector access and sorting. The omap maintains ordering information by default and returns its child nodes in the order they were added.

As for hmaps, newly added children are placed at the end of the vector or at their appropriate vector position when sorted and a top-level node set container.

The following table summarises the map types and their supported features:

Map Types
Type Single Parent Containment Propagates Events Vectored and Sortable Maintains Insertion Order
hmap tick tick tick After first use or sort
smap
omap tick tick

The root node of a process is a hmap, so structures built on it will comprise hmaps also. This is generally what is required, as such structures tend to remain in the process's node space, propagating events from nodes within.

If a structure is temporarily built on the stack but hmaps are required for its containers then an explicit declaration can be used to provide the correct seed type:

hmap m;
int m.myVars.count;
string m.myVars.name;

When this structure is placed in the context node we can discard the seed map m. Remember, however, that a hmap can only have one parent at any given time, so myVars must be removed from m as it is added to $this:

add(remove(m.myVars), path($this.myVars));

The remove function returns the specified node, which it also removes from the node space. The add function places its first operand at the path specified by the second. The expression path(<node_reference>) is used when a node reference itself is required, rather than the node it evaluates to. We will return to the use of add and path in later sections.

Other Container Types

Inq provides arrays and sets as additional utility containers.

The array Type

The array type supports vector access and ordering. Its contents do not have to be unique. Some examples of array handling are shown in array.inq. It produces the following output:

>inq -show -in examples/array.inq
3
[2, 3, 1, 4]
[2, 3, 1, 4, 3]
[2, 1, 4, 3]
12
5
[2, 5, 1, 4, 12, 3]
4
[2, 5, 1, 12, 3]
[1, 2, 3, 5, 12]
Inq done

The set Type

The set type, unlike array, supports neither vector access or ordering and enforces uniqueness within its contents. The example set.inq produces the following output:

>inq -show -in examples/set.inq
[1, 2, 3]
[1, 2, 3]
[1, 2, 4, 3]
[1, 4, 3]
true
false
Inq done

As for unordered maps, the order in which a set chooses to represent its contents is not necessarily the order in which the items were added.

Anonymous Declarations and Avoiding Direct Use of Types

As in many languages, better Inq script results when there are fewer references to specific types. An anonymous declaration takes the form

any <path> = <expression>;

The any declaration is used when the type of the expression is unknown and irrelevant. Every Inq expression returns a value and the any declaration allows this value, whatever it is, to be placed at the specified path. Usually the expression results in a new value, however if it does not, the effect is that an alias is created. This example demonstrates this:

>inq -show
int x=4;
4
any y = x;
4
y=10;
10
x;
10

From the above we see that x and y are both the same variable.

The new Function

We detail how application types and value type aliases are defined using typedef in the typedef section. The new function creates instances of a typedef entity, entity field, one of its defined i/o keys or a defined value type alias by symbolic reference. The following examples assume a typedef entity called Price containing the field Bid and having a i/o key named Filter.

Creating typedef and field Instances

The new function is used to create an instance of Price like this:

any somePrice = new(Price);

and similarly if we want, say, a temporary variable based on the data type of the Bid field it can be created with

any tempBid = new(Price.Bid);

Creating I/O Key Instances

When the symbol includes a field reference, new checks to see if there is an i/o key by the given name. If so, a map containing that key's fields is created:

any filterKey = new(Price.Filter);

There is no requirement that fields and i/o keys cannot have the same name, however if they do then new returns the key, not the field.

Value Type Aliases

A value type alias abstracts a particular type with a symbol. Considering the definition of FXRate described in the typedef section, new variables can be created like this:

any fxRate = new(FXRate);

Setting an Initial Value

As a convenience, new accepts an optional second argument. If present, this argument is used to initialise the return value and must be compatible with it:

any tempBid = new(Price.Bid, "2.5487");

Function Variables

Inq supports executable statements held as variables. Such variables are declared as type func or cfunc. Function variables are frequently used to pass call-back information across service requests and specify handlers for exceptions raised in application script.

A full discussion of function variables is given in the section on control flow and examples of their use in the services to illustrate transactions. They also appear as arguments to Inq built-in functions, such as read and aggregate, covered in building node structures.