Sage
Tusk Language
Welcome to Sage
Volume (54%) Hide Volume
Topics
Types
Tusk features a rich type system, closely mirroring that of Delphi.

Primitives

Tusk defines 22 primitive data types…
  • Variant
    • Variant
    • Any
    • NoCall
  • Integer
    • Int8 / ShortInt
    • UInt8 / Byte
    • Int16 / SmallInt
    • UInt16 / Word
    • Integer / Int32
    • Cardinal / UInt32
    • Int64 / UInt64
  • Float
    • Double (no support for Extended)
    • Single
    • Currency
  • String / Char
    • string / UnicodeString
    • AnsiString
    • Char
    • AnsiChar
  • Boolean
    • Boolean
  • Date / Time
    • TDateTime
  • Unit
    • Units are only used as a prefix, as in DSList.IList.
  • Data Type
    • IType

Several types in the above list have alternative names, defined in the Tusk_System unit (for example Byte is an alternative for UInt8).

Additionally, Tusk exposes several Delphi types that are distinct aliases to other types. For example, TDate and TTime are functionally equivalent to TDateTime, but are their own distinct types.

Strings

Tusk supports Delphi's string and AnsiString types, but not WideString (use string instead).

As in Delphi, Tusk strings are one-based, not zero-based. This applies even when accessing a string through an Any variable…

var s: string := 'hello'; Writeln(s[2]); // prints "e", not "l" var v: Any := s; Writeln(v[2]); // prints "e", not "l"

This also applies to standard RTL functions like Copy, Insert, and Delete

var s: string := 'hello'; Writeln(Copy(s, 2, 2)); // prints "el", not "ll" var v: Any := s; Writeln(Copy(v, 2, 2)); // prints "el", not "ll"

Note that Delphi doesn't allow indexing into a string value through a Variant variable.

Enumerations

Tusk fully supports Delphi's enum types, including scoped enums.

The syntax for defining an enum in Tusk is the same as in Delphi, except that Tusk does not support assigning explicit ordinal values to enum choices (this is not a good idea in Delphi, as it causes the type to not have run-time type information).

Here are some examples of defining enum types…

type TDrumKind = (dkSnare, dkKick, dkFloorTom, dkRackTom); {$ScopedEnums On} TBasicColor = (Black, Gray, White, Red, Green, Blue, Yellow); {$ScopedEnums Off}

With the above definitions, the following code demonstrates using enum types…

var DrumKind: TDrumKind := dkFloorTom; var Color := TBasicColor.Green; for DrumKind := Low(TDrumKind) to High(TDrumKind) do Writeln(Ord(DrumKind)); DrumKind := TDrumKind.dkSnare;

Note that Tusk, like Delphi, stores enums as Integer values when converting an enum to Variant.

For a complete list of enum types exposed to Tusk, consult the RTL documentation.

Syntax Diagram

Enums


Sets

Tusk fully supports Delphi's set types, including sets of Byte, Int8, AnsiChar, Boolean, and enum types.

The syntax for defining a set in Tusk is the same as in Delphi. For example…

type TDrumKind = (dkSnare, dkKick, dkFloorTom, dkRackTom); TDrumKinds = set of TDrumKind;

Tusk supports the same set operations as Delphi.

In Tusk, like Delphi, sets not Variant-compatible. However, the Any and NoCall types don't have this limitation.

For a complete list of set types exposed to Tusk, consult the RTL documentation.

Syntax Diagram

Sets


Dynamic Arrays

Tusk fully supports Delphi's dynamic arrays, with the newer TArray<T> notation, not the obsolete array of T notation.

For example…

type TDrumKind = (dkSnare, dkKick, dkFloorTom, dkRackTom); TDrumKindArray = TArray<TDrumKind>;

The + Operator

Tusk supports the + operator for concatenating dynamic arrays…

var a: TArray<Byte> := [1,2,3]; var b := a + [4,5,6];

Above, b is initialized to [1,2,3,4,5,6].

Constructors

Tusk also supports Delphi's dynamic array constructor notation…

var x := TArray<Double>.Create(1, 2, 4.5);

Variant-Compatibility

