Datatype Element/en

Aus expecco Wiki (Version 2.x)
(Weitergeleitet von Datatype Element)
Wechseln zu: Navigation, Suche

Introduction

A datatype element is used to define additional datatypes which are not available in the default set of provided datatypes. They can be modified in the datatype editor.

Standard (Predefined) Types

A number of types are already built into and well-known to expecco. These are:

General Types

Any
any object. Useful when the exact type is not known/not relevant. Instances can be of any other type. But please read "Any Type Considered Harmful" before defining an output pin, or freezing an input pin with this type
struct
the "struct" singleton type (i.e. there is only one such type) represents arbitrary compound values, whose field names are defined at runtime. These can be used for compound objects, whose structure is not known in advance (for example, which are returned by an XML-RPC call). Technically, instances are represented as Dictionary (HashTable) instances. In a textual type definition, use the keyword "struct".

Common Predefined Primary Types

The following well known types are predefined and guaranteed to be present in any expecco, without a need for a special type definition. They all refer to instances of standard classes of the underlying Smalltalk runtime system. More information is found in Expecco API document and the Smalltalk Class Documentation of the Smalltalk/X online documentation. A good entry point for further learning is the Smalltalk/X Basic Classes Overview Document, which provides links to the most heavily used classes and more detailed information.