In Delphi, when a dynamic array converted to Variant, it becomes a Variant array, which is very different from a Delphi-native dynamic array…
  • Variant arrays use the COM memory manager, not Delphi's FastMM memory manager, resulting in noticeable performance degradation.
  • Variant arrays are not reference-counted: every Variant array is a brand new copy of the data.
  • Variant arrays may only contain OLE-compatible elements (integers, floats, WideString, Boolean, date/times, and interfaces). Specifically, Delphi records and sets are not supported.

For the above reasons, especially the last one, Tusk does not create a Variant array when converting a dynamic array to Variant. Instead, it uses a custom Variant type (defined in DSGenUtil) that can hold values of any Delphi data type. This is also how Tusk stores records, sets, and pointers.

Tusk does support Variant Arrays, when created explicitly via routines such as VarArrayCreate.

For a complete list of dynamic array types exposed to Tusk, consult the RTL documentation.

Syntax Diagram

Arrays


Pointers

Tusk includes pointer types, which are familiar to most Delphi programmers…

var a, b: Integer; var p: ^Integer; p := if Odd(Length(GetUserName)) then @a else @b; Inc(p^); Writeln(a); Writeln(b);

Above, p will point to either a or b (depending on whether the current user name has an odd or even number of characters). Then the program increments the integer p points to.

For a complete list of pointer types exposed to Tusk, consult the RTL documentation.

Memory Safety

In Delphi, pointers are not memory-safe, opening programs up to issues such as buffer overflows and over-writes, dangling pointers, wild pointers, etc.

In Tusk, when you take the address of a variable (using the @ operator), the result points to a value that will be kept "alive" as long as the pointer survives.

For example…

function GetIntPtr: PInteger; var x: Integer; begin x := 123; Result := @x; end; var p: PInteger := GetIntPtr; Writeln(p^); Inc(p^); Writeln(p^);

The above program prints 123 followed by 124. In Delphi, this program exhibits a serious problem: GetIntPtr returns the address of a local variable, which ceases to exist once the function returns. This is not a problem for Tusk – the local variable x continues to exist until p no longer references it.

Additionally, Tusk includes runtime checks when using pointers. For example, revisiting the above code, suppose there's a bug…

var p: PInteger := GetIntPtr; Writeln(p^); Inc(p); // NOTE: should be Inc(p^), not Inc(p)! Writeln(p^);

This code is incrementing p, instead of incrementing p^. In Delphi, this causes the subsequent Writeln to print out some random/garbage value. In Tusk, however, the call to Inc(p) is a runtime error: Tusk knows that p doesn't point to an array or list, so incrementing it is not allowed.

Important

The above safety measures only apply to pointers obtained by taking the address of a variable (including a dynamic array element or a record/interface property). For pointers obtained by a typecast (e.g., casting a string to PChar or casting a NativeInt to any kind of pointer), or by calling a function implemented in Delphi (e.g., GetMem or New), then all bets are off.


Pointer Math

Like Delphi, Tusk supports pointer math (addition and subtraction operations involving pointers). Unlike Delphi, pointer math is memory-safe in Tusk…

type PInteger = ^Integer; var a: TArray<Integer> := [1,2,3,4,5,6]; var p: PInteger; p := @a[0]; for var i := 0 to High(a) do begin Writeln(p^); Inc(p); end;

Important

The above safety measures only apply to pointers obtained by taking the address of a variable (including a dynamic array element or a record/interface property). For pointers obtained by a typecast (e.g., casting a string to PChar or casting a NativeInt to any kind of pointer), or by calling a function implemented in Delphi (e.g., GetMem or New), then all bets are off.


Pointers to Properties

Tusk supports taking the address of a property…

var ls := NewStringList; var cs := @ls.CaseSensitive; ls.CaseSensitive := False; Writeln(cs^); // prints False ls.CaseSensitive := True; Writeln(cs^); // prints True cs^ := False; Writeln(ls.CaseSensitive); // prints False

As the example above demonstrates, a pointer to a property is an additional way to access the property's value. Above, the variable cs maintains a reference to the IStringList object, independent of the ls variable…

var ls := NewStringList; var cs := @ls.CaseSensitive; ls.CaseSensitive := False; Writeln(cs^); // prints False ls := NewStringList; // ls and cs diverge ls.CaseSensitive := True; Writeln(cs^); // prints False

Above, assigning a new value to ls causes ls and cs to no longer reference the same IStringList object.

When taking the address of a property in a record, the pointer maintains a reference to the variable that holds the record.

Pointers to Array Properties

Tusk also supports taking the address of elements in an array property…

var b := NewPropBag; b['x'] := 'hello'; b['y'] := 'goodbye'; var p := @b['x']; Writeln(p^); // prints "hello' p^ := UpperCase(p^); Writeln(b['x']); // prints "HELLO"

The same idea applies to lists…

var ls := NewList(['hello', 3, True]); var p := @ls[1]; Writeln(p^); // prints 3 Inc(p^); Writeln(ls[1]); // prints 4

In the above example, Tusk understands that the IVector.Items property is array-like, so it allows pointer math on p

var ls := NewList(['hello', 3, True]); var p := @ls[0]; Writeln(p^); // prints "hello" Inc(p); Writeln(p^); // prints 3 Writeln(p[1]); // prints True

Tusk only supports pointer math on specific array properties such as IVector.Items, but not others such as IAssociation.Values. In the Tusk RTL Documentation, you'll see such properties annotated with the [TuskArray] attribute (for example, scroll to the end of DSGenUtil.IVector).

Anonymous Method Types

In Tusk, anonymous method types are just like in Delphi…

type TLoginFunc = reference to function( const User, Pwd: string): Boolean; TWorkProc = reference to procedure( const Items: TArray<IPropBag>);

The above example defines two types: TLoginFunc is a function taking two string arguments and returning a Boolean; TWorkProc is a procedure taking a dynamic array of IPropBag.

These types are compatible with nil, and with functions / functions with compatible signatures. For example…

type TWorkProc = reference to procedure( const Items: TArray<IPropBag>); var p: TWorkProc := procedure(const A: TArray<IPropBag>) begin for var Bag in A do Writeln(Inspect(Bag)); end; p([NewPropBag]); p([]); p(nil);

For a complete list of anonymous method types exposed to Tusk, consult the RTL documentation.

Records

Tusk supports records defined in Delphi, including the following features…

  • fields
  • methods
    • instance methods
    • class methods
    • record constructors
    • operator overloads
  • properties
    • read-only, write-only, and read/write properties
    • array properties
    • default array properties
  • nested constants
  • nested types

When a Delphi record has been exposed to Tusk, all public fields are available in Tusk (except those of unsupported types). In addition, all operator overloads are automatically exposed to Tusk. However, properties, methods, and nested types/constants must be exposed explicitly.

New record types may be defined in Tusk, but at present, only fields can be specified (no properties or methods yet).

For a complete list of record types exposed to Tusk, consult the RTL documentation.

Syntax Diagram


Interfaces

Tusk supports interfaces defined in Delphi, including the following features…

  • methods
  • properties
    • read-only, write-only, and read/write properties
    • array properties
    • default array properties
  • casting with the is and as operators
  • casting with TType.Supports and TType.VarSupports

At present, interfaces cannot be defined in Tusk.

For a complete list of interface types exposed to Tusk, consult the RTL documentation.

Generic Interfaces

Tusk supports generic interfaces defined in Delphi, including instantiating a generic interface using types defined in Tusk. For example…

type TSize = (XSmall, Small, Medium, Large, XLarge); TSizes = set of TSize; ISizesSet = ISet<TSizes>;

Above, the generic interface ISet<T> is instantiated on TSizes, which is a type defined at runtime.

For a complete list of generic interface types exposed to Tusk, consult the RTL documentation.

Synthetic Types

Types defined in Tusk, like the three above, are called synthetic types, and they are fully functional. They even have associated type information (PTypeInfo) like built-in types, so they are usable with low-level Utility and Delphi RTL functions that use RTTI.

Warning

Synthetic PTypeInfo have a limited lifetime before Tusk disposes them. They are kept alive primarily by the Tusk TContext. Additionally, for types that are not Variant-compatbile (sets, records, pointers, and dynamic arrays), the custom Variant used to store these values also keeps the PTypeInfo alive. Finally, the lifetime of a synthetic PTypeInfo can be explicitly managed with TypeInfoBuilder.AcquireSyntheticType and TypeInfoBuilder.ReleaseSyntheticType, from the DSTypeInfo unit.


Last Modified: 2/15 10:06:11 am
In this article (top)  View article's Sage markup
2/15 10:06:11 am