Numeric Types
FixedPoint
a decimal number which presents itself rounded to a given scale (= number of post-decimal digits). Although presenting its value rounded to a number of post-decimal digits, the exact value is kept internally for arithmetic operations, to prevent accumulating rounding errors. These are useful when monetary values are to be processed.
Float
an IEEE rational number with double-precision (64bit).
Be careful when using floats!
Floats sometimes show surprising behavior. E.g. the sum of 10 floats of 0.1 does not result in an exact 1.0, but in 0.99999999999999989 due to rounding errors. Any number which is not exactly a sum of power-of-two fractions is not representable exactly as float and suffers from this rounding error (which is not a problem specific to expecco or its implementation, but a general problem of floating point numbers). Therefore, never use floats when you need exact (e.g. monetary) values. Read Wikipedia for background information. There are extra blocks for fuzzy comparing float values in the Standard Library.
Fraction
an exact fraction. These can result from dividing two integers. Operations on fractions are exact (without rounding errors). When possible, results are reduced and if possible converted to integral results (i.e. (1/3) * (1/3) * 9 -> 1, exact).
Integer
an integral number of arbitrary size (i.e. only limited by memory). Operations on integers automatically care for overflow and reserve more memory if required (i.e. there is no 32- or 64-bit overflow as in most other programming languages). If required to reproduce invalid numeric operations of other programming languages (i.e. C, C++, Java or C#), modulu operations which wrap the result within the 32bit range are provided in the standard library and/or the underlying Smalltalk library.
Number
any numeric value (integer, float or fraction). Most arithmetic action blocks allow for mixed mode arithmetic; i.e. it is possible to add integer values to fractions, floats and vice versa. The resulting value's representation depends on the combination of input values, and will usually be the one with less exactness. I.e. when addinf an exact integer or fraction to an inexact float, the result will be an inexact float.
Text Types
Filename
represents a path/file name. Filename instances know how to construct sub- and parent folder names, provide check for file existence and size queries, etc. If used inside an elementary block, the full protocol of the Filename class can be used on them.
String
a collection of characters of the Unicode character set. For a full protocol description, see String class documentation. Notice that String inherits a lot of functionality from its superclasses CharacterArray, SequenceableCollection and Collection, most of it is also useful for String instances.
StringCollection
a collection of lines, typically as read from a file. StringCollections inherit from OrderedCollection, this any operation which is possible for OrderedCollections is also valid for StringCollections. See StringCollection for more info.
StringOrFilename
a union type which can be either a Filename object, or a String. This is used as input type of many file-handling actions, which will accept both. If required, those actions convert the string to a Filename instance automatically, before performing their action.
UnicodeString
now obsolete, as String has been generalized to include both single-byte and wide characters. The type is still present for backward compatibility, but should not be used explicitly in new projects.
URL
URLs (Universal Resource Locators) are used in web browsers to address web documents and to locate web services. Instances know how to extract host, port, access method and path. See URL for more info.
Container Types
BitString
an array of bits. The elements are integers in the range {0..1}. See BitArray for more info.
ByteArray
an array of bytes. The elements are integers in the range {0..255}. See ByteArray .
HexString
same as ByteArray, for TTCN3 compatibility
Collection
a collection of any other object (possibly unordered). Collection is an abstract superclass covering all other container types. This includes ordered and unordered collections, Strings, Sets and Dictionaries. See Collection for more info.
Dictionary (HashTable)
a mapped collection, where the key/index may be any arbitrary object. The Java name of this is "Hashtable". See Dictionary for more info.
OctetString
same as ByteArray, for TTCN3 compatibility
OrderedCollection
a growable (variable sized) collection of arbitrary objects which is indexed by a numeric index. See OrderedCollection for more info.
SequenceableCollection
any collection, with integral index (possibly non-growing). See SequenceableCollection for more info.
Set
an unordered collection, where every object occurs at most once. See Set for more info.

The system contains many more collection types, such as Bags, BTrees, LinkedLists, etc. For more information refer to the ["Smalltalk/X Online Documentation"] or open a class browser and look at the subclasses of the "Collection" class.

Time and Date
Date
represents a date (dd-mm-yy). See Date.
DateTime
represents both date and time (aka: timestamp) with at least millisecond resolution. See Timestamp.
Timestamps are not limited to the 1970..2039 range as in many Unix systems, or the 1900.. range, as in MS Windows. However, they do not correctly handle the Julian-Gregorian calendar change (eg. when asked for weekday, leap year etc, they will simply extrapolate the Gregorian calendar [1]).
Time
a time of the day (hh:mm:ss). See Time.
TimeDuration
a time-delta (10m, 1h or 3d) with at least millisecond resolution. See TimeDuration.
Often Useful Types
Boolean
boolean truth values: true and false. See Boolean.
IPAddress
represents an ipv4 or ipv6 address. See SocketAddress.
Socket
represents a socket connection stream. See Socket. Notice that sockets inherit from Stream, so all operations defined there are also useful when writing elementary code for socket connections.
Stream
any stream. See Stream and its subclasses, especially PositionableStream, ReadStream, WriteStream and ExternalStream, which is the superclass of FileStream.
UUID
a globally unique identifier. See UUID.
Special Low Level Types (not normally used)
Address
a pointer to external data (C pointer). Instances are typically allocated using malloc/free and are passed to/returned from calls to C library functions (via DLL action blocks).
Handle
a handle as returned by some operating-system calls. For example, window handles and open file handles are represented by them.

Expecco Types

These represent objects as used or generated by expecco itself.

ActivityLog
Represents detail information of a test- or block execution. In particular, pin values, log-messages, caller and called actions are recorded in here.
Datatype
a meta type; instances are datatypes themselves. These are most useful as a pin type of an instance creation block. For example, in the collection-instantiation blocks, a type pin defines what type of collection is to be created.

Datatype-types can be constraint (a so called "constraint datatype-type") to a subset of types which match a particular query.
This is eg. useful to specify that the frozen type must be a subclass of Collection.<p>Details are found in the datatype editor's documentation.

Error & Exception
Additional details of an exception (provided at exception output pins)
GUI Aspect
used with GUI block descriptions
Library
Instances represent imported libraries
Performer
Instances represent action blocks. Their primary use is as input value of a virtual block, in order to define which concrete block is to be executed. Constant values (freeze values) of this type are kept as GUID (Globally Unique Identifier) of the referred-to block. The id of the block is found via the block's selection menu in the project tree, or in the documentation page of the block's editor.
Report Template
Instances represent Report Templates
Resource
Represents a device, a human operator or any other resource.
SUnit TestResult
represents test results as generated by an internal unit test execution
Symbol
unique symbols of the underlying Smalltalk runtime system. These are used as hash key when naming classes, methods, UI aspects and others. Also, all enum values are internally represented as instances of Symbol. Symbols inherit from the String class and can thus be used (readonly) wherever strings can be used; in particular, with the formatting and string concatenation functions.
Verdict
Instances represent a test- or block execution's outcome (PASS, FAIL, ERROR or INCONCLUSIVE)

User Defined Types

New user defined types can be constructed by a number of different mechanisms:

Primary Types

Primary types represent existing classes of the underlying Smalltalk VM (virtual machine). These are the classes as described in the "Smalltalk/X Online Documentation", in addition to any classes which have been loaded dynamically via the plugin mechanism or as extension classes. Notice that the above listed builtin types are actually predefined well-known primary types for the corresponding underlying Smalltalk classes.

Tuple Types

A tuple type consists of a fixed number of indexed fields, each defined by its type. For example, an object which associates a name with an age and a gender could be represented by a tuple instance as:

tuple type:

    (
        String
        Integer
        enum(M|F)
    )

when instantiated, fixed sizes arrays are created for tuple instances. Given a tuple type T, it can be instantiated with:

    inst = T.new();

and the fields can be set with:

    inst[1] = "Andy";
    inst[2] = 45;
    inst[3] = 'M'.asSymbol();

In most cases, compound types are a better choice to tuples: due to the naming of fields, compounds are less error prone and more self-describing.

Compound Types

A compound type consists of a number of named fields, each defined by its type. For example, a customer record could be represented by a compound type instance as a structure consisting of individual fields:

compound type:

    {
        firstName: String
        lastName: String
        zip: Integer
        age: Integer
    }

when instantiated, an anonymous class is created for instances of compound types. These objects provide virtual accessor functions (getters and setters), which are named as the field names. Thus, given a compound type T, it can be instantiated with:

    inst = T.new();

and the fields can be set with:

    inst.firstName( "Andy" );
    inst.lastName( "Miller" );
    inst.age ( 41 );

Enumeration Types

An instance of an enumerated type can take a value from a defined list of values. For example, a test-outcome could be defined as an enumerated type:

enum type:

    (
        pass | fail | inconclusive 
    )

(notice, that there is already a Verdict type present in the set of standard types, so the above example is somewhat unrealistic).

Internally, enum values are kept as instances of the underlying Smalltalk Symbol class, which itself derives from String. Thus, an enum value can be fed to any input which accepts strings. Be aware of this: if you define an enum type as "( 1 | 2 | 3)", the enum-elements will be the strings "1", "2" and "3" - not integers.

A freeze value editor presents the enum values as a combolist, which presents the values as a list from which the user chooses the desired item. The organization of this list can be specified in the datatype editor to better meet UI requirements (for example, as hierarchical menu, sorted etc.)

The following section is only value for expecco version starting with 2.8:

Enum values can have an associated integer value, which is useful, if the type is actually a mapping of a corresponding C or Java type.
For this, add an "= <integerValue>" after the element's name to the above definition. I.e.:

enum type:

    (
        pass = 10 | fail = 20 | inconclusive = 30
    )

Without explicit integer value, elements get auto-incremented values, starting with 0 (zero) assigned implicitly.

Subrange Types

A subrange of the set of values of either the integer- or the character type. A subrange type defines the minimum and maximum values from the base type's set of values.

For example, a type to describe byte-valued integers could be described as:

range( Integer : 0 .. 255 )

Union Types

A union type represents a number of alternative fields. The selection of which field is actually valid within a union must be done elsewhere (or, via reflection, by asking the object dynamically).

For example, a type to describe an object which is either numeric or a string, could be described as:

union type:

    (
        String | Integer
    )

Datatype Types

A type representing other types

The type definition:

datatype

defines a type which can represent any other type.

Datatype types can be constraint as described in more detail below. For example, a type to represent all types which are CTypes can be described as:

datatype( isCTypeType )

These types are useful as pinType of instance creators, especially because the freeze-value men provides useful selection lists.

Constraint Datatype Type

Starting with expecco 2.8, a datatype type can be constraint to a subset of types, by defining it as:

datatype ( <constraint> )

where constraint a selector from one of:

  • isArrayType - for types which have integer-indexable slots
  • isCTypeType - for C-defined types
  • isCEnumType - for C enum types (2.12)
  • isCStructType - for C-defined struct types (2.12)
  • isCUnionType - for C-defined union types (2.12)
  • isCollectionPrimaryType - for subtypes of Collection (i.e. Dictionary, Set, OrderedCollection etc.)
  • isCompoundType - for compound types
  • isEnumType - for enum types
  • isNumberPrimaryType - for subtypes of Number (i.e. Float, Integer, Fraction, FixedPoint)
  • isPrimaryType - for any primary type
  • isStreamPrimaryType - for subtypes of Stream
  • isTupleType - for any tuple type
  • isUnionType - for any union type
  • isUserDefinedDatatype - for all user defined types
  • isWellKnownDatatype - for all builtin (i.e. not user defined) type

or a selector plus string argument from one of (expecco vsn >= 2.12):

  • nameMatches: 'globPattern'
  • nameMatchesRegex: 'regexPattern'

Constrained datatypes should be used by the StandardLibraries only, and are used to limit the input values of some instance creation actions. For example, the "New Collection" action has an input pin, which defines the type of collection to be created. This pin has the type: "datatype( isCollectionPrimaryType )".

Examples:

datatype ( Integer ) -- a type whose instances are all datatypes which are compatible with the Integer class
datatype ( isCTypeType ) -- a type whose instances are all known C-types
datatype ( isCStructType ) -- a type representing all known C struct types
datatype ( nameMatches: 'DPU*' ) -- a type representing all types which match this name

CTypes

A CType is similar to a compound type; however, a data representation is used, which is compatible to structures of the C programming language. This representation is mapped onto a byte container, such as a ByteArray or a malloc'd chunk of memory, and can be passed to/from external C functions.

Be aware, that by default, C data is allocated on the C heap using malloc() and released by free(). Therefore, the overhead in terms of memory use and processing power is much higher, when compared to normal compound or primary instances (i.e. normal expecco objects). You should therefore only use CType instances when data has to be exchanged with C programs (typically via a DLL-call action block).

A CType's definition is given in C syntax.
For example, the above customer record could be represented as the following C structure:

    /* C: */
    struct {
        char firstName[20];
        char lastName[20];
        int zip;
        short age;
    }

Notice the "/* C: */"-comment in the first line, which is obligatory to mark C type definitions. Also notice the explicit dimension of the strings. These are required to define the C structure's exact size. When instantiated, an object is created, which has the underlying C structure layout, and which is not moved in memory by the garbage collector (i.e. its address remains constant). It can therefore be passed to a called C function, for example in a DLL call.

Similar to compound types, instances of CTypes understand accessor functions.
Given a CType in variable T, it can be instantiated with:

    inst = T.new();

a good trick to get hold of the datatype is to fetch it from an output pin, as in:

    inst = aCtypeOutputPin.datatype.new();

Then, the fields can be set with:

    inst.firstName( "Andy" );
    inst.lastName( "Miller" );
    inst.age ( 41 );
Memory Management

By default, a CDatum's underlying memory is allocated with malloc() on the C-heap, and automatically freed when there is no longer any reference to it from expecco.

This scheme works in most situations, except when the datum is to be given to an external C-function which frees the data after use (for example, in a message queue-like framework, where data-buffers are passed to a C-function which frees the data, when done).

In this case, expecco should not free the data. To prevent this, send a "protectFromGC" message to the CDatum:

    inst.protectFromGC();

The opposite is to explicitly free the underlying emory, which is useful if a big memory block should be released as soo as possible:

    inst.free();
Nested Structures and Arrays of Structures

Some care is required when C structures are nested or if arrays of structures/unions are used, as in the following structure:

/* C: */
struct s {
    unsigned long Address;
    unsigned long Length;
    unsigned long PortAddress;
    struct p {
        unsigned char ProtocolID;
        union u {
            uint16 InOutAreaSize;
            uint16 InAreaSize;
        } Parameter;
    } Protocol[4];
};

Assuming, that the above type is referred to as T (acquired usually by getting a pin's datatype), a new instance is created as usual with:

    T = outputPin.datatype;
    inst = T.new();

and gives you a C-datum of size 40 (on a 64 bit machine).

and the fields can be set with:

    inst.Address( 1234 );
    inst.Length( 100 );
    inst.PortAddress ( 40 );

or (in JavaScript) with:

    inst.Address = 1234;
    inst.Length = 100;
    inst.PortAddress = 40;

The accessor functions will always extract (copy) out values from the C datum. Therefore, the inner fields of (say) parameter cannot be accessed directly using code like:

    this does not work!!!

    inst.Protocol[0].ProtocolID = 17;
    inst.Protocol[0].Parameter.InOutAreaSize = 20;

does not work, because it would first extract the Protocol-substructure (Protocol[0]) as a copy of the inner structure, and then access the ProtocolID and Parameter values there, instead of setting fields in the original datum.

To set those inner fields, you need to first get a pointer to the inner structure, and then refer to the fields via the pointer, as in:

    // the following gives us a pointer to the Protocol field...
    pProtocol = _newInst.refMemberAt('Protocol');

    // a pointer to the first element there...
    pProtocol0 = pProtocol.refAt(0);

and then manipulate fields via those pointers:

    pProtocol0.ProtocolID = 17;
    pProtocol0.Parameter.InOutAreaSize = 20;

So here follows example code for an action to instantiate and initialize an instance of the above data structure. To make the example a little shorter, not all fields are initialized; the action block is assumed to have input pins for Address, Length and Port values (all defined as Integer or unsigned long typed pins) and additional pins named pID0, param0 and pid1, param1 to preset two of the inner structures's elements:
New struct s action.png

and its execution code is:

execute() {
    // Generates a new instance of struct s

    var structType;
    var newInst;
    var pProtocol, pProtocol0, pProtocol1;
    
    structType = new_struct_s.datatype;
    newInst = structType.new;

    newInst.Address = Address.value;
    newInst.Length = Length.value;
    newInst.PortAddress = Port.value;

    // the following gives us a pointer to the Protocol field...
    pProtocol = newInst.refMemberAt('Protocol');
    // a pointer to the first element...
    pProtocol0 = pProtocol.refAt(0);

    // set those values from pin values
    pProtocol0.ProtocolID = pID0.value;
    pProtocol0.Parameter.dpInAreaSize = param0.value;
    
    // a pointer to the 2nd element...
    pProtocol1 = pProtocol.refAt(1);

    // set those values from pin values
    pProtocol1.ProtocolID = pID1.value;
    pProtocol1.Parameter.dpInAreaSize = param1.value;

    // write it to the output pin
    new_struct_s.value(newInst);
}
Dealing with Pointers

Warning: the previous expecco releases (pre 2.12) do not handle embedded structure pointers correctly. The example below will only work in 2.12 and later versions.

Consider the following two type definition, which are used in an expecco type definition (of type CType):

/* C: */
struct a {
    int i1;
    float f1;
    double d1;
    char c[20];
};

struct b {
    int i2;
    float f2;
    double d2;
    struct a *aPtr;
};

first notice that there are two structures in one type definition. In this case, the expecco type item (in the tree) represents the last of the defined types - in this case the "struct b".

Let's assume, that we have to allocate an instance of "struct b" in an instance creation block (which has a single pin named "newInst" of type "struct b":

New struct b action.png

and the following code:

execute
    |structB_t newInstOfB|

    structB_t := newInst datatype.
    newInstOfB := structB_t gcMalloc.
    
    newInst value:newInstOfB. "/ write the pin

then, when executed, the output pin "newInst" will generate the following value:


New struct b action output.png

As seen, the "struct a"-pointer in "aPtr" is NULL. An instance needs to be allocated separately and a pointer to it must be placed into the pointer field. To get the pointer-field's type, we can ask the "struct b" instance for its "aPtr" field definition: (newInstOfB type fieldNamed:'aPtr') and then ask this field information about the fields type. Thus, we acquire the aPtr-fields type (i.e. the "struct a"-type) with:

structAPtr_t := (newInstOfB type fieldNamed:'aPtr') type.

Notice, that this gives us a pointer to "struct a" type - not the "struct a" type. Thus,

structA_t := structAPtr_t baseType.

and in the same way as above, we allocate an instance of it with:

newInstOfA := structA_t gcMalloc.

Finally, the new instance is stored into the pointer field with:

newInstOfB aPtr: newInstOfA.

So the final code to instantiate the two structs and link them via the pointer will be:

execute
    |structB_t newInstOfA newInstOfB structAPtr_t structA_t|

   "/ fetch the "struct b"-type from the pin's datatype
    structB_t := newInst datatype.
    "/ get a new instance of "struct b"
    newInstOfB := structB_t gcMalloc.
    newInstOfB i2:22.
    newInstOfB f2:22.2.
    newInstOfB d2:22.22.

    "/ fetch the "pointer-to-struct a"-type from the field
    structAPtr_t := (newInstOfB type fieldNamed:'aPtr') type.
    "/ dereference the type, getting the "struct a"-type
    structA_t := structAPtr_t baseType.

    "/ get a new instance of "struct a"
    newInstOfA := structA_t gcMalloc.
    newInstOfA i1:11.
    newInstOfA f1:11.1.
    newInstOfA d1:11.11.
    newInstOfA c:'hello1'.
    
    "/ store the pointer
    newInstOfB aPtr:newInstOfA.

    "/ send it to the pin
    newInst value:newInstOfB.

when this version is executed, the output pin "newInst" will generate the following value:


New struct b action output2.png

Special Types

The following two types are useful as a pin datatype. They are usually not declared as elements in the project tree, but attached to a pin type (i.e. they are usually anonymous).

Template Types

Template types allow for type save declaration of polymorphic operations.

Template types are a kind of placeholder-type, which will be bound to a real type as soon as a pin is connected. Template types are initially unbound. When a pin with an unbound template type is connected to a real typed pin, the template becomes bound and is treated like a pin of the other type. If a bound template typed pin is connected, the usual type compatibility checks are performed against the underlying real type. Template types have a special name, consisting of a "#"-character followed by a number (i.e. "#1", "#2" etc.). Within the set of pins of a single step, all template types with the same name are unified; i.e. bound together.

For example, assume that we have a block which takes an input value and, depending on a control input, sends its input value to one of two outputs. Let's call this a "multiplexer" block. This block's operation is independent of the input type: it could handle any datum. This might lead us to declare the value input and output pins as "Any":

        +-----+
Any --->| MUX |
Bool -->|     |---> Any
        +-----+

However, having an "Any"-output prevents it from being connected to any non-Any input. You will need a lot of type-cast blocks, to downcast from the very general "Any" to whatever type is actually passed. What we really want to say is that the output's type should always be the same as the input's type, and vice versa. I.e. if the input is connected to a Number-typed output, the multiplexer's output should also be of Number-type. If connected to a String-pin, the output should have String-type, and so on.

        +-----+
#1  --->| MUX |
Bool -->|     |---> #1
        +-----+

Exactly that is what a template type does: it states, that whatever type is connected to the input pin, will be the type of the output pin.

Template types can be embedded in a user-defined type: for example, a block which takes three inputs of arbitrary type, and generates a tuple of these values on its output, could be defined as:

       +--------+
#1 --->|        |
#2 --->| Tupler |---> (#1 #2 #3)
#3 --->|        |
       +--------+

If the inputs are connected to [Bool, Bool, String], it will only be possible to connect to blocks which accept tuples like (Bool, Bool, Number) or more general tuples, such as (Bool, Bool, Any), but not to more specific typed tuples, such as (Bool, Bool, Integer).

Please also read the motivation for template types in "Any Type Considered Harmful".

Constraint Template Types

Constraint Template Types are similar to template types, in that they are initially unbound and bind to a concrete type when connected. However, they restrict the set of possible types which can be bound. For example, if some pin expects the type to be a kind of collection, the datatype can be defined as "#1(Collection)". Similar to a regular template type, this initially unbound type will be bound whenever a pin gets connected. Also, other "#1(Collection)" types at the same step are unified with this type.

However, when connecting, binding is only allowed to types which are compatible with a Collection type.

Formal (BNF) Syntax of Type Declarations

Please refer to the "Formal BNF Description of the Expecco Type Syntax" in the datatype editor documentation .

Defining & Loading new Classes (Primary Types)

Knowledgeable users can define their own classes, bundle them in a package, and load them into expecco; both dynamically during a test run or at expecco startup time. The procedure to generate such class libraries is described in the separate document: " Creating new Class Library Packages/en ".

   


A description of the underlying Smalltalk class library is found in "Smalltalk/X Online Documentation"

The full online documentation can be found under: Online Documentation



Copyright © 2014-2016 eXept Software AG