Difference between revisions of "Expecco API/en"

From expecco Wiki (Version 2.x)
Jump to navigation Jump to search
(3 intermediate revisions by the same user not shown)
Line 3,107: Line 3,107:
 
You have to make sure that a CC-compiler toolchain is available and the "cc" command is found among your path.
 
You have to make sure that a CC-compiler toolchain is available and the "cc" command is found among your path.
   
Warning: bridged C-code is highly dependent on the c-compiler toolchain of the target machine. It should only be used by experts and only for very special situations, when all alternatives have been tried and failed. Bridged C code can be used to implement time critical functions or to interface to C/C++ libraries, when the simple DLL-call interface is too complicated to use.
+
Warning: bridged C-code is highly dependent on the C-compiler toolchain of the target machine. It should only be used by experts and only for very special situations, when all alternatives have been tried and failed. Bridged C code can be used to implement time critical functions or to interface to C/C++ libraries, when the simple DLL-call interface is too complicated to use (for example, if complicated data structures have to be exchanged, C-callbacks are needed, or C++ interfaces are to be implemented).
   
 
=== Bridged C Code API ===
 
=== Bridged C Code API ===
   
Note: Bridged C Elementary Actions are currently being developed - the API may change slightly until officially released.
+
Note: This is a previous API documentation. Bridged C elementary actions are currently being developed - the API may change slightly until officially released.
 
 
 
The API is intended to look similar to the other language's elementary code API. However, due to the nature and syntax of the C-language, certain differences are apparent.
 
The API is intended to look similar to the other language's elementary code API. However, due to the nature and syntax of the C-language, certain differences are apparent.
Line 3,139: Line 3,139:
 
==== Logging (C) ====
 
==== Logging (C) ====
   
*'''logFail''' (''fmt'' , ...) <br>Adds a fail message to the activity log. The arguments are printf-style.
+
*'''logFail''' (''fmt'' , ...) <br>Adds a fail message to the activity log, but continues execution. The arguments are printf-style.
   
*'''logError''' (''fmt'' , ...) <br>Adds a error message to the activity log. The arguments are printf-style.
+
*'''logError''' (''fmt'' , ...) <br>Adds a error message to the activity log, but continues execution. The arguments are printf-style.
   
*'''logWarning''' (''fmt'' , ...) <br>Adds a warning to the activity log. The arguments are printf-style.
+
*'''logWarning''' (''fmt'' , ...) <br>Adds a warning to the activity log, but continues execution. The arguments are printf-style.
   
*'''logInfo''' (''fmt'' , ...) <br>Adds an info message to the activity log. The arguments are printf-style.
+
*'''logInfo''' (''fmt'' , ...) <br>Adds an info message to the activity log, and continues execution. The arguments are printf-style.
   
 
*'''alert''' (''fmt'' , ...)<br>Adds a warning message to the activity log, and also shows a DialogBox, which has to be confirmed by the operator. The dialog box and confirmation can be disabled by a settings flag in the "''Execution''" - "''Log -Settings''" dialog (by default it is disabled).
 
*'''alert''' (''fmt'' , ...)<br>Adds a warning message to the activity log, and also shows a DialogBox, which has to be confirmed by the operator. The dialog box and confirmation can be disabled by a settings flag in the "''Execution''" - "''Log -Settings''" dialog (by default it is disabled).
Line 3,243: Line 3,243:
   
 
The code will generate a string by concatenating the printf strings of its arguments.
 
The code will generate a string by concatenating the printf strings of its arguments.
  +
 
Create a new bridged C action, and enter the code:
 
Create a new bridged C action, and enter the code:
 
 

Revision as of 08:05, 13 August 2019

Contents

How to Program[edit]

Before you start programming, please read the "How to Program" document, which describes how program code is handled in expecco. Unless you are familiar with the dynamics of a Smalltalk development environment, some of it may be unknown to you, and you will have more fun and be more productive, if you know the power of the tools. For the best development experience, take a look at the debugger, workspace (notepad) and data inspectors.

The rest of this document describes the syntax and semantics of the elementary action languages; for tool usage, please read the HowTo document.

expecco API[edit]

The expecco API provides functions and access to the underlying class library for the use in elementary blocks written in Smalltalk and JavaScript (i.e. for code which is executed inside expecco itself). This API is not available for elementary blocks written in other languages which are executed by external script engines (Shell, Batch, Python, Node.js etc.) or inside a different Java or CLR Virtual Machine (Groovy, VBScript, IronPython).

For a short introduction to the Smalltalk programming language, please read the Smalltalk tutorial in the Smalltalk/X online manual. For a full book on learning Smalltalk, read "The joy of Smalltalk, An introduction to Smalltalk" by Ivan Tomek.

The API for Groovy elementary blocks is different and described below.
The API for bridged Node.js actions is described here.
The API for bridged Python and Jython actions is described here.

JavaScript and Smalltalk Elementary Blocks[edit]

The JavaScript and the Smalltalk API consist of the same functions - both call into the underlying Smalltalk/X system, for which extensive documentation is available as Online Documentation and as Class Reference. The Smalltalk/X class library is ANSI compatible; therefore the official ANSI documents and Smalltalk literature are also valuable sources of information.

JavaScript and Smalltalk only differ slightly in their syntax; semantically they are very similar:

JavaScript Smalltalk
this self the current activity
this.functionName() self functionName a call without arguments
this.functionName(arg) self functionName:arg a call with one argument
this.namePart1_part2(arg1,arg2) self namePart1:arg1 part2:arg2 two arguments
functionName(...) self functionName... implicit this/self receiver
accessor self accessor slot access - implicit this/self send
stat1 ; stat2 stat1 . stat2 statement terminator / separator
in ST: required to separate statements
in JS: required to terminate every statement.
Thus, in Smalltalk, the last period "." inside a method or block can be and often is omitted.
if (cond) {
  ifStats
} else {  
  elseStats
}
cond ifTrue:[
  ifStats
] ifFalse:[
  elseStats
]
conditional execution
while (cond) {
  stats
}
[ cond ] whileTrue:[
  stats
]
while-loop. Notice the extra brackets in Smalltalk
function () { stats } [ stats ] an anonymous (inner) function (called "block" in ST)
function (a1, a2,...) { stats } [:a1 :a2 ...| stats ] in ST: blocks are references to anonymous function
var v1, v2, ... ; | v1 v2 ... | local variables inside a function (or block)
return expr; ^ expr return a value.
In ST: a return from within a block returns the enclosing top-level function
In JS: a return returns from the inner function

Smalltalk beginners may be irritated by the missing binary operator precedence rules. In Smalltalk, all operators have the same precedence and are evaluated left to right. Thus, "a + b * 5" in JavaScript means "a + (b * 5)", whereas in Smalltalk, it is evaluated as "(a + b) * 5". For this, as a guideline (and actually a strict convention), all Smalltalk expressions should be parenthesized to make the intention clear, whenever it conflicts with the intuitive (standard mathematical) precedence.

All functions are actually implemented in Smalltalk and follow the standard Smalltalk naming conventions. The same function names are used for JavaScript. As seen above, this scheme works well for functions without or with a single argument, but requires a name translation for functions with more than one argument. This translation is done by the JavaScript compiler by replacing every colon (:) of the Smalltalk name by an underline (_) character, except for the last colon. Thus for example, the Smalltalk name "at:put:" will be replaced to "at_put" in JavaScript.

In JavaScript, a function-name alone (i.e. without explicit receiver) is translated into a self-send; thus "this.foo()" and "foo()" are equivalent.

Also, for non-argument accessor functions (getters), the empty argument list can be omitted in JavaScript; therefore, "foo" and "this.foo", "foo()" and "this.foo()" are all equivalent.

For example, the JavaScript call:

    this.environmentAt("foo")

is written in Smalltalk as:

    self environmentAt:'foo'

and, to demonstrate the multi-argument translation rule, the Smalltalk code:

    self environmentAt:'foo' put:1234

is written in JavaScript as:

    this.environmentAt_put("foo", 1234)

Syntax[edit]

For a formal specification of the JavaScript and Smalltalk languages, see the appendixes below. The following gives a rough overview over the most common syntactic constructs.

JavaScript Smalltalk Notes
this self the current activity
null nil a null reference (UndefinedObject)
; . Statement Terminator/Separator
"..." '...' a String constant (JavaScript allows single quotes too)
#'...' a Symbol constant (not available in JavaScript)
#... also a Symbol constant (not available in JavaScript)
[ el1 , el2 , ... ] #( el1 el2 ... ) an Array constant (elements must be constants)
[ expr1 , expr2 , ... ] { expr1 . expr2 . ... } a computed Array (notice the expression terminators in ST, and that "," (comma) is an operator in ST)
#[ el1 el2 ... ] a ByteArray constant (not available in JavaScript)
( num / den ) a Fraction constant (not available in JavaScript)
rcvr.f () rcvr f function call
rcvr.f (arg) rcvr f: arg with 1 arg
rcvr.a_b (arg1, arg2) rcvr a: arg1 b: arg2 with 2 args
notice the different names of the function
"a_b" vs."a:b:"
return; ^ self without return value (in ST: from outer function; in JS: from current function)
return expr; ^ expr with return value (in ST: from outer function; in JS: from current function)
return from execute; ^ self return outer from inner function
! expr expr not
e1 && e2 (e1 and:[ e2 ]) non evaluating and (e2 is not evaluated if e1 is false)
e1 || e2 (e1 or:[ e2 ]) non evaluating or (e2 is not evaluated if e1 is true)

Syntactic Sugar[edit]

Some syntactic constructs of the JavaScript language are implemented as library functions in Smalltalk. Among others, most noteworthy are conditional execution (if-the-else) and loops.
The JavaScript syntax is mapped to corresponding Smalltalk library functions as follows:

JavaScript Smalltalk Notes
if (expr) { .. } (expr) ifTrue:[ ... ]
if (! expr) { .. } (expr) ifFalse:[ ... ]
if (expr) { .. }
else { ... }
(expr) ifTrue:[ ... ]
ifFalse:[...]
while (expr) { .. } [expr] whileTrue:[ ... ] Notice the square brackets
for (expr1;expr2;expr3) { .. } expr; [expr2] whileTrue:[ ... expr3 ]
for (var i=start; i <= stop; i += incr) { .. } start to: stop by: incr do:[:i | ... ]
try { .. } finally {...} [...] ensure:[...]
try { .. } catch(e) {...} [...] on:Error do:[:e | ...]
try { .. } catch(e) {...} finally {...} [ [...] on:Error do:[:e | ...] ] ensure:[ ... ]
var++ var := var + 1
var-- var := var - 1
arr[ idx ] arr at: idx Array and Dictionary indexed read
Notice that JavaScript uses 0-based indexing, whereas Smalltalk is 1-based.
arr[ idx ] = expr arr at: idx put: expr Array and Dictionary indexed write

Operators[edit]

JavaScript Smalltalk Notes
% \ modulu operator
<< bitShift: left-shift; negative shift count is right-shift
>> rightShift: right-shift; negative shift count is left-shift


Array Indexing[edit]

A big potential pitfall is the different indexing base used in Smalltalk vs. JavaScript: in Smalltalk, array indices start at 1 and end at the array's size. In JavaScript, indices start at 0 and end at the array's size minus 1.

This also affects some character- and substring search functions, which return 0 (zero) in Smalltalk, whereas some of the standard JavaScript functions return -1 if nothing is found.

The following functions expect or return 0-based indices if used in JavaScript:

  • collection.indexOf(element)
    attention: there exists a corresponding Smalltalk method, which returns a 1-based index
  • collection.lastIndexOf(element)
    attention: there exists a corresponding Smalltalk method, which returns a 1-based index
  • collection.indexOf(element, startIndex)
    the corresponding Smalltalk method "indexOf:element startingAt:startIndex" uses 1-based indices
  • collection.lastIndexOf(element, startIndex)
    ditto


We admit that this is a little confusing at times, but there is no easy solution to this: and we decided to neither change the Smalltalk nor the JavaScript scripting languages inside expecco.

To make your intentions explicit, you can use variants of the "indexOf"/"lastIndexOf" functions which make these implicit differences more explicit: "indexOf0()" / "lastIndexOf0()" and "indexOf1()" / "lastIndexOf1()" are available for both languages.

Shame on us:
the above array indexing behavior was changed for indexOf: between 2.7 and 2.8 (due to a missing translation, the 2.7 versions used 1-based indices in both languages). This leads to a problem, if you run any old 2.7 suite in newer expecco versions without reimporting the StandardLibrary or if you used those functions in your own elementary code. To avoid a need to touch those old suites, we have added a backward-bug-compatibility mode, which is described in a separate note of the release notes.

Inner Functions[edit]

Inner Functions in JavaScript[edit]

It is possible to create functions within the elementary block's code. This example shows how to do so:

  execute() {
    ...
    function f(arg1, arg2) {
      return(arg1 + arg2);
    }
    ...
}

A inner function f is created with two arguments (arg1, arg2), which returns the sum of both. The function is called like this:

   f(3,5)

Inner functions can be used for example to filter specific elements from collections, as in:

   function isEven(n) { return n.even(); }

   var numbers = [1,2,3,4,5,6,7];
   var evenNumbers = numbers.select( isEven );

Inner functions are first class objects: they can be stored in variables, collections, passed as argument or returned from functions. They can even be passed to other blocks via input- and output pins or via environment variables. Functions can be anonymous; the above example could also be written as:

  execute() {
    var f;
    ...
    f = function (arg1, arg2) {
      return(arg1 + arg2);
    }
    ...
}

(notice the missing function name after the "function" keyword).


Notice, that there is a difference in the behavior of the return statement between JavaScript inner functions and Smalltalk blocks: a JavaScript "return" statement inside an inner function returns from that inner function only, whereas a return in Smalltalk always returns from the outer-most method. To return from the outermost function in JavaScript, use the "return from <fnName>" statement form.

In expecco, where the outermost function is always named "execute", write:

    execute
       ...
       function myFunction() {
           ...
           return from execute;
       }
    ...

(notice that the execute function is not supposed to return a value)

Lambda Expressions in JavaScript[edit]

JavaScript functions can also be written in a slightly shorter lambda notation as:

    (arg1, ... argN) => { statement; ... return expression; }

or (with a single argument) as:

    arg => { statement; ... return expression; }

or (without arguments) as:

    () => { statement; ... return expression; }

and results in a function object comparable to:

    function (arg1, ... argN) { statement; ... return expression; }

The function's body may also be a single expression (without braces), as:

    arg => expression

For example:

    aCollection.map( el => el ** 2 )

returns a collection of squared numbers, the same as the corresponding Smalltalk code:

    aCollection map:[:el | el ** 2 ]

This is plain "syntactic sugar" - it does not give any new semantic functionality, but makes the code shorter and possibly easier to read.

Inner Functions in Smalltalk (Blocks)[edit]

In Smalltalk, inner functions are called "block", and are defined as:

   f := [:arg1 :arg2 | arg1 + arg2 ].

This block can be called by sending at a "value:value:" message (one "value:" for each argument):

   f value:3 value:5

Blocks without argument are defined as:

   f := [ Transcript showCR:'blabla: Euler was great' ].

and invoked with a simple "value" message.

In both JavaScript and Smalltalk code, it is an error to invoke an inner function/block with a wrong number of arguments.

Smalltalk blocks and JavaScript inner functions can be used interchangeable - i.e. it is possible to pass a JS inner function to a collection method such as "collect:" or "select:". It is also possible, to pass either via input/output pins to other activities (which is not considered good style, as it could make the program quite hard to understand, if not used with caution).

Notice again, that the behavior of the Smalltalk return ("^") is different from a JavaScript return ("return-Statement") inside inner functions. The JavaScript return returns a value from the inner function, whereas the Smalltalk return forces a return from the containing method (the block's "execute" method). The Smalltalk return always behaves like the "return from execute" JavaScript special form.

Example uses for Inner Functions[edit]

In Smalltalk, blocks are very often used when enumerating collections. For example, code corresponding to a C# 3.0 collection "select (where(x => ...))" is written in Smalltalk as:

    |names namesStartingWithA|

    names := #( 'Alice' 'Ann' 'Bob' 'Mallory' ).
    namesStartingWithA := names select:[:n | n startsWith:'A'].

or in JavaSript as:

    var names, namesStartingWithA;

    names = [ "Alice" , "Ann" , "Bob" , "Mallory" ];
    namesStartingWithA = names.select( function(n) { n.startsWith("A"); } );

To find the first element in a collection, for which some condition is true, use:

    |names firstNameContainingAnO|

    names := #( 'Alice' 'Ann' 'Bob' 'Mallory' ).
    firstNameContainingAnO:= names detect:[:n | n includesString:'o'].

or in JavaSript as:

    var names, firstNameContainingAnO;

    names = [ "Alice" , "Ann" , "Bob" , "Mallory" ];
    firstNameContainingAnO = names.detect( function(n) { n.includesString("o"); } );

For more information on the Smalltalk syntax, see [Smalltalk Basics] in the [Smalltalk Online Tutorial].

Builtin Data Types[edit]

The builtin types below and user defined primary types are mapped to corresponding classes of the underlying Smalltalk runtime system. An introductory overview and links to the detailed documentation of individual classes is found in the Smalltalk Class Documentation of the Smalltalk/X online documentation. The following gives a rough summary of the main concepts. 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[edit]

The underlying numeric type implementation supports multiple number representations. These can be used transparently in mixed-operations, and values are automatically converted as required. For example, the division of two integers returns another integer iff the division does not produce a remainder. Otherwise, a fraction object is returned. Although very very rarely required in practice, values can be converted explicitly, via one of the "asXXX" messages, if required:

  • asFloat()
  • asInteger()
  • asFraction()
  • asFixedPoint(scale)

Thus, if you want to avoid fractions (why would you?), process the result of an integer division using one of the asFloat(), truncated(), floor(), ceiling() or rounded() functions. But be aware that fractions provide an exact result, whereas float arithmetic is inherently imprecise, rounds on the the last bit and that such rounding errors accumulate.

Integer[edit]

This represents arbitrary precision integral numbers. Conversion and memory allocation is completely automatic. Thus, you can write:

x = 100.factorial();

to get the huge number:

933262154439441526816992388562667004907159682643816214685929638952175999932299156089
41463976156518286253697920827223758251185210916864000000000000000000000000

Small integer values (<32bit or <64bit, depending in the underlying hardware's native word length) are stored more efficiently than large integers - however, the conversion and representation as used by the system is completely transparent and automatic. The same is true for mixed mode arithmetic between small and large integers.

Occasionally it is required to perform modulo arithmetic in 32 or 64bit, for example to reproduce or check values as generated by C or Java programs. For this, use methods from the "special modulo" category, such as "add_32:", "add_32u:", "mul_32:" etc. Use a class browser to find those methods.

Integer constants (of arbitrary size) can be given in hexadecimal (Smalltalk: 16rxxx, JavaScript 0xXXXX), octal (Smalltalk: 8rxxx, JavaScript 0XXXX), and binary (Smalltalk: 2rxxx, JavaScript 0bXXXX). In fact, Smalltalk supports any base from 2 to 36 written as <b>rxxx, where <b> is the base. Thus 3r100 is the ternary representation of the integer 9.

Overview: More Info
Reference: Class Documentation

Float[edit]

Float represents double precision IEEE floating point numbers (64bit). Although seldom needed, conversion to single precision (32bit) and high precision (80bit/128bit, depending on the underlying hardware) is possible via the asShortFloat() and asLongFloat() conversion functions.
Overview: More Info
Reference: Class Documentation

Float Rounding Errors[edit]

Be reminded that floating point arithmetic is inherently inaccurate: there will almost always be rounding errors on the last bit. The reason is inherent in that most rational numbers simply cannot be represented as the sum of powers of two. For example, the 0.3 is one of them: no sum of powers of two (1/2, 1/4, 1/8, ...) is able to represent this number exactly.

Typically, print functions (which convert floating point values to a string representation) will "cheat" and round on the last bit. However, if you operate with such numbers (i.e. add, multiply, etc.) these rounding errors will accumulate and may finally add up to more than one bit of error. When this happens, you will get the typical ".999999" result strings.

As an example, try adding 0.3 a hundred times, as in:

|t|
t := 0.3.
1000 timesRepeat:[t := t + 0.3].
t print

which may give "300.300000000006" as a result.

As a consequence, NEVER compare floating point values for equality. In the above case, the comparison with "300.3" would return false!

Floating point numbers should always be compared by giving a range (i.e. between x-delta and x+delta) or by giving a number of valid digits, by which to compare. expecco provides a number of action blocks for this kind of "almost-equal" comparisons. Of course, there are also functions in the Float class, in case you need this in elementary Smalltalk or JavaScript code.

Fraction[edit]

Fractions are arbitrary-precision rational numbers. You will get them when dividing two integers and the result is not integral. Fractions represent an exact result, in contrast to floats, which are only approximations.

For example:

1 / 3

returns a fractional result with a numerator of 1 and a denominator of 3 (as opposed to the Smalltalk // operator, which truncates the result of the division to an integer). Fractions have the advantage of avoiding rounding errors; thus, when the above fraction is divided by 3, you will get (1/9), which we can multiply by 9 to get the exact 1 (an integer) without any rounding error. Be reminded that this is typically not the case with Float or Double precision numbers. Due to rounding errors on the last bit, you will often get 0.9999999 as a result there.

Fractions automatically reduce themselves - therefore, when adding (1/3) + (2/6), you will get (2/3).

Be aware that fractional arithmetic is done by software, whereas floating point arithmetic is done by CPU instructions. Therefore, floating point arithmetic is typically much faster.
Overview: More Info
Reference: Class Documentation

FixedPoint[edit]

FixedPoint numbers are decimals, with a configurable number of post-decimal digits. They are typically used when dealing with money. Like fractions, they avoid rounding errors. However, when printed, they round in their last digit (however, the exact information is always kept for further processing). FixedPoint numbers are perfect to represent money and other fractional data, which must be represented with a fixed number of post-decimal digits. Internally, fixed point numbers are represented as fractions with a power-of-10 denominator.
Overview: More Info
Reference: Class Documentation

Number[edit]

The Number type is an abstract union type which includes all of the above types. Thus, a variable or pin declared as being of "Number" type can take any of the above values. Due to the polymorphic implementation of the underlying numeric class library, most operations can be passed any of the above values (even mixing arguments is possible). For example, the "Add" elementary block, which is implemented by simply calling the Smalltalk "+" operation, is able to deal with any combination of argument types.
Overview: More Info
Reference: Class Documentation

String Types[edit]

Strings can come in three flavours (subclasses of CharacterArray), depending on how many bits are required to encode the character's code point. In general, Unicode is used internally as the encoding. However, converters are available to translate into other encodings, such as JIS or ISO 8859-x.

Bulb.png Notice that ASCII (0..127) and ISO 8859-1 (0..255) are proper subsets of the Unicode set. Do not confuse Unicode with UTF-8: Unicode is the numeric value associated to a character and used everywhere within expecco. UTF-8 is an encoding format for data interchange of Unicode characters. UTF-8 is NOT used inside expecco, only for data exchange with the external world.

In most situations, you do not need to care for the actual number of bits required to store the characters, and expecco/Smalltalk takes care of the actual representation in memory. However, when dealing with the external world (files, sockets, devices), it may be required to perform explicit conversions to/from wider formats or different encodings (UTF-8, UTF-16, etc.)

String[edit]

This is used to represent character strings, where each individual character has a one-byte encoding. I.e. the values are 0..255. Individual characters can be accessed by an integral index starting with 1 in both Smalltalk and JavaScript.
See also: Most Useful String Functions and Most Useful Collection Functions
Overview: More Info
Reference: Class Documentation

Unicode16String[edit]

This is used to represent character strings, where at least one character needs a two-byte encoding. I.e. any character's code point is in the range 0x0100 .. 0xFFFF. In fact, there is even a Unicode32String class (see below), which supports the full four-byte Unicode character set. But in practice, such strings are very rarely ever encountered or needed.

Unicode32String[edit]

This is used to represent character strings, where at least one character needs more than a two-byte encoding. I.e. any character's code point is above 0xFFFF. Such strings are very rarely ever encountered or needed.

Character[edit]

Individual characters (as extracted from a string or possibly read from a file) are represented as instances of the Character class. Queries are available to ask the character for its type (isLetter, isDigit etc.) or its Unicode encoding (code point). There is virtually no limit and every character from the full Unicode set (e.g. even above 0xFFFF) can be represented by instances of the Character class.
Overview: More Info
Reference: Class Documentation

Collection Types[edit]

Smalltalk and therefore also expecco provides a very complete set of collection types which are tuned for different access patterns or memory consumption. An introductory overview on the collection classes is found in the
Most Useful API: Most Useful Collection Functions
Overview: Collections Overview of the Smalltalk/X online documentation.
Reference: Class Documentation

The following paragraphs list the most used collection classes. Notice, that Smalltalk provides many more useful container classes, so please take a look at the online documentation or take a life tour with the browser.

Array[edit]

Ordered, fixed size, indexed by an integral index starting with 1 in both Smalltalk and JavaScript. Can store any type of object, and storage requirements are 1 machine word per element. As the size is fixed (when created), adding and removing of elements is not possible. Use one of the other collections (OrderedCollection, Set or Dictionary) if the number of elements needs to change later or is not known in advance. When accessing elements by index, access time is constant O(1). Searching for an element in an Array (using includes:, indexOf: or similar methods), time complexity is O(N), because the search is linear through all elements of the array.
Overview: More Info
Reference: Class Documentation

ByteArray[edit]

Ordered, fixed size, indexed by an integral index starting with 1. Can only store very small byte-valued unsigned integers (0..255), and allocates 1 byte per element. Access time and complexity are like with ordinary arrays, but the required memory is much less. ByteArrays are used to represent the contents of binary data, binary files, protocol elements (data packets) or low level memory dumps.
Overview: More Info
Reference: Class Documentation

BitArray[edit]

Ordered, fixed size, indexed by an integral index starting with 1. Can only store tiny bit-valued unsigned integers (0..1), but only needs one byte of storage for every 8 elements.
Reference: Class Documentation

BooleanArray[edit]

Ordered, fixed size, indexed by an integral index starting with 1. Can only store booleans (false..true), but only needs 1 byte for every 8 stored boolean elements.
Reference: Class Documentation

FloatArray[edit]

Ordered, fixed size, indexed by an integral index starting with 1. Can only store short IEEE floats (32bit) and allocates 4 bytes per element. Access times and complexity are like those of regular arrays, but they require much less memory. FloatArrays are often used for data interchange with C language functions or in data acquisition scenarios (series of measurement values).
Overview: More Info
Reference: Class Documentation

DoubleArray[edit]

Ordered, fixed size, indexed by an integral index starting with 1. Can only store IEEE doubles (64bit) and allocates 8 bytes per element. These are used in similar situations as FloatArrays.
Overview: More Info
Reference: Class Documentation

HalfFloatArray[edit]

Ordered, fixed size, indexed by an integral index starting with 1. Can only store IEEE half floats (16bit) and allocates 2 bytes per element.
Notice that half floats have a very low precision and are seldom used. Exceptions are game programs, 3D graphics accelerators, to preserve memory by using this very compact format for depth information or texture attributes, and some audio formats. It is also occasionally used with AI programs in neural networks.

IntegerArray[edit]

Ordered, fixed size, indexed by an integral index starting with 1. Can only store small 32bit unsigned integer values (0..0xFFFFFFFF) and requires 4 bytes of storage per element.
Reference: Class Documentation

SignedIntegerArray[edit]

Ordered, fixed size, indexed by an integral index starting with 1. Can only store small 32bit signed integer values (-0x800000000..0x7FFFFFFF), requiring 4 bytes of storage per element.
Reference: Class Documentation

LongIntegerArray[edit]

Ordered, fixed size, indexed by an integral index starting with 1. Can only store small 64bit unsigned integer values (0..0xFFFFFFFFFFFFFF), allocating 8 bytes per element.
Reference: Class Documentation

SignedLongIntegerArray[edit]

Ordered, fixed size, indexed by an integral index starting with 1. Can only store small 64bit signed integer values (-0x80000000000000000..0x7FFFFFFFFFFFFFFF), allocating 8 bytes per element.
Reference: Class Documentation

SignedByteArray[edit]

Ordered, fixed size, indexed by an integral index starting with 1. Can only store very small 8bit signed integer values (-128..127), requiring 1 byte of storage per element.
Reference: Class Documentation

OrderedCollection[edit]

Ordered, variable size, indexed by an integral index starting with 1. Can store any type of object. OrderedCollections are specifically tuned for adding/removing elements at either end. Access time to elements by index has a time complexity of O(1). Insertion and remove at either end approaches O(1), but is O(n) for inner elements, because the elements are moved inside a linear container. When searching for an element in an OrderedCollection (using includes:, indexOf: or similar methods), time complexity is O(n), because a linear search is done.
Overview: More Info
Reference: Class Documentation

For very big or lightly sparse collections, you can use a SegmentedOrderedCollection. This uses a hierarchy of buckets and does not need storage for big unused regions inside the collection. However, due to the additional computations, the break even in performance compared to regular OrderedCollections is usually at around 100K to 1M elements.

For very very big collections (millions of elements) which are very lightly filled, use a SparseArray. This only keeps the filled slots, and is great, if you have only a few valid elements inside a virtually huge index space.

SortedCollection[edit]

Sorts itself, variable size, indexed by an integral index starting with 1. Can store any type of object for which a order relationship is either implicit (i.e. the elements responds to the "<" (less) message) or for which an explicit comparison block can is given (i.e. you provide a sort-block, which takes two elements and returns a boolean depending on the ordering).

By default, sorting is by ascending order based on the "<"-message. However, the sort order can be arbitrarily changed by setting the sortBlock, a two-argument JS-function or a two-argument Smalltalk block which should return true, if its first argument is to be "considered" smaller than the order (i.e. the default sort block is "[:a :b | a < b]").

Access by numeric index is O(1). Insertion and remove is O(1) for the first and last element, O(n) for inner elements, because the elements are moved inside a linear container.
When searching for an element in a SortedCollection (using includes:, indexOf: or similar methods); time complexity is O(log n), because a binary search can be done. When a big sorted collection is created, it is faster to first create it as a non-sorted collection (OrderedCollection) and then converting the whole collection via asSortedCollection(), instead of adding individual elements one-by-one.
Overview: More Info
Reference: Class Documentation

Set[edit]

Unordered, variable size, keeps a single reference only, per equal element. Can store any type of object. When searching for an element in a Set (using the includes: method), time complexity approaches O(1), because the search can be done based on a hashing algorithm in asymptotic constant time.
Overview: More Info
Reference: Class Documentation
In addition to the unordered Set, useful classes are also OrderedSet and SortedSet.

Bag[edit]

Unordered, variable size, keeps a count, per equal element. Adding, removing and insertion check all run in O(1). Can store any type of object.
Reference: Class Documentation

Dictionary[edit]

Unordered, variable size, implements arbitrary key-value mappings. Can store any type of object and use any kind of object as key. When accessing or searching an element in a Dictionary (using at:, includesKey: and similar methods), time complexity asymptotically approaches O(1), because the key search can be done based on a hashing algorithm. Notice, that the same value may be stored multiple times in a dictionary (i.e. stored under multiple keys). Asking for the presence of a particular value in the dictionary (i.e. the reverse query without key) still has O(N) time complexity.

If you need fast access both via the key and the value, either use an additional reverse mapping dictionary, or store an additional value->key mapping in the same dictionary (if the key and value spaces are disjoint).

Java users will know a subset of the dictionary functionality as "Hashtable".
Overview: More Info
Reference: Class Documentation

OrderedDictionary[edit]

Ordered, variable size, implements arbitrary key-value mappings. Can store any type of object and use any kind of object as key. Being similar to dictionaries, these remember the order by which elements were added. It provides both access by hash-key and access by index (which is the order in which elements have been added). Creation of an OrderedDictionary is somewhat slower than for Dictionary or OrderedCollection, but access by either numeric or hashkey is fast with O(1) time complexity.
Reference: Class Documentation

OrderedSet[edit]

This is very similar to a Set, but remembers the order in which elements were added, and provides index read access methods. When enumerated, the elements are generated in this order.

Queue[edit]

Ordered, variable size. Can store any type of object. Queues are especially tuned for adding elements at one end, and removing them at the other. The internal representation uses a buffer in round-robin fashion. Both adding and removal are done on O(1) time.
Overview: More Info
Reference: Class Documentation

If multiple processes are accessing the same queue concurrently, use a SharedQueue, which is implicitly synchronized.

BTree, AATree, BinaryTree[edit]

Combining the functionality of Dictionary and SortedCollection, these keep their elements in a sorted order, indexed by a key, both of which can be arbitrary objects. In contrast to dictionaries, which use hashing, these use a tree-like representation and guarantee both asymptotic O(log N) worst case and average runtime. (Dictionaries have a much better typical time complexity of O(C), but also a worst case of O(N), if the hash keys are badly distributed). These trees are self balancing to avoid the linear worst case access time of non balanced trees. [Class Documentation]

There is also a non-balancing BinaryTree, which is slightly faster (by not balancing), but its worst case access times may degenerate to O(N), if elements are added non-randomly.
Reference: Class Documentation

Stream Types[edit]

Streams are accessors for either internal collections or external files, sockets and other character oriented I/O devices. They are created by either "opening" a file or device or by creation "on" a numerical indexable collection object (String, Array, OrderedCollection etc.). Once created, read streams deliver the next element via a "next()" operation, write streams append elements via the "nextPut(e)" operation. In addition to the above elementary functions, bulk read/write and linewise read/write and various other functionality is provided.

An introductory overview on the stream classes is found in the [Streams Overview] of the [Smalltalk/X online documentation].

ExternalStream[edit]

A common superclass providing bytewise or linewise access to any stream of the underlying operating system. Concrete subclasses are Socket, Filestream, Console Streams, PTYStream etc.
Overview: More Info
Reference: Class Documentation

FileStream[edit]

Provide bytewise or linewise access to the underlying file system.
Overview: More Info
Reference: Class Documentation

CharacterWriteStream[edit]

A special stream usable for mixed single- and multibyte characters (i.e. Unicode). This stream starts to collect into a single byte string buffer, but switches automatically to a multibyte character string as required.
Reference: Class Documentation

Socket[edit]

Provides low level bytewise or linewise access to a communication socket. Typically, but not limited to TCP/IP connections.
Overview: More Info
Reference: Class Documentation

There are a number of useful companion classes which deal with host addresses:
Reference: SocketAddress, IPSocketAddress and IPv6SocketAddress.

PipeStream[edit]

These are used to read/write from/to another program's standard input or standard output. For details, consult a Unix programmer's manual.

Other useful Types[edit]

Date[edit]

Represents a calendar date. Instances can be created with "Date today", by giving day/month/year or a calendar day. Instances offer a large number of queries, such as weekday, dayInWeek, leapYear etc.
Overview: More Info
Reference: Class Documentation

Time[edit]

Represents a time-of-day in second resolution.
Overview: More Info
Reference: Class Documentation

Timestamp (Date and Time)[edit]

Represents a timestamps. With the 18.1 release, the resolution has been improved to sub-nanosecond resolution, although internal timestamps as generated by the operating system will usually not generate such fine grained values (some systems will only generate, 1 millisecond resolution stamps).
For backward compatibility, the default resolution is 1 millisecond for internally generated stamps, and whatever resolution is provided when stamps are read from an external source. When printed, the default print format includes 3 millisecond digits, but a separate print generator can produce higher resolution strings (in 18.1).

The Timestamp class has been enhanced to support dates before 1.1.1970 and after 2036, which are typical restrictions of systems which represent times as "seconds since the epoch" in a 32bit integer. Expecco timestamps handle values before and after those dates, although no calendar adjustments are done for timestamps before the julian/gregorian switch (i.e. this affects weekdays, leap years, etc. before 1582, which are very unlikely to be encountered in technical environments ;-) ).
Overview: More Info
Reference: Class Documentation

TimeDuration[edit]

Represents an amount of time, to represent time intervals in millisecond resolution (18.1: sub-millisecond resolution). When reading, a unit-specifier character is allowed to specify milliseconds (ms), seconds (s), minutes (m), hours (h) or days (d).

For example, "1h 20s" specifies 1 hour and 20 seconds.


Overview: More Info
Reference: Class Documentation

Delay[edit]

Utility providing delay functions. Most useful are (in Smalltalk):

Delay waitForSeconds: n

and

Delay waitUntil: timestamp

or (in JavaScript) "Delay.waitForSeconds(n)" and "Delay.waitUntil(timestamp)".
Reference: Class Documentation

Filename[edit]

Represents names of files in the filesystem. Provides many useful query functions for file size, access- and modification time, parent directory, children, path queries etc.
Most Useful API: Filename API Functions
Reference: Class Documentation

Encoders / Decoders[edit]

The CharacterEncoder class provides a number of functions to encode/decode strings into various other encodings; for example, to read an ISO 8859-7 encoded string, use:

    decodedString := (CharacterEncoder encoderFor:#'ISO8859-7') decodeFrom:encodedString.

and vice versa:

    encodedString := (CharacterEncoder encoderFor:#'ISO8859-7') encodeFrom:originalString.

By the time of writing, the following encodings are supported (many are aliases of others):

adobe-fontspecific ansi_x3.4-1968 arabic ascii ascii7 asmo-708 big5 cns11643 cp-037 cp-37 cp-437 cp10000 cp1250 cp1251 cp1252 cp1253 cp1254 cp1255 cp1256 cp1257 cp367 cp437 cp878 cyrillic ebcdic ebcdic-037 ecma-114 ecma-118 gb2312 gb2312.1980 gb2312.1980-0 greek hangul hebrew ibm-367 ibm-437 ibm-819 ibm-cp367 ibm-cp437 ibm-cp819 iso-10646-1 iso-8859-1 iso-8859-10 iso-8859-11 iso-8859-13 iso-8859-14 iso-8859-15 iso-8859-16 iso-8859-2 iso-8859-3 iso-8859-4 iso-8859-5 iso-8859-6 iso-8859-7 iso-8859-8 iso-8859-9 iso-ir-100 iso-ir-101 iso-ir-109 iso-ir-110 iso-ir-126 iso-ir-127 iso-ir-138 iso-ir-144 iso-ir-148 iso-ir-157 iso-ir-203 iso-ir-6 iso10646-1 iso10646_1 iso646-us iso8859 iso8859-1 iso8859-10 iso8859-11 iso8859-13 iso8859-14 iso8859-15 iso8859-16 iso8859-2 iso8859-3 iso8859-4 iso8859-5 iso8859-6 iso8859-7 iso8859-8 iso8859-9 iso8859_1 iso8859_10 iso8859_11 iso8859_13 iso8859_14 iso8859_15 iso8859_16 iso8859_2 iso8859_3 iso8859_4 iso8859_5 iso8859_6 iso8859_7 iso8859_8 iso8859_9 java javaText jis0201 jis0208 jis0212 jisx0201.1976-0 jisx0208 jisx0208.1983-0 jisx0208.1990-0 johab koi7 koi8-r koi8-u ksc5601 latin-1 latin-10 latin-2 latin-3 latin-4 latin-5 latin-6 latin-7 latin-8 latin-9 latin-celtic latin1 latin10 latin2 latin3 latin4 latin5 latin6 latin7 latin8 latin9 mac-arabic mac-centraleurope mac-centraleuropean mac-croatian mac-cyrillic mac-dingbats mac-farsi mac-greek mac-hebrew mac-iceland mac-japanese mac-korean mac-roman mac-romanian mac-symbol mac-thai mac-turkish macarabic maccentraleurope maccentraleuropean maccroatian maccyrillic macdingbat macdingbats macfarsi macgreek machebrew maciceland macintosh macjapanese mackorean macroman macromanian macsymbol macthai macturkish microsoft-ansi microsoft-arabic microsoft-baltic microsoft-cp1250 microsoft-cp1251 microsoft-cp1252 microsoft-cp1253 microsoft-cp1254 microsoft-cp1255 microsoft-cp1256 microsoft-cp1257 microsoft-cp437 microsoft-cyrillic microsoft-easteuropean microsoft-greek microsoft-hebrew microsoft-turkish ms-ansi ms-arabic ms-baltic ms-cp1250 ms-cp1251 ms-cp1252 ms-cp1253 ms-cp1254 ms-cp1255 ms-cp1256 ms-cp1257 ms-cp367 ms-cp437 ms-cp819 ms-cyrillic ms-default ms-easteuropean ms-ee ms-greek ms-hebrew ms-oem ms-turkish next nextstep sgml thai us-ascii utf-16b utf-16be utf-16e utf-16le utf-8 utf-8-mac utf16b utf16be utf16l utf16le utf8 utf8-XML utf8-mac windows-1250 windows-1251 windows-1252 windows-1253 windows-1254 windows-1255 windows-1256 windows-1257 windows-latin1

For base64 encoding, refer to the Base64Coder class, or use the string messages: base64Encoded and base64Decoded.

For utf8 encoding/decoding, use the string messages: utf8Encoded and utf8Decoded.

CTypes[edit]

These types describe C-data structures. They are needed when data is exchanged with DLL functions or is to be read/written in binary from/to a file.

CTypes are usually parsed from a string using the C Parser, which is invoked by expecco when a type definition starts with a comment like "/* C: */". But of course, the C-Parser can also be invoked explicitly via a workspace or programmatically via an elementary block. The following example would create a C-type from a structure definition string (in JavaScript code):

    t = CParser.parseDeclaration("
        struct s {
            unsigned long DPR_Address;
            unsigned long DPR_Length;
            unsigned long PortAddress;
            struct p {
                unsigned char ProtocolID;
                union u {
                    uint16 dpInOutAreaSize;
                    uint16 dpInAreaSize;
                } Parameter;
            } Protocol[4];
        };
    ");

the type in variable "t" can then be instantiated by sending it a "new" message:

    datum = t.new();

or with:

    datum = t.malloc();

The difference is that "new" allocates the bytes in the regular garbage collected expecco memory (which may move objects), whereas "malloc" allocates in pinned (not moving) memory on the C heap. Thus references to such malloc'd memory can be passed to DLL code which wants to keep a reference to it, whereas C-data allocated with new can only be used temporarily by C code and will vanish sooner or later after the call returns.

You can inspect the created instance via the inspector (evaluate: "datum.inspect()").

Individual fields of the c-datum are accessed via the following functions:

  • datum.memberAt(fieldName)
    retrieves a field. If the field is a composite datum (array or structure), a new datum is extracted (i.e. a structure copy is made).
  • datum.at(index0Based)
    retrieves an indexed array element as a copy.
  • datum.memberAtPut(fieldName, value)
    stores a field. If the field is a composite datum (array or structure), the whole value is copied.
  • datum.atPut(index0Based)
    stores an indexed array element.
  • datum.refMemberAt(fieldName)
    creates a pointer to a field. This allows for subfields to be modified.
  • datum.refAt(index0Based)
    creates a pointer to an array element.

notice that in JavaScript, indexed access can be written more convenient as "datum[index0Based]" and that member field access can be written as "datum.fieldName".

For the full protocol of CDatum, please refer to the class code in the Class Documentation.
Additional information is also found in the Datatype Element/en#CTypes documentation.

Additional Standard Objects for JavaScript[edit]

In order for a "well known" environment to be provided to those who know JavaScript, but do not know Smalltalk, mimicri classes (Math) and mimicri protocol has been added to the underlying Smalltalk system. These functions simply call corresponding Smalltalk functions and only exist to provide a JavaScript compatible API which is described below. Notice that this represents only a small fraction (less than 1/100th) of the real functionality of the base system. In order to find out more about all existing classes, you should open a class browser and navigate through the system yourself. There is also a more detailed introduction to the basic classes found in the Smalltalk/X Base Classes Documentation.

Math[edit]

Constants[edit]
  • E
    Euler's constant, the base of the natural logarithm (approximately 2.718).
    Same as "Float e" in Smalltalk.
  • LN10
    Natural logarithm of 10 (approximately 2.302). In Smalltalk, you can use "Math LN10".
  • LN2
    Natural logarithm of 2 (approximately 0.693).
    Same as "Float ln2" in Smalltalk.
  • LOG10E
    Base 10 logarithm of E (approximately 0.434)
  • LOG2E
    Base 2 logarithm of E (approximately 1.442)
  • PI
    Pi (approximately 3.14159).
    Same as "Float pi" in Smalltalk.
  • SQRT1_2
    Square root of 1/2 (approximately 0.707)
  • SQRT2
    Square root of 2 (approximately 1.414).
    Same as "Float sqrt2" in Smalltalk.
Min & Max[edit]
  • max (number1 , number2,...)
    Returns the largest of 0 to 5 arguments
  • min (number1, number2,...)
    Returns the smallest of 0 to 5 arguments
Miscellaneous[edit]
  • abs (aNumber)
    Returns the absolute value of aNumber
  • binco (n, k)
    Returns the binomial coefficient C(n,k) (n over k, choose k from n)
  • ceil (aNumber)
    Returns the smallest integer greater than or equal to aNumber
  • exp (aNumber)
    Returns EaNumber, where aNumber is the argument, and E is Euler's constant, the base of the natural logarithm
  • fac (aNumber)
    Returns the factorial of aNumber
  • floor (aNumber)
    Returns the greatest integer less than or equal to aNumber
  • gcd (a, b)
    Returns the greatest common divisor of a and b
  • log10 (aNumber)
    Returns the log base 10 of aNumber
  • log (aNumber)
    Returns the log base E of aNumber
  • pow (base, exp)
    Returns base to the exponent power, that is, baseexp
  • random
    Returns a pseudo random number between 0 and 1
  • round (aNumber)
    Returns the value of aNumber rounded to the nearest integer
  • sqrt (aNumber)
    Returns the square root of aNumber
Trigonometric[edit]
  • acos (aNumber)
    Returns the arccosine (in radians) of aNumber
  • asin (aNumber)
    Returns the arcsine (in radians) of aNumber
  • atan2 (x, y)
    Returns the arctangent of the quotient of its arguments (in radians)
  • atan (aNumber)
    Returns the arctangent (in radians) of aNumber
  • cos (aNumber)
    Returns the cosine of aNumber (given in radians)
  • sin (aNumber)
    Returns the sine of aNumber (given in radians)
  • tan (aNumber)
    Returns the tangent of aNumber (given in radians)

Number[edit]

Properties[edit]
  • MAX_VALUE
    The largest representable number. The returned number is 0x7FFFFFFFFFFFFFFF, to mimicri a 64 limit on numbers for JS programs ported from other systems. In fact, expecco does not impose any such limit in integer values.
  • MIN_VALUE
    The smallest representable number. The returned number is -0x8000000000000000, to mimicri a 64 limit on numbers for JS programs ported from other systems. In fact, expecco does not impose any such limit in integer values.
  • NEGATIVE_INFINITY
    Returns the special 'negative infinity' value
  • NaN
    Returns the special 'not a number' value
  • POSITIVE_INFINITY
    Returns the special 'positive infinity' value
Methods[edit]
  • asFloat ()
    Returns a floating-point version of the receiver object. For example: "1.asFloat()" yields the floating point number: "1.0".
  • asInteger ()
    Returns an integer version of the receiver object (truncating). For example: "1.0.asInteger()" yields the integer number: "1". Alternatives are ceiling(), floor() and rounded().
  • ceiling ()
    For non-integral values, ceiling returns the smallest integer which is larger than the receiver. For integers, the original value is returned. For example: "1.4.ceiling()" yields the integer number: "2", whereas "1.ceiling()" returns "1". See also: floor() and rounded().
  • floor ()
    For non-integral values, floor returns the largest integer which is smaller than the receiver. For integers, the original value is returned. For example: "1.4.floor()" yields the integer number: "1", whereas "1.floor()" returns "1". See also: ceiling() and rounded().
  • rounded ()
    For non-integral values, rounded returns the nearest integer from rounding. For integers, the original value is returned. For example: "1.4.rounded()" yields the integer number: "1", "1.6.rounded()" yields the integer number: "2" and whereas "1.rounded()" returns "1". See also: ceiling() and floor().
  • roundTo (r)
    Rounds towards the nearest multiple of r. For example, "1.543.roundedTo(0.01)" returns "1.54" and "1.567.roundedTo(0.01)" returns "1.57". See also: ceiling(), floor() and rounded().
  • toExponential (nDigits)
    Returns a string representing the number in exponential notation, where nDigits is the number of digits to appear after the decimal point
  • toExponential (nDigits, nDigitsAfter)
    Returns a string representing the number in exponential notation
  • toFixed (nDigits)
    Returns a string representing the number in fixed notation, where nDigits is the number of digits to appear after the decimal point

Random[edit]

A built in random generator, which generates linear congruential pseudo random numbers itself. On Unix/Linux operating systems, an additional class called "RandomGenerator" uses the operating system's random generator (/dev/rand). In addition, alternative generators are available as "RandomParkMiller" and "RandomTT800". For most applications, the default generator named Random should provide a reasonable random sequence.

  • next ()
    Returns a random number between 0 and 1 (float)
  • next (n)
    Returns n random numbers between 0 and 1 (float) as an array
  • nextBoolean ()
    Randomly returns either true or false
  • nextBetween_and_ (min, max)
    Returns a random float between min and max
  • nextIntegerBetween_and_ (min, max)
    Returns a random integer between min and max (use 1 and 6, to simulate a dice)

Time / Date[edit]

Time and Date instances provide a rich protocol for all kinds of queries. The list below is only a short extract of the most used methods:

  • getDate
    Returns the day of the month (1..31)
  • getDay
    Returns the day of the week (0..6); Sunday is 0
  • getFullYear
    Returns the year
  • getHours
    Returns the hours (0..24)
  • getMinutes
    Returns the minutes (0..60)
  • getMonth
    Returns the day of the month (1..12)

String[edit]

Be reminded that the String class inherits from CharacterArray, ByteArray, SequentialCollection, Collection and Object. Thus all of those methods are also available (and useful) with strings. Use the browser or take a look at the online documentation for the full protocol.

  • charAt0 (index)
    Returns the n'th character as a Character instance object, using a 0-based indexing scheme
  • charAt1 (index)
    Returns the n'th character as a Character instance object, using a 1-based indexing scheme
  • charCodeAt0 (index)
    Returns the code of the n'th character, using a 0-based indexing scheme
  • charCodeAt1 (index)
    Returns the code of the n'th character, using a 1-based indexing scheme
  • indexOf0 (aCharacter)
    Returns the index of aCharacter, using a 0-based indexing scheme; -1 if not found
  • indexOf1 (aCharacter)
    Returns the index of aCharacter, using a 1-based indexing scheme; 0 if not found
  • lastIndexOf0 (aCharacter)
    Returns the last index of aCharacter, using a 0-based indexing scheme; -1 if not found
  • lastIndexOf1 (aCharacter)
    Returns the last index of aCharacter, using a 1-based indexing scheme; 0 if not found
  • quote()
    Wraps the receiver into double quotes (")
  • split (separator)
    Splits the string into a collection of substrings using the separator. Warning: the JavaScript split(xxx) function and the Smalltalk split: method have different semantics (and argument order)
  • substr0 (index, count)
    Extracts a substring starting at the index with the given length, using a 0-based indexing scheme
  • substr1 (index, count)
    Extracts a substring starting at the index with the given length, using a 1-based indexing scheme
  • substring0 (index1)
    Extracts a substring starting at the index, using a 0-based indexing scheme
  • substring0 (index1, index2)
    Extracts a substring between the given indices, using a 0-based indexing scheme
  • substring1 (index1)
    Extracts a substring starting at the index, using a 1-based indexing scheme
  • substring1 (index1, index2)
    Extracts a substring between the given indices, using a 1-based indexing scheme
  • toLowerCase
    Returns a copy of the receiver with all chars in lower case
  • toUpperCase
    Returns a copy of the receiver with all chars in upper case
  • trim
    Returns a copy of the receiver with all leading and trailing white space removed
  • trimLeft
    Returns a copy of the receiver with all leading white space removed
  • trimRight
    Returns a copy of the receiver with all trailing white space removed

 

Collection (Array)[edit]

Notice that Array, OrderedCollection, ByteArray, String and others are also inheriting the Collection protocol. Thus the below listed messages can also be applied to instances of those.

  • concat (aCollection)
    Returns a new collection consisting of the concatenation of the receiver and the argument
  • every (filterFunction)
    Returns true, if "filterFunction" returns true for all elements
  • filter (filterFunction)
    Selects elements for which "filterFunction" returns true
  • forEach (function)
    Applies "function" for each element
  • join (seperator)
    Joins the strings of the receiver into a new single string using the separator
  • map (function)
    Returns a new collection collecting the results of applying "function" to each element in sequence
  • pop
    Removes and returns the last element of the collection. Modifies the receiver collection as a side effect.
  • push (value)
    Adds an element to the end of the collection. Modifies the receiver collection as a side effect.
  • reduce0 (filterFunction)
    Applies "function" against two values, reducing from left to right. Function must be declared as: f(previousValue, currentValue, index, arr). Pass 0-based indices to the filter.
  • reduce0 (filterFunction, initialValue)
    Applies "function" against two values, reducing from left to right. Function must be declared as: f(previousValue, currentValue, index, arr). Pass 0-based indices to the filter.
  • reduce1 (filterFunction)
    Applies "function" against two values, reducing from left to right. Function must be declared as: f(previousValue, currentValue, index, arr). Pass 1-based indices to the filter.
  • reduce1 (filterFunction, initialValue)
    Applies "function" against two values, reducing from left to right. Function must be declared as: f(previousValue, currentValue, index, arr). Pass 1-based indices to the filter.
  • shift
    Removes and returns the first element of the collection. Same as removeFirst. Notice that this modifies the receiver collection as a side effect.
  • slice0 (index1, index2)
    Extracts a subcollection, using a 0-based indexing scheme
  • slice1 (index1, index2)
    Extracts a subcollection, using a 1-based indexing scheme
  • some (filterFunction)
    Returns true, if "filterFunction" returns true for any element
  • unshift (arg)
    Adds an element to the beginning of the collection. Same as addFirst. Notice that this modifies the receiver collection as a side effect.

 

Transcript[edit]

The global variable "Transcript" refers to either the Transcript information window (if it is open) or the standard error stream. As such, it implements most of the stream interface functions. Most noteworthy are:

  • cr ()
    Adds a linebreak (i.e. followup text will be shown on the next line)
  • show (arg)
    Adds a textual representation of the argument, which can be a string, number or any other object.
  • show (fmt, arg,...)
    Like show, but "%i" (i=1..8) sequences in the format argument are expanded by corresponding argument strings. Up to 8 arguments are allowed.
  • showCR (arg)
    A combination of show(), followed by a linebreak.
  • showCR (fmt, arg,...)
    Like showCR, but "%i" (i=1..8) sequences in the format argument are expanded by corresponding argument strings. Up to 8 arguments are allowed.

and from the inherited write stream interface:

  • nextPut (aChar)
    Sends a single character
  • nextPutAll (aString)
    Sends a string
  • nextPutLine (aString)
    Sends a string followed by a linebreak

 

Logger[edit]

The global variable "Logger" refers to an object which will handle system log messages. This is the underlying framework's logging mechanism, not to be confused with expecco's ActivityLog. The Logger will filter messages according to their severity, which is one of {debug, info, warn, error, fatal}. Severities are ordered as debug < info < warn < error < fatal. For filtering, a severityThreshold can be set, and only messages with a higher severity will be reported.

By default, the Logger sends its output to the stderr stream, which is usually redirected into a (log-)file when expecco runs unattended as a service or controlled by command line arguments. This behavior can be changed by assigning a different logger to the global "Logger". Under Unix systems (OSX and Linux), the defaul logger can also be replaced by one which writes to the syslog facility.
The Logger supports the following protocol:

  • debug (message)
    logs a debug message
  • info (message)
    logs an info message
  • warn (message)
    logs an warning message
  • error (message)
    logs an error message
  • fatal (message)
    logs a fatal message

 

Expecco Objects[edit]

Many objects internal to expecco's execution machinery are reachable via elementary code (i.e. from within an elementary-coded action's code). A lot of useful and required information can be acquired by consulting these objects. The anchor to all those objects is the "current activity" object.

Current Activity[edit]

Within elementary code the activity instance which is executing this piece of code can be accessed via the variable "this" (in Smalltalk: "self"). For every executed action, a new activity object is created. It is usually alive during the execution only (i.e. it is destroyed and its memory reused automatically, after the block's action has finished).

The current activity object supports the following functions:

Reporting[edit]
  • error ()
    Report a defect (in the test). Stops execution.
  • error (infoString)
    Report a defect (in the test). Stops execution.
  • fail ()
    Report a failure (in the SUT). Stops execution.
  • fail (infoString)
    Report a failure (in the SUT). Stops execution.
  • inconclusive ()
    Report an inconclusive test. Stops execution.
  • inconclusive (infoString)
    Report an inconclusive test. Stops execution.
  • success () OBSOLETE - see activitySuccess below
    Finishes the current activity with success.
  • success (infoString) OBSOLETE - see activitySuccess below
    Finishes the current activity with success.
  • activitySuccess ()
    Finishes the current activity with success. Actually not needed, because simply returning from an action's execute method is semantically equivalent (however, this allows for early successful finish). Due to the more descriptive name, use this function instead of "success()".
  • activitySuccess (infoString)
    Finishes the current activity with success.
  • pass ()
    Finishes the current test case with success. Notice: this aborts the current activity and all of the callers up to the test case level. This behavior is different from activitySuccess's behavior.
  • pass (infoString)
    Finishes the current test case with success.

 

Logging[edit]
  • logData (data)
    Adds data to the activity log.
  • logDataFile (fileName)
    Adds a file attachment to the activity log.
  • logError (messageString)
    Adds a error message to the activity log.
  • logError (messageString, detail)
    Adds a error message to the activity log.
  • logWarning (messageString)
    Adds a warning to the activity log.
  • logWarning (messageString, detail)
    Adds a warning to the activity log.
  • logInfo (messageString)
    Adds an info message to the activity log.
  • logInfo (messageString, detail)
    Adds an info message to the activity log.
  • logImageInfo (messageString, image)
    Adds an info message with an image (screendump) to the activity log. There is an option in the report generator to include those in the generated pdf-report.
  • alert (messageString) (1.8.1)
    Adds a warning message to the activity log, and also shows a DialogBox, which has to be confirmed by the operator. The dialog box and confirmation can be disabled by a settings flag in the "Execution - Log -Settings" dialog (by default, it is disabled).
  • warn (messageString) (1.8.1)
    Same as alert() (for Smalltalk compatibility).

Notice the similarity and difference between "error()" and "logError()": both actually create a log-data entry, but "error()" stops the execution, whereas "logError()" proceeds (maybe, the naming is a bit confusing here...)

 

Execution[edit]
  • call (calledActionName)
    Calls another action without any input values. The called action is specified by the "calledActionName" parameter.
  • call (calledActionName, arg...)
    Calls another action with argi input value(s). The called action is specified by the "calledActionName" parameter. Up to 8 arguments are supported.
  • call_with (calledActionName, inPinValues)
    Calls another action passing input values as a collection of values. The called action is specified by the "calledActionName" parameter.
  • call_into (calledActionName, funcReceivingValues)
    Not passing any input values (similar to call), for actions with multiple output values or which write multiple values.
  • call_with_into (calledActionName, inPinValues, funcReceivingValues)
    Similar to the above, for actions with multiple output values or which write multiple values.

The called action is specified by the "calledActionName" parameter which must be one of:

  • a UUID (the called action-block's version- or function-ID),
  • a UUID-string (the called action-block's version- or function-ID),
  • a name-string (the called action-block's name),
  • a direct reference to the action-block object.

Although giving a simple name-string is propably the easiest way to specify the action, it may lead to an error, if multiple actions are found with the same name (which never happens, if a UUID is used). If a UUID is used, this can be either the action's functional- or version-ID. If the version-ID is given, that particular action is called. If a functional-ID is given, the first action which is found to have a matching functional-ID is called. The version-ID is specific, but intolerant to reimports or changes in the called action. Therefore, you should propably use the functional-ID, if you do not want to use the action's name.
In most "normal" situations, the action's name is unique and good enough.

There is an automatic mechanism, to deal with renaming, in that constant action names are found and replaced. Also, such action references are found via the "References" tree menu item and you'll get a warning if such an action is renamed or removed.

The "inPinValues" argument can be either a vector (array) or a dictionary (mapping pin-name to value) of values to be passed to the input pins of the called action. An input value of "Void" ("void" in JavaScript) will be treated like an unconnected pin.

The first variants (without the "into:" argument) return the value of the first output pin (or nil, if there are no output pins). If the output is written multiple times, an error is reported.

The other variant expects a "funcReceivingValues" argument, which must be a JS-function or Smalltalk-Block. This function/block is called with the output pin values as arguments. For each pin, one collection containing all values written to the corresponding pin is passed to it as argument (a collection of values is passed, because the pins could be written multiple times by the action).

For example from a Smalltalk coded action, the "Arith [MinMax]" action (which has 2 outputs) can be called with:

self
    call:"Arith [ MinMax ]" 
    with:{ addend1 . addend2 } 
    into:[:minOutValues :maxOutValues | 
       ... do something with the min/max values ...
       min := minOutValues first.
       max := maxOutputValues first.
    ]

with JavaScript code, the above would be written as:

this.call_with_into("Arith [ MinMax ]",
                          [ addend1 , addend2 ], 
                          function (minOutValues , maxOutValues) { 
                             // ... do something with the min/max values ...
                             min = minOutValues.first();
                             max = maxOutputValues.first();
                          })

or, using a lambda function, as:

this.call_with_into("Arith [ MinMax ]",
                          [ addend1 , addend2 ], 
                          (minOutValues , maxOutValues) => { 
                             // ... do something with the min/max values ...
                             min = minOutValues.first();
                             max = maxOutputValues.first();
                          })

In most cases, this callback function would simply store the output values into local variables, as in:

execute
   |.. minResult maxResult ..|
   ...
   self 
       call:'Arith [ MinMax ]' with:#(10 20)
       into:[:mins :maxes |
           minResult := mins first.
           maxResult := maxes first.
       ].
   ...
   do something with minResult and maxResult
   ...

Exceptions in the called action can be handled in the calling action:

    ...
   [
       result := self call:'Arith [ Quotient ]' with:{ dividend . 0}.
   ] on:ZeroDivide do:[:ex |
       Transcript showCR:'a division by zero occurred'.
   ].
   ...

Notice, that a test-fail verdict is reported as TestFail, which is a subclass of Exception, whereas a test-error verdict is reported as TestError, which is a subclass of Error. Thus, to handle both failure and error in one handler, you may write: ...

   [
       result := self call: ...
   ] on:(Error, Exception) do:[:ex |
       Transcript showCR:'either error or fail'.
   ].
   ...

or in JS: ...

   try {
       result = call( ... )
   } catch(Error + Exception) {
       Transcript.showCR("either error or fail");
   }
   ...
Reflection, Information, Queries and Accessing[edit]
  • activityCreator ()
    The triggering activity (i.e. the caller). Except for special tricks, this is only useful to generate nice print messages (such as "activity foo, called from bar".
  • activityProcess ()
    The thread executing the activity
  • blockDescription ()
    The definition of the activity (i.e. the tree definition item of the action's step)
  • environment ()
    The currently valid environment variables of the compound step which contains this action, or (if none) of the test plan in which this action executes (see "Environment Access" below)
  • environmentAt (anEnvironmentVarName)
    The value of an environment variable (in the above environment)
  • environmentAt_put (anEnvironmentVarName, value)
    Changing the value of an environment variable (in the above environment)
  • inputValueForPin (pin)
    The value of a certain pin
  • inputValueFor (pinName)
    The value of a certain pin by name. Reports an error, if there is no such pin, the pin is unconnected or has no value.
  • inputValueFor_ifAbsent (pinName, ifAbsentValue)
    The value of a certain pin by name. Returns "ifAbsentValue" if there is no such pin, the pin is unconnected or has no value.
  • inventory ()
    The inventory of the activity
  • nameOfStep ()
    The name of the corresponding step of the activity
  • resources ()
    All resources which have been acquired for the activity (as specified in the resource-definition of the action). Retrieves a collection of Resource objects, as described below.
  • resourcesForId (skillId)
    Resources which have been acquired for one particular skill-requirement. The argument skillId corresponds to the name as specified in the first column of the resource definition of the action). Notice, that a collection of Resource objects is returned - even if only one resource has been acquired.
  • step ()
    The corresponding step of the activity

 

Environment Access[edit]

Starting at every compound activity, a nested stack of outer environments is present, ending in a top project environment. The following activity methods provide access to some of them. This can be used to automatically setup environment variables from a configuration or when autodetecting a system-under-test's configuration and leaving the values in a defined outer environment. Depending on the intended lifetime of those values, one of the following environments should be chosen as target of such operations:

  • environment ()
    the environment of the compound in which the current elementary action is executing (as a step)
  • executorEnvironment ()
    an environment which is only present during a single testplan execution
  • projectEnvironment ()
    an environment which is attached to the current test suite (but not persistent when the suite is saved/loaded from an ets file)
  • browserEnvironment ()
    an environment which is present while the expecco application is running (i.e. the current session). (it is not made persistent)

None of the following can be made persistent directly. However, every test suite, test plan and compound block provide a description of initial values and how to compute them in their environment description, which is edited using the Environment Editor . Also, such descriptions can be saved and loaded separately from the test suite via the "Load/Save Parameter Set" menu functions of the "File" menu.

 

Event Sending[edit]

Events can be pushed onto an event queue from either actions found in the standard library or via API calls to the current activity (also from bridged elementary actions). The global event handler should be already running (and especially, a handler should be registered) before the first event is sent. Otherwise, the event might be ignored or an error is raised. A sample suite is found in "demos/d62_Event_Queue_Demos".

  • pushEvent (eventOrPayloadData)
    pushes an event onto the global event handler's event queue. The argument may be either an instance of Event, or the payload (which is packed into an Event instance with type #default). Raises an error, if no global event handler process is running.
  • pushEventType_data (eventTypeSymbolOrNil, payloadData)
    pushes an event of type (or #default) onto the global event handler's event queue. Raises an error, if no global event handler process is running.

 

Executor Functions[edit]

The executor (as returned from the current activity via the "executor()" function described above) is responsible to execute steps in a network and elementary code in a controlled fashion. In addition to sequencing and assigning activities to a particular execution thread, it also cares for the activityLog and error handling. Usually, you do not need to interact with an executor directly.

  • executorsWorkingDirectory ()
    Temporary working directory. This is removed after the execution (i.e. useful only for very temporary files, which are generated in an action and read in another)
  • testplan ()
    The currently executed testplan (nil if a block test blocks are executed)
  • project ()
    Project (testsuite or library) in which the test is executed

 

Resource Functions[edit]

Resources can be assigned to an action as a "required resource", and the execution will be synchronized with the allocation and reservation of a particular resource as acquired from an inventory. Once allocated, the resources are available to the action via resources() or resourcesForId() functions:

  • resources ()
    retrieves the set of resources allocated for the action. The collection is keyed by the name as given in the "Resources" tab, the associated value is a resource instance providing the concrete values of the resource's skills.
  • resourceForId (idString)
    retrieves the resource allocated for the idString as given in the "Resources" tab, the returned value is a resource instance providing the concrete values of the resource's skills.

Resource instances respond to the following protocol:

  • name ()
    The name of the resource
  • skillNamed (nameOrUUID)
    Fetch a concrete skill-value of that resource. The argument can be either a skills name, or the functional-UUID of the skill element (in the project tree).
  • skillAttributeNamed (name)
    Fetch a concrete skill-attribute-value of the resource

 

Test Suite (Project) Functions[edit]

Every activity, step or action has a reference to its containing project (or library, if imported). The direct enclosing project/library is returned by the "project()" function, the top level project (i.e. the loaded suite) via the "topProject()" call. Projects, Test Suites and Libraries are all the same kind of object (i.e. any project can be used as a test suite or imported into another as imported library).

  • documentation ()
    The documentation or nil
  • functionalId ()
    A unique ID which defines this testSuite independent of its name (remains constant after change)
  • projectsWorkingDirectory ()
    Project directory - i.a. all attachments. This directory is created when a project file is loaded and is deleted when expecco is closed. Do not use it for permanently needed files.
  • modelLanguageStringFor (aString)
    translates a string according to the model-language translation table
  • versionId ()
    A unique ID which defines this testSuite in exactly this version. (changed with any edit operation)
  • elementWithName (aString)
    Retrieve an element by name
  • elementWithId (aUUID)
    Retrieve an element by its id
  • elementWithFunctionalId (aUUID)
    Retrieve an element by its version id
  • typeNamed (aTypeName)
    Retrieve a datatype. To instantiate a type, write "(self project typeNamed:'Foo') new".

 

Testplan Functions[edit]

  • documentation ()
    The documentation or nil
  • name ()
    The name of the Testplan
  • tags ()
    A collection of tags of the testplan
  • functionalId ()
    A unique ID which defines this testplan independent of its name (remains constant after change)
  • versionId ()
    A unique ID which defines this testplan in exactly this version. (changed with any edit operation)

 

Step Functions[edit]

  • name ()
    The name of the Step
  • tags ()
    A collection of tags of the Step
  • inputPins()
    A collection with all input pins
  • inputPinForPinName(inputPinName)
    The corresponding input pin. Raises an error, if no such input pin exists.
  • inputPinForPinName_ifAbsent(inputPinName, ifAbsentValue)
    The corresponding input pin. Returns ifAbsentValue, if no such input pin exists.
  • outputPins()
    A collection with all output pins
  • outputPinForPinName(outputPinName)
    The corresponding output pin. Raises an error, if no such output pin exists.
  • outputPinForPinName_ifAbsent(outputPinName, ifAbsentValue)
    The corresponding output pin. Returns ifAbsentValue, if no such output pin exists.

 

Pin Functions[edit]

The input and output pins can be referenced in elementary code directly by their name. The name refers to the pin - NOT the passed value (this allows for the code to check, if a value is present). For indexed pins (i.e. in steps with a variable number of pins), which is a new feature in v2.1, the collection of pins is referred to by the name, and pins are accessed via an index (1..). See more about how to handle variable pins in the section below.

Common to all Pins:[edit]
  • datatype ()
    Returns the data type of the pin (Reflection-API)
Input Pins:[edit]
  • hasValue ()
    Returns true if the pin has received a value (useful to determine if there is a value if the trigger condition is "AndConnected" or "Or")
  • hasEnvironmentFreezeValue ()
    Returns true if the pin has a freezeValue from an environment
  • hasFreezeValue ()
    Returns true if the pin has a freezeValue
  • isConnected ()
    Returns true if the pin is connected (but not frozen)
  • value ()
    Returns the value of the pin. This is either the value received via a connection, or a constant freeze value, or an environment variable freeze value. Raises an error if the pin did not receive any value.
  • valueIfPresent ()
    Returns the value of a pin or nil if the pin did not receive any value. Similar to "value()", but avoids the exception.
  • valueIfAbsent (alternativeValue)
    Returns the value of a pin or the alternativeValue, if the pin did not receive any value. Similar to "value()", but avoids the exception.
  • waitForValue ()
    The pin has to be a mailbox pin. The execution of this action will be suspended until the pin receives a value.
  • waitForValueWithTimeout (seconds)
    Like above, but the execution will only wait for the specified time limit, given in seconds. The parameter is typically an integer, but can also be a fraction (e.g. 1/3) or a floating-point number (e.g. 0.3).
Output Pins:[edit]
  • isBuffered ()
    True if the pin is buffered
  • isConnected ()
    Returns true if the pin is connected
  • value (data )
    Writes the value. If the output pin is unbuffered and connected to another step's input pin, the other step's action may be triggered (if the other step's trigger condition is fulfilled). If the output pin is buffered, the datum will be passed to the connected input, when the action has finished with success.
Variable Input Pins[edit]

Starting with expecco v2.0, steps can have a variable number of input pins, if the pin's schema has the "variable pin" attribute set. To change the number of pins of a step in the diagram editor, pull the pin-handle of a placed step, to add or remove pins. In elementary code, the following API call entries (to the variable pin) are provided to deal with this situation:

  • pinName
    now refers to a collection of pins, representing the set of pins actually present at the step. In contrast regular pins, where pinName refers to a single pin.
  • pinName.numberOfVariablePins ()
    returns the number of actual pins present in the step for the (variable) schema pin named "pinName"
  • pinName.size ()
    same as the above "numberOfVariablePins"
  • pinName.inputPinAt (idx)
    returns the idx'th actual pin of the schema pin named "pinName". Notice this retrieves the pin for reflection queries, NOT the pin value. To get value or query if it has a value, call valueAt:() / hasValueAt(). Pin indexing starts with 1.
  • pinName.at (idx)
    same as inputPinAt(idx)
  • pinName.hasValueAt (idx)
    true if the idx'th variable pin is connected and has received a value. Indices start with 1.
  • pinName.values ()
    Retrieves the values passed to the pins as an array. The array contains nil entries for disconnected pins and those without a value
  • pinName.valuesPresent ()
    Retrieves the values as present; skips unconnected pins or those without a value.
  • pinName.valueAt (idx)
    to get the idx'th pin's value; raises an error if no value was given or pin is not connected
  • pinName.valueIfPresentAt (idx)
    to get the idx'th pin's value, or nil if no value was given or pin is not connected
Variable Output Pins[edit]

Starting with expecco v2.1, steps can also have a variable number of output pins, if the schema has the "variable output pin" attribute set. To change the number of pins, pull the pin-handle of a placed step, to add or remove pins. In elementary code, the following API call entries (to the variable pin) are provided to deal with this situation:

  • pinName.numberOfVariablePins ()
    returns the number of actual pins present in the step for the (variable) schema pin named "pinName".
  • pinName.outputPinAt_value (idx, value)
    write a value to the idx'th output pin. Index starts with 1.
  • pinName.outputPinAt (idx)
    returns the idx'th actual pin of the schema pin named "pinName". Notice that this retrieves the pin; to write an output value to it, call value(v) on the returned pin.
  • pinName.at (idx)
    same as outputPinAt(idx)
  • pinName.outputPins ()
    returns a collection of all actual pins present in the step for the (variable) schema pin named "pinName".

 

Datatype Functions[edit]

A datatype can be retrieved by asking a pin for its datatype using the datatype () function. This is sometimes useful in elementary code, in order to instantiate a new value (given the pin) or to ask for details of the type.

  • name ()
    The name of the Datatype
  • isAnyType ()
    true if the datatype is the builtin "Any" type
  • isArrayType ()
    true if the datatype is an "Array" type (with any element type)
  • isCompoundType ()
    true if the datatype is a compound (structure) type
  • isEnumType ()
    true if the datatype is an enumeration type
  • isPrimaryType ()
    true if the datatype is any builtin primary type
  • isRangeType ()
    true if the datatype is a subrange of another type
  • isTupleType ()
    true if the datatype is a tuple type
  • isUnionType ()
    true if the datatype is a union type
  • isStringPrimaryType ()
    true if the datatype is the builtin "String" primary type

Enum Type Functions[edit]

  • values ()
    returns an array of enum-value names (Symbolic)
  • integerValueOf (elementName)
    returns the associated integer value. (new in expecco 2.8)

Compound Type Functions[edit]

  • namedFieldDescriptions ()
    returns an array of descriptions of the named fields (if any)
  • indexedFieldDescriptins ()
    returns a description of indexed fields (if any)

Each field description can be asked for the "fieldName()", "fieldType()" and "defaultValue()".

Primary Type Functions[edit]

  • typeClass ()
    returns the underlying Smalltalk class, of which values are instances of.

Range Type Functions[edit]

  • baseType ()
    returns the underlying base type. Typically this will be the Integer primary type.
  • minValue ()
    returns the minimum value of the range
  • maxValue ()
    returns the maximum value of the range

 

Functions for Any Tree- or Diagram Element[edit]

These include: BlockDescriptions, Types, TestPlans, TestCases, Steps, Connections, Pins, Annotations etc.

  • versionId
    A globally unique identifier (GUID or UUID) which is changed with every modification of the item
  • functionalId
    A globally unique identifier (GUID or UUID) which is assigned once-and-for-all to the item
  • id
    alias for versionId().
  • tags
    A collection of tags.
  • isAnnotation
    true, if it is an annotation (in a diagram)
  • isStep
    true, if it is a step (in a diagram)
  • isConnection
    true, if it is a connection (in a diagram)
  • isBlockDescription
    true, if it is a block description (in the tree)
  • isAttachment
    true, if it is any attachment (in the tree)
  • isFileAttachment
    true, if it is a file attachment (in the tree)
  • taggedValueAt (aKey)
    To retrieve a tagged value. These are usually preserved when objects are transported to/from other systems.
  • taggedValueAt_put (aKey, anObject)
    to change a tagged value. Only a limited set of datatypes are supported: anObject must be a String or a Number.
  • propertyAt (aKey)
    to retrieve an expecco property. These are expecco-internal properties. These are usually not recognized by other systems.
  • propertyAt_put (aKey, anObject)
    to set an expecco property. These are usually not recognized by other systems. Only a limited set of datatypes are supported: anObject must be a String or a Number.

 

Groovy Elementary Blocks[edit]

Groovy is an open source interpreter and compiler for a Java like scripting language which runs on top of the Java Virtual machine. For details, visit "groovy-lang.org" and especially the Groovy Documentation Portal.

Code written as a Groovy elementary block is not executed directly by expecco. Instead, the code is forwarded to a Groovy shell which runs inside a Java Virtual Machine (JVM). This may be a local JVM, whose sole purpose is to provide additional utility functions or which provides an interface to the actual system under test (SUT), or it may be the JVM which executes the tested application and is running physically on the SUT.

By using Groovy blocks, expecco's basic black-box test functionality is extended to include powerful gray- and white-box tests. You can access the internals of objects inside the SUT, call its functions and even define new classes and functions and call them. Groovy is especially useful, if you have to install callback- or hooks inside the SUT, which interact back to expecco (e.g. to collect SUT-internal events and allow expecco to read those from the SUT). However, you should keep in mind that your tests may become affected by changes in the SUT and needs to be modified if interfaces change (which is a general property of white box tests, not a limitation of expecco).

The machinery to interact with a JVM (and therefore to execute Groovy code blocks) requires the Java Bridge plugin, which is now (rel18.1) part of the basic expecco package. Groovy itself is available for free as a jar class library, which is loaded with or into the target application. The Java Bridge plugin already includes a prepackaged Groovy interpreter which can be loaded transparently into the SUT application whenever a Groovy block is executed. So except for the setting of the JVM path, no further setup or installation is required.

Why use Groovy Elementary Blocks?[edit]

The Java Bridge and especially Groovy code is very useful when testing GUI frameworks, communication protocols or machine control soft- and hardware which is written in Java. Especially, if you have to call functions internal to the SUT in order to trigger actions or if you need to be informed about state changes via listeners or callbacks, Groovy is the perfect tool for the task. Note that calls into the JVM are relatively straight forward, using the method forwarding mechanism, even from Smalltalk code. However, callbacks and listeners, which usually have to be implemented as instances of special derived classes of a an abstract framework class, are hard to realize without Groovy.

Without Groovy, you would have to add such classes to the software inside the SUT - e.g. by adding them to the tested software's jar packages, or by adding extra jar packages. In addition, you'd have to provide a communication mechanism (socket, pipe or file interchange), by which your test action is able to retrieve this information.

What is the Difference between a regular (expecco-) JavaScript Block and a Groovy Block?[edit]

In one sentence: expecco JavaScript actions are executed inside expecco, Groovy actions inside the SUT (System Under Test) or any other external JVM (Java Virtual Machine).

More detailed:

Expecco's JavaScript actions are actually alternative syntactic forms of Smalltalk actions. When executed, they run inside the expecco executable and have access to any class or object inside expecco itself, or those which are passed in via input pins or environment variables.

On the other hand, Groovy actions are "injected" (eg. downloaded) into a Java VM and execute in that object space. They cannot directly access expecco classes or objects, but instead can access all public classes and objects within the system under test directly. By means of proxy objects (which are forwarding operation-calls), expecco can deal with remote objects almost transparently: these can be returned from a Groovy action to expecco, and later be used again as arguments to other Groovy actions; i.e. they can be passed around via output- and input pins, stored in variables and inspected.

What is the Difference between a Groovy Block and a Bridged Node Block?[edit]

Groovy blocks execute inside a JVM (Java Virtual Machine) and can access any Java code (eg. classes inside a java application or inside a dynamically loaded jar-library).

In contrast, Node actions are executed inside a Node interpreter (which is not a JVM).

There are also slight differences in the syntax: Groovy is more Java-like, whereas Node blocks use pure JavaScript.

Groovy Compilation Mechanism and Performance Issues[edit]

When a Groovy action is executed for the very first time, the block's code is transmitted via the bridge to the Groovy shell on the target JVM as a string. There, the code is parsed and an anonymous jar is created, containing Java byte code. Thus, there is some initial overhead involved in both sending the code and more so in compiling the code in the JVM. However, for every followup call, the JVM will directly call the generated code and run at full Java speed (i.e. the execution speed is almost as if native Java code was executed), although there is still some overhead due to the interprocess communication (typically a few milliseconds for every call)

Using a Single Local JVM Connection / Executing Groovy Actions on the Local Machine[edit]

By default, a Groovy action is executed on a JVM which runs on the local machine; i.e. the machine on which expecco itself is running. With the execution of the very first Groovy action, a JVM is started as defined in the expecco settings dialog, then a bridge connection is established to that JVM, and the Groovy interpreter (called "GroovyShell") is loaded and instantiated on the JVM. Then a reference to that GroovyShell object is remembered and used for all followup Groovy actions which are to execute on that local JVM. This default behavior is convenient if you want to use Java support libraries for reporting, statistics or other computations, or if your SUT is running inside a local JVM, or if you want to call interface functions (protocols) which actually talk to your SUT.

Using a Particular JVM Connection / Executing Groovy on a Possibly Remote Machine[edit]

If you either need to customize the JVM startup and/or your test scenario has to talk to one or multiple remote systems (or multiple java programs within one scenario), you have to setup multiple java connections, and pass the connection to use to your Groovy actions. This can be done either via an input pin, or via an expecco environment variable. The algorithm to provide this is as follows:

  • if the action has a pin named "groovy", and it is connected, that pin's value is used as GroovyShell handle
  • otherwise, if the environment contains a variable named "GROOVY", that variable's value is used
  • otherwise, a new GroovyShell is instantiated and used, on the JVM which is given by:
    • if the action has a pin named "java", and it is connected, that pin's value is used as Java Bridge handle
    • otherwise, if the environment contains a variable named "JAVA", that variable's value is used
    • finally, otherwise a local JVM connection is used (a shared singleton connection).

This scheme makes the most common case easy, where only one single local JVM is required, but still allows the flexibility to handle multiple JVMs and even multiple different GroovyShell instances within each JVM.

To use input pins, you have to create corresponding pins manually, and the pin names MUST be "java" / "groovy". You cannot use these two reserved names as regular input pin names.

Additional JAR Class Libraries[edit]

By default, the Groovy code runs inside a JVM which has its classPath initialized from the preferences. These class path preferences can be changed in the "Settings" ► "Project Management" ► "Groovy Class Path" settings dialog. Additional jars can be added to the common classPath (before a JVM is started) by the "[Add JAR to Groovy Class Path]" action, or dynamically to a concrete already running JVM instance via the "[Add JAR to JVM]" action.

Notice that you have to restart existing groovy connections to make such changes effective (via the "Extras" ► "Debugging" ► "Shut Down Bridge Connections" menu function).

Groovy Code Snippets / Examples[edit]

Calling Existing Code in the System Under Test (SUT) via a Groovy Action[edit]

A Groovy block's code looks very similar to a regular JavaScript block's code (with some differences in the Groovy language). However, it is executed inside a JVM, which may be inside the SUT. It can therefore instantiate Java objects and call static and member functions inside the target system, if it is programmed in Java.

For example, too call a static function named "foo" in a class named "MyTestClass", use the following Groovy block:

def execute() {
    MyTestClass.foo("hello world");
}

if the class is located in another package (or especially: in one of your own), use explicit package prefixes:

def execute() {
    javax.swing.JFrame frame = new javax.swing.JFrame();
    javax.swing.JButton button = new javax.swing.JButton( "Press Me" );

    frame.add(button);
    frame.setSize(300,100);
    frame.setVisible(true);
}

or an import declaration:

import javax.swing.*;

// end of import definitions

def execute() {
    JFrame frame = new JFrame();
    JButton button = new JButton( "Press Me" );

    frame.add(button);
    frame.setSize(300,100);
    frame.setVisible(true);
}

Notice that you may not remove the comment line starting with "// end of....".
This separator pattern has been hardcoded into the Groovy machinery to separate any class and import definitions from the execution method.

Defining a New Class in the System Under Test using Groovy[edit]

Drag this image into expecco

You can define your own classes in a Groovy block:

For demonstration, let's start with a simple running example:

class TestSleep {
   static void main(String[] args) {          
      println 'Step 1'
      sleep(1000)
      println 'Step 2'
      sleep(1000)
      println 'Step 3'
   }
}

// end of definitions -- do not remove this line unless imports above is empty

def execute() {
    TestSleep.main(null);
}

Instances of new classes may be required especially to install listeners and callbacks when testing UI or server applications. A typical scenario is when you want expecco to be informed about mouse or keyboard events (MouseListener).

Usually, instances of such classes are created in one Groovy action, and passed to expecco via an output pin:

class MyClass extends Object {
    Object someState;

    def set_someState(Object newValue) {
        someState = newValue;
    }
    def get_someState() {
        return someState;
    }
    def String toString() {
        return "a very\nlong\nmultiline\na\nb\nstring\n";
    }
}

// end of local definitions

def execute() {
    outputPin.value( new MyClass() );
}

Then, additional Groovy actions take that as an input value and call the classes methods:

def execute() {
    Object myInst = inputPin.value();

    myInst.setSomeState(123);
}

or:

def execute() {
    Object myInst = inputPin.value();

    output.value( myInst.getSomeState() );
}

Defining a Listener Class in the System Under Test using Groovy[edit]

A common pattern in Java communication frameworks is to define an abstract classes or interfaces which has to be subclassed by users of the framework. This pattern is common for callbacks or event listeners. The programmer has to define a listener class, create an instance of it, and register it towards the communication framework. To interact with such a framework in expecco, you will need a listener class, which responds to callbacks from the framework and either remembers them for later queries from a testcase's activity, or by directly calling back into expecco. The later is much harder to deal with, as those callbacks may come at any time, even when the test has already finished or the test is being debugged (remember that the JVM may run independent from expecco). So synchronization issues may be hard to solve. It is much easier and recommended to implement the first approach: the listener collects incoming events, until expecco is ready to process them.

You will need an elementary Groovy block to instantiate and register the listener towards the framework similar to the conceptual code below:

// a Groovy Elementary Block

import <interface or abstract class from which to inherit>;

class MyListener extends AbstractListener {
    ArrayList eventQueue = new ArrayList();
    Object eventNotifier = new Object();

    /**
      * Called from a framework when an event occurs.
      */
    public void onExampleEvent(Object event) {
        synchronized (eventQueue) {
            eventQueue.add(event);
        }
        synchronized (eventNotifier) {
            eventNotifier.notifyAll();
        }
    }

    /**
      * Called from expecco to fetch next event.
      *
      * Of there is no event in the queue, blocks
      * until an event arrives (by means of #onExampleEvent())
      */
    public Object getNextEvent() {
        while (true) {
            synchronized (eventQueue) {
                if (! eventQueue.isEmpty()) {
                    return eventQueue.remove(0);
                }
            }
            synchronized (eventNotifier) {
                try {
                    eventNotifier.wait();
                } catch (InterruptedException ie) {
                    // Pass, try again
                }
            }
        }
    }
}

// end of local definitions

// called from expecco, to instantiate a listener
def execute() {
    var theListener = new MyListener();

    framework.registerListener ( theListener );
    outputPin.value( theListener ); // return it to expecco
}

Make sure that the above elementary code is called from your activity diagram in the initialization/setup phase, and that the returned listener reference is either remembered in an environment variable of your suite, or passed down to the actual test case via input/output pins.

Define an additional elementary block (here in JavaScript, but could also be Smalltalk or Groovy), which takes the listener as input pin and fetches the next event via getNextEvent():

// a JavaScript Elementary Block

execute() {
    var theListener;
    var event;


    theListener = inPin.value();
    event = theListener.getNextEvent();
    outpin.value ( event );
}

The above code blocks the test suite until an event arrives. It may be useful to add another method to query the number of received events, or to flush any previously received and already enqueued events.

In your test case, perform any stimulation required to make the framework generate an event, so that the listener's framework entry is called. Then, invoke the #getNextEvent() action to suspend execution until an incoming event arrives (you will probably set a time limit on that step's execution). Once this action finishes, the event is available in expecco as a reference to the real event object (remember: the event object is an object inside the remote Java machine, so what you have in expecco is a handle to that remote object).

All getters, setters and other methods of the event's implementation can be invoked from elementary code (any of the programming languages can do that, due to the message forwarding of the bridge).

For example, assuming that the event provides getEventID() and getMessageString() getters, which can be called from a Smalltalk action with:

id := theEvent getEventID. msg := theEvent getMessageString.

and from a JavaScript action or from another Groovy action witht:

id = theEvent.getEventID(); msg = theEvent.getMessageString();.

Also, the expecco object inspector has been enhanced to recognize those proxy bridge objects, and presents the internal state, inheritance and class protocol in a multitab fashion. This makes looking into those remote objects almost as comfortable as looking into local ones.

Accessing JMX MBeans[edit]

import java.lang.management.*

// end of definitions

def execute() {
    def os = ManagementFactory.operatingSytemMXBean;
    def mem = ManagementFactory.memoryMXBean;

    Transcript.showCR("OS architecture: " + os.arch.toString() );
    Transcript.showCR("MEM heapUsage  : " + mem.heapMemoryUsage.toString() );
    Transcript.showCR("POOLS:");
    ManagementFactory.memoryPoolMXBeans.each { eachPool ->
        Transcript.showCR("  pool name     : " + eachPool.name.toString() );
        Transcript.showCR("  pool peakUsage: " + eachPool.peakUsage.toString() );
    }
}

Please refer to the corresponding Java documentation for details.

Executing Jython, JRuby or other Script Code inside the JVM[edit]

Bulb.png the following example is somewhat outdated due to the new builtin support for jython (in 19.2).

The following example executes Jython (Python) code inside the JVM (please make sure that the jython jar is in your Groovy class path):

import org.python.util.PythonInterpreter; 
import org.python.core.*; 

// end of definitions -- do not remove this line unless imports above is empty

def main()
    throws PyException
{ 
    Properties p = new Properties();
    // p.setProperty("sys.path", "/Users/cg/work/exept/bridgeFramework/javaBridge/javaBin/jython2.7.0");

    p.setProperty("python.path", "<path to jython2.7.0/Lib>");
    p.setProperty("python.home", "<jython2.7.0>");

    Object props = System.getProperties();
    // String[] args = new String[0];
    String[] args = { "" };
    PythonInterpreter.initialize(props, p, args);

    PythonInterpreter interp =
        new PythonInterpreter();

    System.out.println("Hello, brave new world");
    interp.exec("import sys");
    interp.exec("print sys");

    interp.set("a", new PyInteger(42));
    interp.exec("print a");
    interp.exec("x = 2+2");
    PyObject x = interp.get("x");

    System.out.println("x: "+x);
    System.out.println("Goodbye, cruel world");
}

def execute() {
    main();
}

Of course, you can also write the "interp" instance to an output pin, and remember it somewhere or pass it to other actions. Then, other actions can directly invoke the "exec(...)" function, without instantiating a new interpreter.

Groovy Datatype Limitations[edit]

Integer types[edit]

As Groovy dynamically compiles to plain Java bytecode and executes on top of a regular JVM, the integer range is limited to 32bit (64bit for "long int" types). No automatic conversion between small and large integers is performed. If required, you will have to use a BigNum package and/or cast int to long int. There is currently no automatic conversion or transparent support to pass large numbers from expecco to Groovy and vice versa. Integers passed from Java to expecco will be represented as instances of the Integer type there.

Limited Object Conversion to/from Groovy[edit]

Some limited form of object conversion is automatically performed when passing expecco's Smalltalk objects to Groovy, and back when returning values from Groovy. This conversion especially affects values passed to/from pins of a Groovy action.

The following table summarizes the conversion process:

from Smalltalk to Java and from Java to Smalltalk
String String String
Integer (up to 32/64 bit) int, long int, boxed Integer Integer
Float, Double float, double; boxed or unboxed Float
(Notice that Smalltalk Floats have double precision)
Fraction float, double; boxed or unboxed (danger alert: precision may be lost) Float
(Notice that Smalltalk Floats have double precision)
Boolean Boolean Boolean
Array of any above ArrayList of any above Array of any above
other Smalltalk Object(!) - -

(!) does not work, yet: at the time of writing, Smalltalk objects other than the basic types listed above cannot be passed to Java. However, the reverse direction is possible, and Smalltalk includes a mechanisms to handle (and even inspect) remote Java objects.

No Smalltalk Classes, No Smalltalk Objects in Groovy[edit]

Of course, no Smalltalk class can be used directly in Groovy code. And Smalltalk objects can only be passed as opaque handles to/from Groovy as described above. However, all Java classes are at your hands now! Some limited interaction with expecco and underlying Smalltalk objects is possible via the "eval()" and "perform()" API described below.

However, in general: if more complex objects need to be interchanged, this must be either done by converting them to an array (of objects), possibly an array of arrays, which is passed to a Groovy "interface function" first, which instantiates the required Java object(s). Or, alternatively to some ASCII representation (XML or JSON, for a more lightweight approach) and convert this back and forth.

Handling of Java (and Groovy) Objects in expecco/Smalltalk[edit]

Java Object References (Handles)[edit]

Groovy elementary function can return references to Java objects via output pins. Because the actual object is located inside a JVM (i.e. outside of expecco), these references are treated like opaque handles by the underlying Smalltalk runtime system. However, a mechanism is provided to allow for almost transparent access to the referenced object's private slots, its classes' static slots, its class hierarchy and method information. The object inspector is able to present that information in a convenient way, similar to how Smalltalk objects are presented. Also methods of the Java object can be called and values returned to expecco.

Message Forwarding[edit]

Smalltalk and JavaScript code can send regular messages to Java objects inside a remote JVM. This is possible via the Smalltalk "doesNotUnderstand:" mechanism, which intercepts any call, sends a message to the real object (via the Java Bridge), awaits an answer and returns the result, which is usually a reference to another Java object.

All of this is transparent to the programmer of the elementary code, except for the fact that Java method naming is different from Smalltalk. And of course a performance penalty, due to the interprocess communication, marshalling overhead and dynamic lookup via reflection on the Java side.

Method Name Conversions[edit]

When a message is sent from Smalltalk code to a Java object, a message translation mechanism similar to the Smalltalk <-> JavaScript mechanism is used:

foo                  -> foo()
foo:arg              -> foo(arg)
foo:a1 _:a2 ... _:aN -> foo(a1, a2, ... aN) where the "_" are arbitrary strings.

thus, to call a Java object's "installHandler(arg1, arg2, arg3)" function, you should write in Smalltalk:

javaObject installHandler:arg1 _:arg2 _:arg3

and in JavaScript:

javaObject.installhandler(arg1, arg2, arg3)

It is obvious, that this kind of code is better written in JavaScript, being the same as a native Java call would look like.

Conversion of Dynamic Smalltalk Objects to Static Java Types[edit]

Inside expecco (which is running under a Smalltalk VM), all object references are dynamically typed. This means, that in Smalltalk, every object holds a reference to its class and in principle, any object can be passed as argument to a called function. In contrast, Java requires the type of a function argument to be declared (either as a class or interface), and the argument type(s) are part of a method's signature.

Therefore, when calling a Java function from Smalltalk, the correct function must be found by reflection at execution time.

For example, if the Java object provides two methods as in:

public void foo(Integer arg) { ... }
public void foo(Float arg) { ... }

and it is called from Smalltalk with:

aJavaObjectHandle foo: 1

then the "Integer"-argument version of foo must be called.

Whereas if called as:

aJavaObjectHandle foo: 1.0

then the "Float"-argument version is to be called.

For this, the Java Bridge side uses reflection to scan the available methods for a best fit, and transforms the passed Smalltalk argument to the best matching type before calling the actual function.

There are some rare situations, where this automatic lookup fails or finds a wrong method. For example, if same-named functions exist with both a "float" (unboxed) and a "Float" (boxed) argument, and you need to make sure which is called. In such cases, you can either use a Groovy block as mediator, which calls the desired function with an explicitly casted argument, and call the Groovy block from Smalltalk (or JavaScript) or use an explicit cast operation ("Bridge2::Cast object:o as:typeName").

Current IDE Limitations[edit]

The Smalltalk IDE has currently only limited support to extract and present Java's static type information. Currently, there is no class- and function name completion in the editor, making elementary code development for Java/Groovy less pleasant. Therefore, for complicated setups, it is a good idea to open a specialized Java development environment (e.g. eclipse) in parallel, and test the setup code there before copy-pasting it into a Groovy block.

Eclipse is also useful to find out class- and method names during development.

Groovy Code API[edit]

Groovy code supports a subset of the above activity functions, which is intended provide an interface similar to the JavaScript and Smalltalk elementary code API. Of course, technically for every API function below, the elementary code executing in the remote Java VM has to make a remote procedure call back to expecco. On the JVM side, this is done very similar to the above described remote message mechanism, when messages are sent from Smalltalk to Java/Groovy. However, due to the static type system, it is not possible to call previously unknown interfaces. Therefore, only a fixed set of API entry points is supported.

Attention / Warning (Groovy)[edit]

Unless explicitly prevented by the "mayStillDoExpeccoCalls()" function (described below), all called expecco functions listed below will ONLY work as expected WHILE the Groovy action is still ACTIVE (i.e. while inside the Groovy "execute()" function).

The reason is that expecco ensures that handlers, which are responsible for transferring information from the Groovy code back to expecco, will always be released, when the Groovy step has finished its execution. This is mostly required to prevent memory leaks in the Java VM, because the observer instance object is registered inside the JVM and would remain there forever, if not unregistered. This observer object is a proxy for Java code, which implements all the functions below and forwards them (via an IPC mechanism) to expecco. One new such object is required for every Groovy action execution and if not released, they sooner or later consume huge amounts of JVM memory. Therefore by default, the observer is released after the Groovy action execution.

In most Groovy blocks this is not a problem, except for those which install callback methods, AND those callbacks get called AFTER the Groovy block has finished (for example by another Java thread), AND the callback code calls one of those expecco interface functions. This includes the Transcript output functions, notifications and pin value writers.

Unless the observer is still around, callbacks are ignored and behave like a no-operation, if called after the step has finished. Of course, writing to a pin from within a callback AFTER the execute() function has finished does not work in any case (with or without a kept observer). The behavior in this case is undefined; currently, it is a no-operation, but it may be changed to raise an error in future versions.

The Current Activity (Groovy)[edit]

The current activity instance is accessed as "this" inside the Groovy action code (like in an elementary JavaScript block). For every executed action, a new activity object is instantiated. It is usually alive during the execution only (i.e. it is destroyed and its memory reused automatically, after the block's action has finished). In Groovy (as in JavaScript or Java), if no receiver is given for a function call, "this" is the implicit receiver. Thus the statements "this.logInfo("hello")" and "logInfo("hello")" are equivalent.

Objects are passed by Reference[edit]

Except for Strings and Numbers, objects are passed from Groovy to expecco by reference. These references can later be sent back to other Groovy actions transparently. Notice, that these references will keep the Groovy object (which is actually a Java object) alive until the reference object is garbage collected in expecco. Be aware that such references may have a very long life time, if kept in an activity log. Therefore, output pins which receive such references should either be marked as non-logging, or you should set the "Log Strings of Bridge Objects" flag in the "Settings" - "Execution" - "Logging" dialog.

Reporting (Groovy)[edit]

  • error ()
    Report a defect (in the test). Stops execution.
  • error (infoString)
    Report a defect (in the test). Stops execution.
  • fail ()
    Report a failure (in the SUT). Stops execution.
  • fail (infoString)
    Report a failure (in the SUT). Stops execution.
  • inconclusive ()
    Report an inconclusive test. Stops execution.
  • inconclusive (infoString)
    Report an inconclusive test. Stops execution.
  • activitySuccess ()
    Finishes the current activity with success.
  • activitySuccess (infoString)
    Finishes the current activity with success.
  • pass ()
    Finishes the current testCase with success.
  • pass (infoString)
    Finishes the current testCase with success.

 

Logging (Groovy)[edit]

  • logError (messageString)
    Adds a error message to the activity log.
  • logError (messageString, detail)
    Adds a error message to the activity log.
  • logWarning (messageString)
    Adds a warning to the activity log.
  • logWarning (messageString, detail)
    Adds a warning to the activity log.
  • logInfo (messageString)
    Adds an info message to the activity log.
  • logInfo (messageString, detail)
    Adds an info message to the activity log.
  • alert (messageString)
    Adds a warning message to the activity log, and also shows a DialogBox, which has to be confirmed by the operator. The dialog box and confirmation can be disabled by a settings flag in the "Execution - Log -Settings" dialog (by default it is disabled).
  • warn (messageString)
    Same as alert() (for Smalltalk compatibility).

 

Reflection, Information, Queries and Accessing (Groovy)[edit]

  • environmentAt (anEnvironmentVarName)
    The value of an environment variable
  • environmentAt_put (anEnvironmentVarName, value)
    Changing the value of an environment variable.
  • nameOfActiveTestPlan ()
    The name of the currently executing text plan
  • nameOfActiveTestPlanItem ()
    The name of the currently executing text case
  • nameOfStep ()
    The name of the corresponding step of the activity

 

Interaction with Expecco and Debugging (Groovy)[edit]

  • eval (smalltalkCodeString)
    Evaluate a piece of Smalltalk code inside expecco.
  • evalJS (javascriptCodeString)
    Evaluate a piece of JavaScript code inside expecco.
  • inspect (javaObject)
    Opens the expecco-inspector showing details of the argument object.
  • halt() or halt(message)
    stops (breakpoint) and opens the expecco-debugger; however, this debugger cannot show the internals of the suspended Groovy code, but will only present the expecco calling chain up to the bridge call.

 

Special Functions (Groovy)[edit]

  • mayStillDoExpeccoCalls (boolean)
    Ensures that the API-functions described here will still be callable (by Java callback functions from other threads) even after the step has finished execution. In other words, it prevents releasing the observer object which is responsible for the transfer of information between the two systems. Be aware that this object remains and will never be released, until the bridge connection is closed. I.e. there is a potential for a memory leak inside the Java VM here.

 

Pin Functions (Groovy)[edit]

Currently, Groovy blocks do not support a variable number of input or output pins.

Attention:
Groovy uses many more reserved keywords for syntax than JavaScript or Smalltalk. These keywords cannot be used as pin names, and you will get a syntax error ("<foo> token not expected") if you try. Be careful to not name your pins as any of: "in", "return", "class", "private", "public", etc. As a proven best practice, add a "Pin" suffix to your pin names (i.e. name it "inPin", instead of "in").

Input Pins (Groovy)[edit]
  • hasValue ()
    Returns true if the pin has received a value
  • value ()
    Returns the value of the pin. Raises an error if the pin did not receive any value.
  • valueIfAbsent (alternativeValue)
    Returns the value of a pin or the alternativeValue if the pin did not receive any value.

 

Output Pins (Groovy)[edit]
  • isBuffered ()
    True if the pin is buffered
  • value (data)
    Writes the value.

 

Transcript, Stderr and Stdout (Groovy)[edit]

The expecco "Transcript", "Stderr" and "Stdout" are also accessible from Groovy code. However, only a limited subset of messages is supported:

  • cr ()
    Adds a linebreak (i.e. followup text will be shown on the next line)
  • show (arg)
    Adds a textual representation of the argument, which can be a string, number or any other object.
  • showCR (arg)
    A combination of show(), followed by a linebreak.
  • writeLine (string)
    For compatibility with Java's PrintStream protocol.
  • print (string)
    For compatibility with Java's PrintStream protocol (expecco 2.8).
  • println (string)
    For compatibility with Java's PrintStream protocol (expecco 2.8).
  • println ()
    For compatibility with Java's PrintStream protocol (expecco 2.8).

In addition, stdout and stderr are also forwarded to the expecco Transcript window, depending on the settings in expecco ("Extras" ► "Settings" ► "Execution" ► "Tracing" ► "Show Stdout and Stderr on Transcript").  

Node.js (Bridged) Elementary Blocks[edit]

Node.js is an open source interpreter for JavaScript. For details on the language and libraries, visit "NodeJS Tutorial" . You have to download and install "node" separately - the interpreter is not part of the expecco installation procedure (see https://nodejs.org/en/download ).

Node.js code execution is supported in expecco via 2 different mechanisms:

  • Node.js Bridged Action Blocks - these look at the outside like regular action blocks with typed input and output pins, which are available inside the action's code via ".value()" APIs. The bridge-partner in which the code is to be executed is started once and will remain an active process until terminated. Any state (data) created inside the bridge remains alive while the partner is running and the connection is alive.
  • Node.js-Script Action Blocks - these look like shell-script blocks, in that the standard input and output is used to interact with the script. For every individual script action, the script interpreter is started anew. No state is alive between and across actions (the script-interpreter is terminated after each action)

The following describes the first kind of blocks (bridged). Node.js-Script blocks are described elsewhere, in the Node.js-Script Blocks chapter.

Code written as a Node.js elementary block is not executed directly by expecco. Instead, the code is forwarded to a node interpreter. This may be a local node process, whose sole purpose is to provide additional utility functions or which provides an interface to the actual system under test (SUT), or it may be on a remote system.

NodeJS Datatype Limitations[edit]

Limited NodeJS Object Conversion[edit]

Some limited form of object conversion is automatically performed when passing expecco's Smalltalk objects to NodeJS, and back when returning values from NodeJS. This conversion especially affects values passed to/from pins of a NodeJS action.

The following table summarizes the conversion process:

from Smalltalk to NodeJS and from NodeJS to Smalltalk
String String String
Float
(node ONLY supports IEEE double numbers)
Float Float
Float, Double Float Float
(Smalltalk Floats have double precision)
Fraction Float Float
(Smalltalk Floats have double precision)
Boolean Boolean Boolean
Array of any above Array of any above Array of any above
- any any NodeJS object as Reference
via "makeRef(obj)"
fetch in Node via "inpin.refValue()"
Filename String (i.e. pathname) String
arbitrary Smalltalk Object (!) Object with slots Dictionary with slots

(-) does not work. In general, only objects which can be represented by JSON can be transmitted between expecco and the node action.

No Smalltalk Classes, No Smalltalk Objects in NodeJS[edit]

Of course, no Smalltalk class can be used directly in NodeJS code. And only a subset of Smalltalk objects (those which can be represented as JSON string) can be passed to/from NodeJS as described above. However, all node.js code (packages/modules) are at your hands now!

In general: if more complex objects need to be interchanged, this must be either done by converting them to an array (of objects), possibly an array of arrays. Or, alternatively to some ASCII representation (XML or JSON, for a more lightweight approach) and convert this back and forth.

It is also possible to pass Node-object-references from the Node interpreter to expecco, and pass it back to the Node interpreter later (usually in another action). This is used to get connection, protocol or device handles from Node, and pass them to other (transmission or control) actions later.

Limited Error Reporting[edit]

Notice, that arithmetic errors are usually not reported by Node. Division by zero or taking the logarithm of a negative number deliver a Nan ("Not a Number") instead of raising an error. Thus you have to explicitly check the result from such computations in your code.

Debugging NodeJS Actions[edit]

Some debugging facilities are provided to support development of Node code. If your code contains an endless loop, you can interrupt the Node interpreter (via the "Interrupt Execution" button at the top right) and get a debugger window, showing the call stack and local variables.

However, there are situations, when the Node interpreter gets completely locked up and ceases to react. In this situation, you will have to shut down the Node interpreter via the "Plugins" - "Bridges" - "Node JS" - "Close Connections" menu function. Of course, this also implies, that any state inside the Node interpreter is lost, and you'll have to rerun your test setup from the start.

Debugger Functions[edit]

Currently, you cannot look "into" objects, and you cannot modify the local variables. The debugger looks very similar to the Smalltalk/JavaScript debugger and supports continue, line stepping, stepping into called functions, stepping out of called functions, aborting an activity and terminating the whole node-interpreter.

Limitations of the Debugger[edit]

One problem which is encountered with the current node version is that the breakpoint line numbers are sometimes off-by-one; this means, that the line reported by the node-debugger is wrong and therefore also shown wrong in the debugger. This is a problem of the particular node version, and may or may not appear in your concrete installation.

Sorry, but we cannot currently provide a workaround for this problem, as we have not yet figured out, what constellation of source-code/statement/situation leads to this problem. (this problem was also reported by other node users in the internet forums)

NodeJS Code API[edit]

NodeJS code supports a subset of the above activity functions, which is intended provide an interface similar to the JavaScript and Smalltalk elementary code API. Of course, technically for every API function below, the code executing in the remote Node interpreter has to make a remote procedure call back to expecco. On the Node side, this is done very similar to the above described remote message mechanism, when messages are sent from Smalltalk to a bridge. Also notice, that due to the single threaded nature of Node programs, all functions which ask for a value from expecco (eg. "environmentAt") will take a callback argument, which is called when the return value arrives.

Variables seen by the Executed Node Function[edit]

The following variables are in the scope of the executed function:

  • Transcript
    supports a few functions to display messages in the expecco Transcript window (see below)
  • Stdout
    supports a few functions to display messages on expecco's stdout stream (see below)
  • Stderr
    supports a few functions to display messages on expecco's stderr stream (see below)
  • Logger
    supports info()/warn()/error()/fatal(); these are forwarded to the Smalltalk Logger object (see Logger)
  • __bridge__
    the bridge object which handles the communication with expecco. The instance slot named "asynchronous" is of special interest if the called function uses asynchronous callbacks (see below)

Reporting (NodeJS)[edit]

  • error (infoString)
    Report a defect (in the test). Stops execution.
  • fail (infoString)
    Report a failure (in the SUT). Stops execution.
  • inconclusive (infoString)
    Report an inconclusive test. Stops execution.
  • activitySuccess (infoString)
    Finishes the current activity with success (same as "success()").
  • pass (infoString)
    Finishes the current testCase with success.

Logging (NodeJS)[edit]

  • logFail (messageString)
    Adds a fail message to the activity log.
  • logError (messageString)
    Adds a error message to the activity log.
  • logWarning (messageString)
    Adds a warning to the activity log.
  • logInfo (messageString)
    Adds an info message to the activity log.
  • alert (messageString)
    Adds a warning message to the activity log, and also shows a DialogBox, which has to be confirmed by the operator. The dialog box and confirmation can be disabled by a settings flag in the "Execution" - "Log -Settings" dialog (by default it is disabled).
  • warn (messageString)
    Same as alert() (for Smalltalk compatibility).

Reflection, Information, Queries and Accessing (NodeJS)[edit]

  • nameOfAction ()
    The name of the activity

Interaction with Expecco (NodeJS)[edit]

  • call(<actionName>, <arg1>, <arg2>, ... <argN>, function(rslt...) {...});
    Executes any other expecco action (inside expecco - not inside the node interpreter), and finally calls the callBack function, when the action has finished.
    Notice that this is an asynchronous callback; you should not perform any further actions after the call, but instead continue inside the callback (and signal final completion via "success() / error()" call from there).
    The callback may expect multiple rslt-arguments, to get the output values of more than one pin. However, it is currently not possible to get the values of pins which are written multiple times (i.e. an error will be reported, if that is the case)
    See example below.

Event Sending (NodeJS)[edit]

  • pushEvent (payloadData)
    pushes an event onto the global event handler's event queue. The payload is packed into an Event object with eventType "#default. Raises an error, if no global event handler process is running.
  • pushEventType_data (eventTypeSymbolOrNil, payloadData)
    pushes an event of type (or #default) onto the global event handler's event queue. Raises an error, if no global event handler process is running.

Environment Access (NodeJS)[edit]

  • environmentAt(<varName>, function(err, rslt) {...});
    Fetches a value from the expecco environment which is in scope of the current activity, and finally calls the callBack function, when the action has finished.
    Notice that this is an asynchronous callback; you should not perform any further actions after the call, but instead continue inside the callback (and signal final completion via "success() / error()" call from there).
    You can only read simple objects (numbers and strings) from node actions.
  • environmentAtPut(<varName>, <newValue>, function(err) {...});
    Writes a value into the expecco environment which is in scope of the current activity, and finally calls the callBack function, when the action has finished.
    Notice that this is an asynchronous callback; you should not perform any further actions after the call, but instead continue inside the callback (and signal final completion via "success() / error()" call from there).
    You can only write simple objects (numbers and strings) from node actions.

Special Functions (NodeJS)[edit]

  • makeRef (<anyNodeObject> [, <optionalName> ])
    Generates a reference to a NodeJS object, which can be passed to expecco as an output pin value. Such reference objects can be passed back to NodeJS later and are dereferenced there back to the original object. This is used to pass NodeJS handles (i.e. server/client handles) to expecco and later back to NodeJS. Read below about reference passing vs. value passing.
    Notice that the nodeObject will be registered (i.e. remembered) inside the node interpreter, in order to be found later, when the reference is passed as input by another node action. When the reference is no longer needed, it should be deregistered by calling the "releaseRef function described below.
    Also notice, that expecco will do this automatically, when the reference object is finalized.
    See example below.
  • releaseRef (<nodeObjectReference>)
    removes the reference from the registery inside the node interpreter.
    See example below.
  • wait ()
    Tells expecco to wait until one of the "success/error/fail/inconclusive" functions is called.
    Use this if the action's execution is not finished when the execute() function returns, but instead will explicitly notify the finish from within a callback. This is also to be used if a pin value has to be written later by a callback function.
    See example below.

Pin Functions (NodeJS)[edit]

Currently, Node.js blocks do not support a variable number of input or output pins.

Attention:
Javascript uses many more reserved keywords for syntax than Smalltalk. These keywords cannot be used as pin names, and you will get a syntax error ("<foo> token not expected") if you try. Be careful to not name your pins as any of: "in", "return", "class", "private", "public", etc. As a proven best practice, add a "Pin" suffix to your pin names (i.e. name it "inPin", instead of "in").

Input Pins (NodeJS)[edit]
  • hasValue ()
    Returns true if the pin has received a value
  • isConnected ()
    Returns true if the pin is connected
  • value ()
    Returns the value at the pin (the datum). Raises an error if the pin did not receive any value.
  • refValue ()
    Returns the object referenced by the value at the pin. The pin must have received a reference. Raises an error if the pin did not receive any value. Use this to pass back the original reference object to expecco. The input pin must have received a reference to a Node object as generated previously by "makeRef". Read below about reference passing vs. value passing.
  • valueIfPresent ()
    Returns the pin's datum if it has one, null otherwise. Similar to "value()", but avoids the exception.
  • valueIfAbsent (repl)
    Returns the pin's datum if it has one, repl otherwise. Similar to "value()", but avoids the exception.
Output Pins (NodeJS)[edit]
  • value (data)
    Writes the value.

Transcript, Stderr and Stdout (NodeJS)[edit]

The expecco "Transcript", "Stderr" and "Stdout" are also accessible from Node code. However, only a limited subset of messages is supported:

  • cr ()
    Adds a linebreak (i.e. followup text will be shown on the next line)
  • show (arg)
    Adds a textual representation of the argument, which can be a string, number or any other object.
  • show (fmt, arg...)
    Like "show(), but "%i" sequences in the format argument are expanded by the corresponding arg strings.
  • showCR (arg)
    A combination of show(), followed by a linebreak.
  • showCR (fmt', arg...)
    Like "showCR(), but "%i" sequences in the format argument are expanded by the corresponding arg strings.

In addition, stdout (console.log) and stderr (console.error) are also forwarded to the expecco Transcript window, depending on the settings in expecco ("Extras" ► "Settings" ► "Execution" ► "Tracing" ► "Show Stdout and Stderr on Transcript").  

Reference passing vs. value passing of Node objects to expecco[edit]

When values are passed via an output pin from Node to expecco, two mechanisms are possible:

  • pass by value
    The object's JSON string is generated and passed to expecco. There, the JSON encoding is decoded and a corresponding expecco object is constructed. Conceptional, the expecco object is a copy of the original object.
  • pass by reference
    The object is remembered inside Node, and a reference is passed to expecco. Whenever this reference object is later sent to Node again (via an input pin), the original Node object is retrieved and passed to the JavaScript code. This mechanism preserves the object's identity on the Node side and must be used for object handles (such as protocol handles).

The default mechanism is "pass by value". To pass a reference, use "makeRef(obj)" and pass the generated reference to an output pin.

Example[edit]

The following code snippet sends an object's reference to an output pin:

function execute() {
    ... generate a handle object ...
   outputPin.value( makeRef (someHandle , "aNodeHandle" ) );
}

the returned handle can later be sent via an input pin to another Node action, and use transparently there:

function execute() {
    var handle = inputPin.value();
    ... do something with handle ...
}

occasionally, it is required that a reference which was received via an input pin must later be sent to an output pin again, preserving the original reference. This is to prevent additional memory allocations which would result from calling "makeRef" again (you would get multiple references to the same object). For this, use "inputPin.refValue()" and send this to an output pin without a "makeRef":

function execute() {
    var handle = inputPin.value();

    ... do something with handle ...
    // send the original reference to the output
    outputPin.value( inputPin.refValue() );
}

Finally, you want to release the reference inside the node interpreter, to prevent memory leaks. Usually, this should be called, when a handle becomes invalid (e.g. when a connection handle is closed).

For this, call the "releaseRef" function (on the node side), passing the reference instead of the referenced object.
For example, a close-connection action block might look like:

function execute() {
    var reference = inputPin.refValue();
    var referredObject = inputPin.value();

    ... do something with referredObject ...
    closeConnection( referredObject );
    // release to prevent memory leaks
    releaseRef(referredObject);
}

Asynchronous and Callback Functions[edit]

A common pattern in node is "continuation passing style" control flow. That means, that many functions expect function as callback argument, which is called later whenever the requested operation has finished. This is used especially with I/O and protocol related operations (such as socket connect, http requests, client connect setup etc.). If your "execute" functions calls any of those, and wants the expecco action to wait until the callback occurred, you should call the "wait()" function at the end of the execute function.

Then expecco will continue to wait for the action to be finished until the node code calls one of the "fail()", "error()", "success()", "pass()", "inconclusive()" functions (listed above).
Example:

 function execute() {
    ... call a function which does a callback ...
   myProtocolConnect( ... , function(err) {
       ... callback possibly called much later ...
       // tell expecco that we're done
       if (err) {
           error("some error happened: "+err.toString());
       } else { 
           success();
       }
   });
   // tell expecco to continue waiting
   wait();
}

Notice that callbacks can be either written as functions (with a statement body):

function(err) { statement; ... statement; }

or as a lambda expression, where the body is an expression:

(err) => expression

Calling other Expecco Actions[edit]

Any expecco action (i.e. elementary or compound) can be called from Node code. However, as node uses a callback mechanism, the code looks a bit different from other languages, in that an additional callback argument is to be passed. You can pass either the name or the UUID of the activity which is to be called. Arguments are passed to the called action's input pin (top to bottom).

For now, there is a limitation, in that only a single value (per pin) can be passed to the callback (i.e. you cannot get the values of pins which are written multiple times).
Example:

 function execute() {
    ... call an expecco activity ...
   call("myAction", 100, 200 , function(result) {
       ... callback gets the first value from myAction's first output pin ...
       success();
   });
   // not reached
}

Notice, that in order to get a sequence of calls, the code needs nested functions, as in:

function execute() {
   
   call("myAction1", 1000, 2000, function(rslt1) {
           Transcript.showCR("after first call: "+rslt1);
           call("myAction2", 10000, 20000, function(err, rslt2) {
                   Transcript.showCR("after second call: "+rslt2);
                   success();
           });
   });
   // not reached
}

If the called action has multiple output pins, provide a callback function which expects more than one rslt argument, as in:

function execute() {
   
   call("actionWIthTwoOutputPins", 1000, 2000, function(valueAtPin1, valueAtPin2) {
           Transcript.showCR("received two values: "+valueAtPin1+" and: "+valueAtPin2);
           success();
   });
   // not reached
}

Reading expecco Environment Variables[edit]

Expecco variables can be fetched via the "environmentAt" function.
For example:

function execute() {
   environmentAt("variable1", function(err, varValue) {
           Transcript.showCR("the variable value is: "+varValue);
           success();
   });
   // not reached

}

Executing Node Actions in Multiple Node Interpreters[edit]

In order to test the interaction between multiple node programs, or to generate load for performance tests, it may be required to start multiple node interpreters, and run action code in them.

This chapter describes how to start a node interpreter and how to interact with them.

Starting another Node Interpreter on the Local Machine[edit]

Every Node bridge uses the given port for bridge communication plus the next port for debugging. Every bridge running on the same host must use different ports. The default bridge uses port 8777 (and 8778 for debugging). Thus, additional bridges should be given ports incremented in steps of 2, and useful ports are 8779, 8781, 8783 etc. for additional node bridges.

Programmatic Start[edit]
 nodeBridge := Workflow::ExecutionBridgedNodeJSCodeDescription 
                    newBridgeConnectionForHost:hostName port:portNr 
                    startBridge:true in:aDirectoryOrNil.
Using an Action from the Standard Library[edit]

use the "Start new Local Node Bridge" action from the library.

Executing an Action inside another Node Interpreter[edit]

Either add a "nodejs" input pin to the action (passing the other node-bridge's handle) or set the "NODEJS" environment variable before the action. An example is found in the "d62_Event_Queue_Demos.ets" suite:
Event Queue Demo.png

Node Packages[edit]

Example: Installing Additional Node Packages[edit]

Use the Node package manager "npm" to install packages.
The "Extras" ► "Bridges" ► "Node Bridge" menu also contains an entry to install npm packages.
Find packages in https://www.npmjs.com.

For example, assume you need the current city weather in a suite, navigate to "https://www.npmjs.com", search for "weather", find the "city-weather" package and click on it. You will arrive on a page giving installation instructions and sample code at the end. Scroll down to the example code and keep that page open (we can use the code later).

Open a cmd/shell window and execute on the command line:

npm install city-weather

Examples: Using a Node Package in Expecco[edit]

Example1: Access to the City Weather Service[edit]

For a first test, we will take the original code from the example. Create a new node action, and enter the code:

var weather = require("city-weather");

function execute() {
    weather.getActualTemp('Rome', function(temp){
        console.log("Actual temperature: " + temp);
    });
}

(you can copy-paste the sample code from the webpage)

Open the expecco console (aka "Transcript" via the "Extras" ► "Tools" ► "Transcript" menu item) and run the test. A temperature should be displayed on the Transcript.

Now add an input pin named "city", and change the code to:

...
weather.getActualTemp(city.value(), function(temp){
...

and try the code in the "Test/Demo" page with a few different cities at the input pin.

Then, add an output pin named "temperature", and change the code again to:

var weather = require("city-weather");

function execute() {
    weather.getActualTemp(city.value(), function(temp){
        console.log("Actual temperature: " + temp);
        temperature.value( temp );
    });
}

if you now execute this action, you will notice, that the output pin does NOT get the value. The reason is, that the pin is written by a node-callback function, which is called at a time after the action's "execute" function has already finished. Thus, we have to tell expecco, that it should wait until the callback is actually called. For this, you should add a call to the "wait()" function.

This tells expecco, that the execute-function is not complete, and will wait for one of the "success/fail/error" functions to be called. So we also have to add a call to "success()" inside the callback (otherwise, expecco would wait forever).

Thus, we change the code to:

var weather = require("city-weather");

function execute() {
    weather.getActualTemp(city.value(), function(temp){
        console.log("Actual temperature: " + temp);
        temperature.value( temp );
        success();
    });
   wait();
}

As a final step, you should remove or comment the call to "console.log()" and add some error reporting, in case a city was not found:

var weather = require("city-weather");

function execute() {
   weather.getActualTemp(city.value(), function(temp){
       // console.log("Actual temperature: " + temp);
       if (temp == "City was not found") {
           error(temp+": "+city.value());
       }
       temperature.value( temp );
       success();
   });
   wait();
}

Example2: Access OpenStreetMap Services[edit]

This is described in a separate document.

Bridged Python Elementary Blocks[edit]

You have to download and install python or python3 or jython separately - the interpreter is not part of the expecco installation procedure. See https://www.python.org/downloads for python, http://jython.org/downloads.html for jython.

Similar to Node, Python code execution is supported in expecco via 2 different mechanisms:

  • Python Bridged Action Blocks - these look at the outside like regular action blocks with typed input and output pins, which are available inside the action's code via ".value()" APIs. Objects can be interchanged between expecco and the python process either by value or by reference. The bridge-partner in which the code is to be executed is started once and will remain an active process until terminated. Any state (data) created inside the bridge remains alive while the partner is running and the connection is alive.
  • Python-Script Action Blocks - these look like shell-script blocks, in that the standard input and output is used to interact with the script. For every individual script action, the script interpreter is started anew. No state is alive between and across actions (the script-interpreter is terminated after each action), and no objects can be exchanged (except by means of reading encoded objects via stdin/stdout/stderr).

The following describes the first kind of blocks (bridged). Script blocks are described elsewhere, in the Python-Script Blocks chapter.

Code written as a Python elementary block is not executed directly by expecco. Instead, the code is forwarded to a python interpreter. This may be a local python process, whose sole purpose is to provide additional utility functions or which provides an interface to the actual system under test (SUT), or it may be on a remote system.

Debugging Python Actions[edit]

Debug support for Python actions is being developped and planned to be deployed with the 19.2 release

In order to support debugging of python actions, you have to install the "ptvsd" package with:

    pip install ptvsd

or:

    pip3 install ptvsd

For more information on the ptvsd package, see https://pypi.org/project/ptvsd and https://github.com/Microsoft/ptvsd.

Notice that the debug features are currently not supported by python2 or jython.

Python Datatype Limitations[edit]

Limited Python Object Conversion[edit]

Some limited form of object conversion is automatically performed when passing expecco's Smalltalk objects to Python, and back when returning values from Python. This conversion especially affects values passed to/from pins of a Python action.

The following table summarizes the conversion process:

from Smalltalk to Python and from Python to Smalltalk
String String String
Float
Float Float
Float, Double Float Float
(Smalltalk Floats have double precision)
Fraction Float Float
(Smalltalk Floats have double precision)
Boolean Boolean Boolean
Array of any above Array of any above Array of any above
- any any Python object as Reference
via "makeRef(obj)"
Python object reference
as previously generated
by "makeRef(obj)"
fetch the reference in Python via "inpin.refValue()"
or the value via "inpin.value()"
fetch the reference in Python via "inpin.refValue()"
or the value via "inpin.value()"
send value or reference with "makeRef(obj)"
Filename String (i.e. pathname) String
arbitrary Smalltalk Object (!) Object with slots Dictionary with slots

(-) does not work. In general, only objects which can be represented by JSON can be transmitted between expecco and the python action.

No Smalltalk Classes, No Smalltalk Objects in Python[edit]

Of course, no Smalltalk class can be used directly in Python code. And only a subset of Smalltalk objects (those which can be represented as JSON string) can be passed to/from Python as described above.

In general: if more complex objects need to be interchanged, this must be either done by converting them to an array (of objects), possibly an array of arrays. Or, alternatively to some ASCII representation (XML or JSON, for a more lightweight approach) and convert this back and forth.

It is also possible to pass Python-object-references from the Python interpreter to expecco, and pass it back to the Python interpreter later (usually in another action). This is used to get connection, protocol or device handles from Python, and pass them to other (transmission or control) actions later.

Limited Syntax Highlighting, Code Completion and Debugging Support[edit]

Currently, all of those are very limited: the syntax highlighter only detects keywords and some constants as such; code completion does not work and no interactive debugger is provided currently.

Bridged Python Code API[edit]

WARNING: Bridged Python Elementary Actions are currently being developed - their behavior might not work as described (yet)

Bridged Python code can use activity functions similar to the above Groovy or Node actions. The API is intended to provide an interface similar to the JavaScript and Smalltalk elementary code API. Of course, technically for every API function below, the code executing in the remote Python interpreter has to make a remote procedure call back to expecco. On the Python side, this is done very similar to the above described remote message mechanism, when messages are sent from Smalltalk to a bridge.

Variables seen by the Executed Python Function[edit]

The following variables are in the scope of the executed function:

  • Transcript
    supports a few functions to display messages in the expecco Transcript window (see below)
  • Stdout
    supports a few functions to display messages on expecco's stdout stream (see below)
  • Stderr
    supports a few functions to display messages on expecco's stderr stream (see below)
  • Logger
    supports info()/warn()/error()/fatal(); these are forwarded to the Smalltalk Logger object (see Logger)

Reporting (Python)[edit]

  • error (infoString)
    Report a defect (in the test). Stops execution.
  • fail (infoString)
    Report a failure (in the SUT). Stops execution.
  • inconclusive (infoString)
    Report an inconclusive test. Stops execution.
  • activitySuccess (infoString)
    Finishes the current activity with success (same as "success()").
  • testPass (infoString)
    Finishes the current testCase with success. This has the same effect as "pass()" in other actions - its name had to be changed because "pass" is a reserved keyword in Python.

Logging (Python)[edit]

  • logFail (messageString)
    Adds a fail message to the activity log.
  • logError (messageString)
    Adds a error message to the activity log.
  • logWarning (messageString)
    Adds a warning to the activity log.
  • logInfo (messageString)
    Adds an info message to the activity log.
  • alert (messageString)
    Adds a warning message to the activity log, and also shows a DialogBox, which has to be confirmed by the operator. The dialog box and confirmation can be disabled by a settings flag in the "Execution" - "Log -Settings" dialog (by default it is disabled).
  • warn (messageString)
    Same as alert() (for Smalltalk compatibility).

Reflection, Information, Queries and Accessing (Python)[edit]

Interaction with Expecco (Python)[edit]

  • call(<actionName>, <arg1>, <arg2>, ... <argN>, function(rslt...) {...});
    Executes any other expecco action (inside expecco - not inside the node interpreter), and finally calls the callBack function, when the action has finished.
    Notice that this is an asynchronous callback; you should not perform any further actions after the call, but instead continue inside the callback (and signal final completion via "success() / error()" call from there).
    The callback may expect multiple rslt-arguments, to get the output values of more than one pin. However, it is currently not possible to get the values of pins which are written multiple times (i.e. an error will be reported, if that is the case)
    See example below.

Environment Access (Python)[edit]

  • environmentAt(<varName>);
    Fetches and returns a value from the expecco environment which is in scope of the current activity.
    You can only read simple objects (numbers and strings) from python actions.
  • environmentAtPut(<varName>, <newValue>);
    Writes a value into the expecco environment which is in scope of the current activity.
    You can only write simple objects (numbers and strings) from node actions.

Event Sending (Python)[edit]

  • pushEvent (payloadData)
    pushes an event onto the global event handler's event queue. The payload is packed into an Event object with eventType "#default. Raises an error, if no global event handler process is running.
  • pushEventType_data (eventTypeSymbolOrNil, payloadData)
    pushes an event of type (or #default) onto the global event handler's event queue. Raises an error, if no global event handler process is running.

 

Special Functions (Python)[edit]

  • makeRef (<anyNodeObject>)
    Generates a reference to a NodeJS object, which can be passed to expecco as an output pin value. Such reference objects can be passed back to NodeJS later and are dereferenced there back to the original object. This is used to pass NodeJS handles (i.e. server/client handles) to expecco and later back to NodeJS. Read below about reference passing vs. value passing.
    Notice that the nodeObject will be registered (i.e. remembered) inside the node interpreter, in order to be found later, when the reference is passed as input by another node action. When the reference is no longer needed, it should be deregistered by calling the "releaseRef function described below.
    Also notice, that expecco will do this automatically, when the reference object is finalised.
    See example below.
  • releaseRef (<nodeObjectReference>)
    removes the reference from the registery inside the node interpreter.
    See example below.
  • wait ()
    Tells expecco to wait until one of the "success/error/fail/inconclusive" functions is called.
    Use this if a pin value has to be written later by a callback function.
    See example below.

Pin Functions (Python)[edit]

Currently, Node.js blocks do not support a variable number of input or output pins.

Attention:
Javascript uses many more reserved keywords for syntax than Smalltalk. These keywords cannot be used as pin names, and you will get a syntax error ("<foo> token not expected") if you try. Be careful to not name your pins as any of: "in", "return", "class", "private", "public", etc. As a proven best practice, add a "Pin" suffix to your pin names (i.e. name it "inPin", instead of "in").

Input Pins (Python)[edit]
  • hasValue ()
    Returns true if the pin has received a value
  • isConnected ()
    Returns true if the pin is connected
  • value ()
    Returns the value at the pin (the datum). Raises an error if the pin did not receive any value.
  • refValue ()
    Returns the object referenced by the value at the pin. The pin must have received a reference. Raises an error if the pin did not receive any value. Use this to pass back the original reference object to expecco. The input pin must have received a reference to a Node object as generated previously by "makeRef". Read below about reference passing vs. value passing.
  • valueIfPresent ()
    Returns the pin-datum if it has one, None otherwise. Similar to "value()", but avoids the exception.
  • valueIfAbsent (repl)
    Returns the pin's datum if it has one, repl otherwise. Similar to "value()", but avoids the exception.
Output Pins (Python)[edit]
  • value (data)
    Writes the value.

Transcript, Stderr and Stdout (Python)[edit]

The expecco "Transcript", "Stderr" and "Stdout" are also accessible from Node code. However, only a limited subset of messages is supported:

  • cr ()
    Adds a linebreak (i.e. followup text will be shown on the next line)
  • show (arg)
    Adds a textual representation of the argument, which can be a string, number or any other object.
  • show (fmt', arg...)
    Like "show(), but "%i" sequences in the format argument are expanded by the corresponding arg strings.
  • showCR (arg)
    A combination of show(), followed by a linebreak.
  • showCR (fmt', arg...)
    Like "showCR(), but "%i" sequences in the format argument are expanded by the corresponding arg strings.

In addition, stdout (console.log) and stderr (console.error) are also forwarded to the expecco Transcript window, depending on the settings in expecco ("Extras" ► "Settings" ► "Execution" ► "Tracing" ► "Show Stdout and Stderr on Transcript").  

Reference passing vs. value passing of Python objects to expecco[edit]

When values are passed via an output pin from Python to expecco, two mechanisms are possible:

  • pass by value
    The object's JSON string is generated and passed to expecco. There, the JSON encoding is decoded and a corresponding expecco object is constructed. Conceptional, the expecco object is a copy of the original object.
  • pass by reference
    The object is remembered inside Python, and a reference is passed to expecco. Whenever this reference object is later sent to Python again (via an input pin), the original Python object is retrieved and passed to the Python code. This mechanism preserves the object's identity on the Python side and must be used for object handles (such as protocol handles).

The default mechanism is "pass by value". To pass a reference, use "makeRef(obj)" and pass the generated reference to an output pin.

Example[edit]

The following code snippet sends an object's reference to an output pin:

def execute():
    ... generate a handle object ...
   outputPin.value( makeRef (someHandle ) )

the returned handle can later be sent via an input pin to another Node action, and use transparently there:

def execute():
    handle = inputPin.value()
    ... do something with handle ...
}

occasionally, it is required that a reference which was received via an input pin must later be sent to an output pin again, preserving the original reference. This is to prevent additional memory allocations which would result from calling "makeRef" again (you would get multiple references to the same object). For this, use "inputPin.refValue()" and send this to an output pin without a "makeRef":

def execute():
    handle = inputPin.value();

    ... do something with handle ...
    # send the original reference to the output
    outputPin.value( inputPin.refValue() )

Finally, you want to release the reference inside the node interpreter, to prevent memory leaks. Usually, this should be called, when a handle becomes invalid (e.g. when a connection handle is closed).

For this, call the "releaseRef" function (on the node side), passing the reference instead of the referenced object.
For example, a close-connection action block might look like:

def execute():
    reference = inputPin.refValue()
    referredObject = inputPin.value()

    ... do something with referredObject ...
    closeConnection( referredObject )
    # release to prevent memory leaks
    releaseRef(referredObject)


Calling other Expecco Actions[edit]

Not yet implemented

Any expecco action (i.e. elementary or compound) can be called from Python code. You can pass either the name or the UUID of the activity which is to be called. Arguments are passed to the called action's input pin (top to bottom).

For now, there is a limitation, in that only a single value (per pin) can be passed to the callback (i.e. you cannot get the values of pins which are written multiple times).
Example:

 def execute():
    ... call an expecco activity ...
   result = call("myAction", 100, 200)

Reading expecco Environment Variables[edit]

Expecco variables can be fetched via the "environmentAt" function. Notice, that this is an asynchronous call and thus expected a callback function.
For example:

def execute():
   varValue = environmentAt("variable1")
   ...

Python Packages[edit]

Example: Installing Additional Python Packages[edit]

Use the Python package installer "pip" to install packages.
The "Extras" ► "Bridges" ► "Python Bridge" menu also contains an entry to install pip packages.
Find packages in https://pypi.org/ or https://docs.python.org/3/py-modindex.html.


Example: Using a Python Package in Expecco[edit]

In this example, we will access the OpenStreetmap API to fetch information about a particular street ("Straße der Nationen") in a given city ("Chemnitz"). The example was taken from https://pypi.org/project/overpy which is documented in https://python-overpy.readthedocs.io/en/latest/index.html.

First, install the package with:

pip install overpy

For a first test, we will take the original code from the example. Create a new Python action, and enter the code:

import overpy

api = overpy.Overpass()

def execute(): 
   # fetch all ways and nodes
   result = api.query("""
       way(50.746,7.154, 50.748,7.157) ["highway"];
       (._;>;);
       out body;
       """)

   for way in result.ways:
       print("Name:%s" % way.tags.get("name", "n/a"))
       print("  Highway:%s" % way.tags.get("highway", "n/a"))
       print("  Nodes:")
       for node in way.nodes:
           print("    Lat:%f, Lon:%f" % (node.lat, node.lon))

(you can copy-paste the sample code from the webpage)

Open the expecco console (aka "Transcript" via the "Extras" ► "Tools" ► "Transcript" menu item) and run the test. A list of street nodes should be displayed on the Transcript.

Bridged C Elementary Blocks[edit]

This is currently being developed and will be available in or after the 19.2 version. You have to make sure that a CC-compiler toolchain is available and the "cc" command is found among your path.

Warning: bridged C-code is highly dependent on the C-compiler toolchain of the target machine. It should only be used by experts and only for very special situations, when all alternatives have been tried and failed. Bridged C code can be used to implement time critical functions or to interface to C/C++ libraries, when the simple DLL-call interface is too complicated to use (for example, if complicated data structures have to be exchanged, C-callbacks are needed, or C++ interfaces are to be implemented).

Bridged C Code API[edit]

Note: This is a previous API documentation. Bridged C elementary actions are currently being developed - the API may change slightly until officially released.

The API is intended to look similar to the other language's elementary code API. However, due to the nature and syntax of the C-language, certain differences are apparent. The biggest are due to C being a static typed language.

Variables seen by the Executed C Function[edit]

The following variables are in the scope of the executed function:

  • Transcript
    supports a few functions to display messages in the expecco Transcript window (see below)
  • Stdout
    supports a few functions to display messages on expecco's stdout stream (see below)
  • Stderr
    supports a few functions to display messages on expecco's stderr stream (see below)

Reporting (C)[edit]

  • error (fmt , ...)
    Report a defect (in the test). Stops execution. The arguments are printf-style.
  • fail (fmt, ...)
    Report a failure (in the SUT). Stops execution. The arguments are printf-style.
  • inconclusive (fmt , ...)
    Report an inconclusive test. Stops execution. The arguments are printf-style.
  • activitySuccess (fmt , ...)
    Finishes the current activity with success (same as "success()"). The arguments are printf-style.

Logging (C)[edit]

  • logFail (fmt , ...)
    Adds a fail message to the activity log, but continues execution. The arguments are printf-style.
  • logError (fmt , ...)
    Adds a error message to the activity log, but continues execution. The arguments are printf-style.
  • logWarning (fmt , ...)
    Adds a warning to the activity log, but continues execution. The arguments are printf-style.
  • logInfo (fmt , ...)
    Adds an info message to the activity log, and continues execution. The arguments are printf-style.
  • alert (fmt , ...)
    Adds a warning message to the activity log, and also shows a DialogBox, which has to be confirmed by the operator. The dialog box and confirmation can be disabled by a settings flag in the "Execution" - "Log -Settings" dialog (by default it is disabled).

Reflection, Information, Queries and Accessing (C)[edit]

Pin Functions (C)[edit]

Currently, C blocks do not support a variable number of input or output pins.

Attention:
C uses many more reserved keywords for syntax than Smalltalk. These keywords cannot be used as pin names, and you will get a syntax error if you try. Be careful to not name your pins as any of: "return", "char", "int", etc. As a proven best practice, add a "Pin" suffix to your pin names (i.e. name it "inPin", instead of "in").

Input Pins (C)[edit]
  • hasValue (inPin)
    Returns true if the pin has received a value, where true is represented as 1, false as 0.
  • isConnected (inPin)
    Returns true if the pin is connected.


  • stringValue (inPin)
    Returns the string value at the pin as a c-char*. Raises an error if the pin did not receive a value, or if the value was not a string.
  • longValue (inPin)
    Returns the integer value at the pin as a c-long. Raises an error if the pin did not receive a value, or if the value was not an integer.
  • doubleValue (inPin)
    Returns the string value at the pin as a c-double Raises an error if the pin did not receive a value, or if the value was not a number.
  • booleanValue (inPin)
    Returns the string value at the pin as a c-int. Raises an error if the pin did not receive a value, or if the value was not a boolean.
  • jsonValue (inPin)
    Returns the string value at the pin as a jsonObject. Raises an error if the pin did not receive a value.


  • stringValueIfAbsent (inPin, repl)
    Returns the pin's datum if it has one, repl otherwise. Similar to "stringValue()", but avoids the exception.
  • longValueIfAbsent (inPin, repl)
    Returns the pin's datum if it has one, repl otherwise. Similar to "longValue()", but avoids the exception.
  • doubleValueIfAbsent (inPin, repl)
    Returns the pin's datum if it has one, repl otherwise. Similar to "doubleValue()", but avoids the exception.
  • booleanValueIfAbsent (inPin, repl)
    Returns the pin's datum if it has one, repl otherwise. Similar to "booleanValue()", but avoids the exception.
  • jsonValueIfAbsent (inPin, repl)
    Returns the pin-datum as a jsonObject if it has one, repl otherwise. Similar to "jsonValue()", but avoids the exception.


  • stringValueIfPresent (inPin)
    Returns the pin's datum if it has one, an empty string otherwise. Similar to "stringValue()", but avoids the exception.
  • longValueIfPresent (inPin)
    Returns the pin's datum if it has one, 0 (zero) otherwise. Similar to "longValue()", but avoids the exception.
  • doubleValueIfPresent (inPin)
    Returns the pin's datum if it has one, 0.0 (zero) otherwise. Similar to "doubleValue()", but avoids the exception.
  • booleanValueIfPresent (inPin)
    Returns the pin's datum if it has one, 0 (false) otherwise. Similar to "booleanValue()", but avoids the exception.
  • jsonValueIfPresent (inPin)
    Returns the pin-datum as a jsonObject if it has one, NULL otherwise. Similar to "jsonValue()", but avoids the exception.
Output Pins (C)[edit]
  • putString (outPin, data)
    Writes a double to the output pin.
  • putLong (outPin, data)
    Writes a long to the output pin.
  • putDouble (outPin, data)
    Writes a double to the output pin.
  • putBoolean (outPin, data)
    Writes a boolean to the output pin. Anything but 0 (zero) is interpreted as true.
  • putJsonObject (outPin, data)
    Writes a jsonObject to the output pin.

Transcript, Stderr and Stdout (C)[edit]

The expecco "Transcript", "Stderr" and "Stdout" are also accessible from C code. However, only a very limited subset of operations is supported:

  • cr (stream)
    Adds a linebreak (i.e. followup text will be shown on the next line)
  • show (stream, arg, ...)
    Adds a textual representation of the argument. The argument is printf-style.
  • showCR (stream, arg, ...)
    A combination of show(), followed by a linebreak.

In addition, stdout and stderrare also forwarded to the expecco Transcript window, depending on the settings in expecco ("Extras" ► "Settings" ► "Execution" ► "Tracing" ► "Show Stdout and Stderr on Transcript").
Thus, you can use "fprintf(stdout, ...)" or "fprintf(stderr, ...)".  

Example: Writing and Calling C Code in Expecco[edit]

In this example, a numbers and strings will be passed to a C action block. The action has been defined with 3 input pins:

  • in1 (with type Float)
  • in2 (with type Integer)
  • in3 (with type String)

and the single output "out1", of type String.

The code will generate a string by concatenating the printf strings of its arguments.

Create a new bridged C action, and enter the code:

static void
execute() {
   double dVal = doubleValue(in1);
   long lVal   = longValue(in2);
   char* sVal  = stringValue(in3);
   char buffer[512];

   snprintf(buffer, sizeof(buffer), "%s / %ld / %g", sVal, lVal, dVal);
   putString(out1, buffer);
   fprintf(stderr, "hello from C-Code!\n");
   if (strlen(buffer) < 20) {
       fail("short string (%d)", strlen(buffer));
   }
}

DotNET Elementary Blocks[edit]

--- to be documented ---

VisualBasic Elementary Blocks[edit]

Support for VisualBasic Script elementary block execution is provided as an extension plugin, and requires a separate plugin license. It is only available for expecco running on the MS Windows operating system.

Code written as a VisualBasic elementary block is not executed directly by expecco. Instead, the code is forwarded to a VisualBasic scripting host which runs as another process either on the local or on a remote host. The scripting host must be a Microsoft Windows host (but expecco itself may run on any type of operating system). By using VisualBasic blocks, expecco's basic black box test functionality can be easily extended by many powerful gray- and white-box tests. Many libraries for device and equipment control, COM/DCOM and other technologies and UI interaction are possible with the VisualBasic plugin.

Please consult the separate VisualBasic plugin documentation for more detail.

Remote expecco Elementary Blocks[edit]

These run on a remote expecco system, and can be used to generate load (stress) or perform additional measurements. Remote expecco actions will be available with the 19.2 expecco version.

--- to be documented ---

Tutorial: Common Tasks[edit]

This chapter gives code fragments and examples for common tasks.

The underlying Smalltalk class library contains (among others) very powerful and robust collection, stream and number representation systems, of which many functions are useful for elementary block developers. The set of usable functions is much larger than for example in Java or C standard libraries. Thus for a newcomer to Smalltalk, it is highly recommended and fruitful to read the introduction texts and/or use the class browser for a deeper understanding. You can benefit from existing code and save a lot of development time in knowing a little about your class libraries.

Also, it is a good idea to try the sample code in a "workspace" window or within the code editor, by selecting the piece of code to be tried, and performing the "printIt" menu function. Thus you can quickly test&try code fragments without a need for an elementary block and its setup.


String Handling[edit]

Notice, that in Smalltalk all collection indices are 1-based. I.e. the index of the first element is 1, and the last index is the collection's size. This is different from C, JavaScript and Java. Thus Smalltalk loops over collection elements should run from 1 to the collection's size, not from zero to the size minus 1. You will get an index exception, if you try. Also notice, that index-based enumeration is actually seldom needed, due to the powerful "do:", "collect:", "select:" etc. family of enumeration methods. These do all the index computations for you, and are often heavily tuned for best performance.

Also notice, that in Smalltalk, String is a subclass of Collection, and most of the functions described below are actually not implemented in the String class, but inherited from a superclass. Take this into consideration, when searching for String utility functions in the class browser (i.e. look there first or click on the SeeInheritedInBrowser.png-"see inherited methods" button in the class browser). On the other hand, the fact that most of those functions are implemented in the Collection superclass also means, that they work on many other collections (Arrays, ByteArray, OrderedCollection).

If you are in doubt, open a workspace window, enter a piece of code and evaluate it the (using the "printIt" menu function). Or open the method finder to find an operation given the desired outcome.


Copying Parts of a String[edit]

To extract a substring, and the index and/or count is known, use one of:

   aString copyFrom: startIndex to: stopIndex
   
   aString copyFrom: startIndex
 
   aString copyTo: stopIndex
 
   aString copyFrom: startIndex count: n
 
   aString copyFirst: count   "/ same as copyTo:
 
   aString copyLast: count    "/ gives the last count characters

All of the above will copy including the character at the start- and stop index. I.e.

   'hello world' copyFrom:2 to:5

will return 'ello'.
Point right.pngThis is different from some C/Java functions, which usually expect the stopIndex to be one-after the last copied element.

Finding Elements and Substrings[edit]

To search for elements of a string (which are characters), use:

   aString indexOf: aCharacter
 
   aString indexOf: aCharacter startingAt: startIndex

and:

   aString lastIndexOf: aCharacter

   aString lastIndexOf: aCharacter startingAt: startIndex

to search backward from the end.


Point right.pngThe Smalltalk methods return a 1-based index, and 0 (zero) if not found; JavaScript methods return a 0-based index and -1 if not found.


Point right.pngNotice that a character constant is written in Smalltalk as $<x>, where <x> is a printable character or a space. Non-printable characters must be specified by their name ("Character return", "Character lf", "Character tab") or their Unicode-point (decimal: "Character value: 127" or hex: "Character value: 16r1F00").

If there are multiple characters to be searched for, pass a collection of searched characters to one of:

   aString indexOfAny: aBunchOfCharacters

   aString indexOfAny: aBunchOfCharacters startingAt: startIndex

for example,

   'hello world' indexOfAny: 'eiou' startingAt:3

looks for the next vocal at or after position 3.

Both receiver and argument may actually be any kind of Collection; thus:

   aString indexOfAny: #( $a $A $e $E )

works the same.

Finally, there is a very generic search function, where you can pass a test-procedure as argument:

   aString findFirst:[:ch | ...a boolean test on ch ...]

will return the index of the next character for which your boolean check computes a true value. For example, to search for the next digit, use:

   aString findFirst:[:ch | ch isDigit]

to search for a digit OR period OR dollar character, you can use one of:

   aString findFirst:[:ch | ch isDigit or:[ ch == $. or:[ ch == $$]]]

   aString indexOfAny:'0123456789$.'

use whichever you find more readable. (there is also a "findFirst:startingAt:", and corresponding "findLast:" and "findLast:startingAt:" for backward searches)


Because it is a common task to search for separators and other non-printable characters, special methods exist for those:

   aString indexOfSeparatorStartingAt: startIndex

   aString indexOfNonSeparatorStartingAt: startIndex

   aString indexOfControlCharacterStartingAt: startIndex

where separators are the whitespace characters (space, tab, vt, cr and lf), and control characters are all characters below ASCII 32.

All of the index-search methods return a 0 (zero) if the character is not found.

Substrings can be searched with:

   aString indexOfString: anotherString
   aString indexOfString: anotherString startingAt: startIndex
   aString indexOfString: anotherString caseSensitive: false
   aString indexOfString: anotherString startingAt: startIndex caseSensitive: false

   aString indexOfString: anotherString startingAt: startIndex

   aString indexOfSubCollection: anotherString
    aString indexOfSubCollection: anotherString startingAt: startIndex

There are also corresponding backward search variants "lastIndexOfSubCollection:".

Notice the somewhat generic names "indexOfSubCollection:" instead of a more intuitive "indexOfString:". The reason is that these methods are inherited from collection, which does not care if the elements are characters or other objects (indexOfString: is an alias for indexOfSubCollection:).

This means, that you can use the same method to search for a slice in an array:

   #(1 2 3 4 5 6 7 8 9 8 7 6 5 1 2 3 2 1) indexOfSubCollection:#(9 8 7)

using the same code.

To find all such methods in the class browser, search for implementors of "*indexOf*", and look at the matches in Collection, SequenceableCollection, CharacterArray and String.

If you don't need the Index[edit]

If you only need to check if a character or substring is present, but not in the exact position, you can use "includes:", "includesSubCollection:", "includesAny:" or "includesAll:" in a similar spirit. These may be slightly faster and make the code more readable. They return a boolean.

Caseless Searches[edit]

For strings, special substring searches are available, which ignore case (upper/lower) differences:

   aString includesString: anotherString caseSensitive: false
   aString indexOfString: anotherString caseSensitive: false

Prefix and Suffix Checks[edit]

   aString startsWith: anotherString 
   aString endsWith: anotherString

are obvious. The powerful:

   aString startsWithAnyOf: aCollectionOfStrings

searches for multiple string prefixes. For example,

   'hello' startsWithAnyOf: #( 'ab' 'ce' 'de' )

would look for all of them and return false in this concrete example. Of course, there is a corresponding "endsWithAnyOf:".

Splitting Parts of a String (or Collection)[edit]

The simplest are:

   aString asCollectionOfWords
   aString asCollectionOfLines

which split at separators and line-ends respectively and return a collection as they say in their name.

More generic is:

   aString js_split: charOrString

which returns a collection of substrings split at "charOrString" elements. as the name suggests, you can pass either a single character or a substring as argument. Thus:

   'hello world isn't this nice' js_split:(Character space)

will give you the strings ('hello' 'world' 'isnt' 'this' 'nice'). Whereas:

   'some : string : separated : by : space : colon space' js_split:' : '

will return a collection containing: ('some' 'string' 'separated' 'by' 'space' 'colon' 'space').

Notice, that we used "js_split:" in the above examples, which is the method used by JavaScript's "split()" function. This is because in Smalltalk, the "split:" method has a different semantics: the receiver is a "splitter", and the argument is splitted by it.
In Smalltalk, the splitter can be both a simple string or a regular expression. For example:

   '[a-z]*' asRegex split:'123abc456def789'

will generate the collection: #('123' '456' '789').


To split into same-sized pieces, use:

   aString splitForSize: charCount

this may be less of interest for strings, but is handy to split fixed records of binary data (remember: these functions are implemented in a superclass of String, from which ByteArray also inherits).

Joining and Concatenating Strings[edit]

The simplest of them is the "," (comma) operator. This takes two collections (and as such also Strings) and creates a new collection containing the concatenation of them. Thus:

   'hello' , 'world'

will create a new string containing 'helloworld', and:

   'hello' , ' ' , 'world'

will give you 'hello world',
and:

   #(1 2 3) , #(4 5 6)

will generate a 6-element array containing the elements 1 to 6.

Notice that concatenation may become somewhat inefficient, if many strings are concatenated, because many temporary string objects are created. Its time complexity is O(n^2), where n is the number of strings concatenated.

If you have to construct a string from many individually, either use one of the join functions below (with O(n) complexity), or a writeStream with O(n log n) complexity. Both handle the reallocations much more efficiently (actually: avoiding most of them).


To join a set of strings, use:

   asCollectionOfStrings asStringWith: separator

where separator may be a character, a separator string or nil. For example:

   #( 'a' 'b' 'c') asStringWith:nil

gives 'abc', and:

   #( 'a' 'b' 'c') asStringWith:' : '

returns: 'a : b : c'

Using a writeStream, write:

   |w result|

   w := WriteStream on:(String new:10).
   w nextPutAll: 'a'.
   w nextPutAll: 'b'.
   ...
   result := w contents

or, using a convenient utility method (which does exactly the same):

   result := String streamContents:[:s |
       s nextPutAll: 'hello'.
       s nextPutAll: 'world'.
       ...
       s nextPutAll: 'Nice world'
   ].

but please read more on streams in the stream chapter below.

Formatting Strings (Method A)[edit]

For text messages, you may want to construct a pretty user readable string which contains printed representations of other objects. For this, use:

   formatString bindWith: argument
 
   formatString bindWith: argument1 with: argument2
 
   formatString bindWith: argument1 with: ... with: argumentN (up to 5 arguments)
 
   formatString bindWithArguments: argumentCollection (any number of arguments)

using "{" .. "}" as array constructor, this is usually written as (notice the periods as expression separators):

   formatString bindWithArguments: { argument1 . argument2 . ... . argumentN }

The formatString itself may contain "%X" placeholders, which are replaced by corresponding printed representations of the argumentX. The arguments will be converted to strings if they are not, thus arbitrary objects can be given as argument (although, in practice, these are usually numbers).

If many arguments are to be passed in, the placeholder names X may consist of a single digit (1-9) only for the first 9 arguments. For other arguments, you must write "%(X)". I.e. '%(10)' to get the tenth argument sliced at that position.

The last of the above methods is a powerful tool, in that it does not only handle argumentCollections indexed by numbers (i.e. Array of arguments), but also Dictionaries, which are indexed by name. For example, if you have a Dictionary collection of a person record (in expecco: a compound datatype instance), with element keys "firstName", "lastName" and "city", you can generate a nice printed string with:

   |record|

   record := Dictionary new.  "/ example using a dictionary
   record at:'firstName' put: 'Fritz'.
   record at:'lastName' put: 'Müller'.
   record at:'city' put: 'Stuttgart'.

   '%(lastName), %(firstName) lives in %(city)' bindWith: record.

gives you the string: 'Müller, Fritz lives in Stuttgart'. In combination with a expecco compound datatype instance, this would be:

   |person|

   person := myType new.
   person firstName:'Fritz'.
   person lastName:'Müller'.
   person city:'Stuttgart'.
 
   '%(lastName), %(firstName) lives in %(city)' bindWith: person.

Formatting Strings with Printf (Method B)[edit]

There is also a formatting method similar to "printf". It takes a format string and a number of arguments and constructs a string representation. As Smalltalk does not support variable numbers of arguments, these must be passed with different keyword messages:

   formatString printfWith: arg

   formatString printfWith: arg1 with: arg2

   formatString printfWith: arg1 with: arg2 with: arg3
   ... up to 5 arguments

   formatString printf: argVector

For the argVector, use the "{" .. "}" construct.

For example,

   '%05d 0x%-7x %f' printf:{ 123 . 517 . 1.234 }

generates the string:

   '00123 0x205     1.234'

The above provides the functionality of C's "sprintf" - i.e. it generates a string as result. However, streams also understand printf-messages, so you can also print directly to a stream:

   Transcript printf:'%05x\n' with:12345

or:

   Transcript printf:'%05x %d %f %o\n' withAll:{ 123. 234*5. 1.234. 254 }

Notice, that printf translates the standard C-character escapes "\n", ”\t" etc.

Regex and Glob Matching[edit]

Both match a string against a match pattern, but the syntax and functionality is different.

  • Glob is much easier to use but less powerful. Glob is the match algorithm used in the Unix and MS-DOS filesystem (e.g. when saying "ls *.foo" or "dir *.foo" on the command line).
  • Regex is the algorithm used in tools like "grep" and many text processing systems and languages.

Notice that regex patterns are different and not just a superset of glob patterns. Especially the meanings of "*" and "." are different.
Please consult Wikipedia for more information on Regex [1] and GLOB [2].

For a Glob match, use:

   aGlobPattern match: aString

   aGlobPattern match: aString caseSesitive: false

for a regex match, use:

   aString matchesRegex: aRegexPattern

   aString matchesRegex: aRegexPattern caseSensitive: false

   aString matchesRegex: aRegexPattern ignoringCase: true  "/ an alias to the above

Sorry for the inconsistent method naming which is due to the fact that the regex matcher originated in a public domain package, which was developed independently from the original ST/X system. In order to remain compatible with other Smalltalk dialects, the names were kept.

Code Converting[edit]

There are code converters for various character encodings in and below the CharacterEncoder class. Most of them are obsolete or seldom used these days, as now most systems support Unicode (which was not the case, the ST/X was written 30 years ago and switched to Unicode in the 90's!).

In general, you can get an instance of an encoder via a convenient CharacterEncoder interface:

   CharacterEncoder encoderFor:<encoding>

or:

   CharacterEncoder encoderToEncodeFrom:<encoding1> to:<encoding2>

where encoding is a name, such as 'iso8859-1', 'unicode', 'utf8', 'utf16', 'ascii', 'jis7' etc.

Thus, to encode a string from Unicode (which is the internal encoding anyway) to Japanese JIS7, use:

   (CharacterEncoder encode:#'jis') encode:'hello')

or to encode from Microsoft cp1253 to koi8-r, use:

   (CharacterEncoder encoderToEncodeFrom:#'cp1253' to:#'koi8-r') encode:<someStringInCP1253>

You will probably not need that general interface, but use UTF-8 these days. For those, the String class provides easier to use interface:

   aString utf8Encoded

   aString utf8Decoded

Collection Slices[edit]

A slice is a reference to a subcollection, which looks and behaves like any other collection, but shares the underlying data with the original collection. Thus modifications in either the original or slice are visible in the other and vice versa.

To get a slice, use one of the "from:", "from:to:", "to:" and "from:count:" methods (the names are similar to the above mentioned "copy*" methods).

For example:

   data := ... some big ByteArray read from a file ...

   record1 := data from: 1 to: recordSize.
   record2 := data from: recordSize+1 to: recordSize*2.
   etc.

gives you the records from the big collection as individual objects. If any modification is made to any of the slices, that modification is actually made in the underlying data collection (i.e. C-Programmers may think of this as a "pointer into the data object").

Slices are especially useful with bulk data processing, to avoid the creation of individual record copies. They are a bit dangerous, as the programmer has to be always aware of the side effects.

Stream Handling[edit]

Streams can be both internal streams (streaming out-of or into a collection) or external streams. External streams are always byte- or character-oriented and operate on files, sockets, pipes or i/o devices. An internal stream elements' type depends on the underlying collection.

The following presents only a very small excerpt of the full stream protocol. Please refer to the online documentation of the Smalltalk/X stream classes.

Creating (Internal Streams)[edit]

streams for reading:

rs := ReadStream on: aCollection.
rs := aCollection readStream

streams for writing:

ws := WriteStream on: (String new: initialSize)
ws := '' writeStream
ws := WriteStream on: (Array new)

notice that the underlying collection is reallocated when more elements are added due to nextPut: operations. The reallocations are done by doubling the size, resulting in O(n log n) complexity.

Checking[edit]

s atEnd

returns true iff the read stream "s" is positioned at the end.

s size

returns the size of the read stream's buffer, or the number of elements written into a write stream.

Positioning[edit]

s position

returns the stream's current read position, starting at 0 when at the beginning. I.e. position represents the number of elements which have already been read or written.

s position: pos

set the position.

s rewind

reset the position to the beginning

s setToEnd

set the position to the end (normally only useful for appending write streams)

s backStep

position one element backwards

s skip: n

skip n elements when reading

Reading[edit]

rs next

retrieve the next element from the stream. Nil if there are no more elements.

rs next: count

retrieve the next "n" elements from the stream.

rs nextAvailable: count

retrieve the next "n" elements or whatever number of elements are available.

rs upTo: element

retrieve elements up to an element which is equal to "element"

rs skipTo: element

skip over elements up to an element which is equal to "element"

rs through: element

retrieve elements up-to and including an element which is equal to "element"

Writing[edit]

ws nextPut: element
ws next: count put: element
ws nextPutAll: aCollectionOfElements
ws nextPutLine: aCollectionOfElements
ws print: anObject
ws println
ws println: anObject
ws contents

File Streams[edit]

aString asFilename readStream
aString asFilename writeStream

See more below in the chapter on file operations.

Numbers[edit]

Reading Numbers from a String[edit]

To read a number from a string, use the "readFrom:" method provided by the number classes, where the argument is either a string, or a headstream. A readstream is obtained with either "(ReadStream on:aString)" or the more convenient "(aString readStream)".

Thus, in general, code to read a number looks like:

   <class> readFrom: aString

or:

   <class> readFrom: (ReadStream on: aString)

where "<class>" is one of "Integer", "Float", "FixedPoint" or "Number". If "<class>" is "Number", any type of number will be read and returned. Otherwise, only that type of number will be accepted (i.e. "Integer readFrom:String", will report an error if there is a decimal point in the string.

If the string contains nothing but the number string, the above two code expressions behave the same. However, there is an often overlooked difference in case of extra characters after the number:

  • the string-reader expects that the given string contains a representation of a corresponding number object, and NO extra characters before or after it. It will report an error otherwise.
  • in contrast, the stream reader will first skip any spaces, then read as many characters as required from the stream, return the number and leave the stream positioned after the number. It will report an error if no number can be read. More values can be read from the stream if required.

Thus both:

   Integer readFrom:'1234'

and:

   Integer readFrom:(ReadStream on:'1234').

return the Integer object 1234.

Whereas:

   Integer readFrom:'1234bla'

will raise an error, but:

   Integer readFrom:(ReadStream on:'1234bla')

returns the integer 1234 and leave the stream positioned on the 'bla'.

Notice that "Integer readFrom:" will read an integer, but not a float. Thus:

   Integer readFrom:'1234.5'

will raise an error, but:

   Integer readFrom:(ReadStream on:'1234.5').

will return the Integer object 1234 and leave the stream positioned on the decimal point character.

When expecting an arbitrary number (e.g. with or without decimal point), use:

   Number readFrom: aStringOrStream

which will return either an integer object or a float object depending on what it gets.

If you want to enforce getting a float, use

   Float readFrom: aStringOrStream

which returns the float "1234.0", even when given the string "1234".

Error handling[edit]

By default, the readers raise an exception, if any conversion error occurs. This can be caught in an error handler as described elsewhere (hint: catch the error with "on:do:").

However, the conversion routines can also be given a correction function as argument, which is invoked in case of an error, and which provides a replacement value. This is easier and shorter to write then an exception handler.

For this, all of the methods described are also present in a variant with an extra "onError:" argument, which provides that replacement.

For example:

   Integer readFrom: aString onError:[ 0 ]

will return the number as usual if OK, but zero if not. Of course, arbitrary code can be provided inside this handler - especially it may show an error dialog and ask the user for a replacement, or return from the executed elementary block:

   val := Integer
           readFrom:aString
           onError:[
               |ersatz|
               ersatz := Dialog request:'Please enter a correct number'.
               Integer readFrom:ersatz
           ]
Reading with a Different Decimal Point Character[edit]

If the number was originally for a european, it may contain a decimal point different from "." - for example, in Germany you may encouter a monetary amount as "1245,99".

For this, use:

   Float readFrom: aStringOrStream decimalPointCharacters:','

or (better):

   FixedPoint readFrom: aStringOrStream decimalPointCharacters:','

If your code has to deal with both US and German numbers, provide all possible decimal points in the string, as in:

   FixedPoint readFrom: aStringOrStream decimalPointCharacters:',.'
Reading Multiple Numbers from a Stream[edit]

A common task is to read multiple numbers from a single long string. Of course, you could first split the string into pieces and use the above "readFrom:" on each.

This may also be a quick&dirty solution, if the individual number strings are contained in fixed-length fields and there are no separators in between the fields (but read below for a better way to do this). Take a look at the string handling examples on how to split strings.

To read multiple numbers, the best solution is to first create a read stream on the string, then read the individual numbers:

   myString := '1234 456.7 0.5 2345.3456 3234234234234234234234234234234234234'.
   myStream := myString readStream.
   n1 := Number readFrom: myStream.      "/ gives the integer 1234 in n1
   n2 := Number readFrom: myStream.      "/ gives the float 456.7 in n2
   n3 := Number readFrom: myStream.      "/ gives the float 0.5 in n3
   n4 := FixedPoint readFrom: myStream.  "/ gives the fixedPoint 2345.3456 in n4
   n5 := Number readFrom: myStream.      "/ gives the large integer 3234...34 in n5

Notice that "readFrom:" first skips any separators (spaces) - therefore no extra code is needed to deal with those.

If extra stuff needs to be skipped, use any of the existing stream functions (skipFor:/skipUntil: etc.). If none does the job, you can look at individual characters and skip until a match is found. For example, to read 3 numbers from the following string '123 bla 456.8|0.5', you could use:

   s := '123 bla 456.8|0.5' readStream.
   n1 := Integer readFrom:s.
   s skipSeparators.
   s nextAlphanumericWord.  "/ skips over the 'bla'
   n2 := Number readFrom:s.
   s next.                  "/ skips over one character
   n3 := Number readFrom:s.

of course, the above code would not handle strings like '123 bla bla 456.8|0.5' or '123 bla 456.8 |0.5'.
Using "peek", which looks at the next character in the stream without consuming it, we could to change the code to:

   n1 := Integer readFrom:s.
   [ s peek isDigit ] whileFalse:[ s next ]. "/ skips over the 'bla bla'
   n2 := Number readFrom:s.
   s next.                                   "/ skips over one character
   n3 := Number readFrom:s.

Notice that "peek" returns nil, if the end of the stream is reached. And that "nil isDigit" will report an error (because only characters can be asked for being a digit). Thus, the code like the above is not prepared to read a variable number of numbers.

Reading with Scanf[edit]

There is also a C-like scanf utility, which can read numbers, strings, characters in various formats.

   myString := '1234 456.7 0.5 2345.3456 3234234234234234234234234234234234234'.
   values := '%d %f %f %f %d' scanf:myString.

generates a collection of numbers in "values":

   OrderedCollection(1234 456.7 0.5 2345.3456 3234234234234234234234234234234234234)

The details and format characters are described in the PrintfScanf utility class, here is a short summary:

Conversions (upper case same as lower case):

  • 'b' binary (base 2)
  • 'c' character or (first char of string)
  • 'd' decimal
  • 'e' float
  • 'f' float
  • 'g' float
  • 'i' integer (alias for 'd')
  • 'o' base-8 octal
  • 's' string
  • 'u' integer
  • 'x' base-16 hex

Length prefix:

  • 'h' with float formats: reads as ShortFloat
  • 'L' with float formats: reads as LongFloat

Examples:

  • '%d %x' scanf:'1234 ff00' -> OrderedCollection(1234 65280)
  • '%d %s' scanf:'1234 ff00' -> OrderedCollection(1234 'ff00')
  • '%d %x %b' scanf:'1234 ff00 1001' -> OrderedCollection(1234 65280 9)

Reading a Particular Number of Numbers from a Stream[edit]

Assume that you are given a number which defines how many numbers to extract from a given string.

Of course, you can use a "start to: stop do:[:idx |..." loop, but experienced Smalltalk programmers make use of the collection protocol, as in:

   (1 to: count) collect:[:idx | Number readFrom: aStream ]

Such a construct is needed e.g. when you have to parse a string, where the first entry defines how many numbers follow:

   coll := (1 to: (Integer readFrom: myString)) collect:[:idx | Number readFrom: aStream ].

which would parse the string "5 1.0 3.0 2.0 -1.0 .05" into a 5-element collection containing the converted numbers.

Reading a Variable Number of Numbers from a Stream[edit]

To do so, we should collect numbers as being read from the stream in a collection:

   collectedNumbers := OrderedCollection new.

   [ aStream atEnd ] whileFalse:[
       collectedNumbers add: ( Number readFrom: aStream ).
   ]

or, if any non-digits and non-spaces between numbers should be skipped, use:

   collectedNumbers := OrderedCollection new.

   [ aStream atEnd ] whileFalse:[
       aStream skipUntil:[:char | char isDigit].
       collectedNumbers add: ( Number readFrom: aStream ).
   ]

Using the Pattern Matcher to Extract Parts[edit]

In rare situations, complex patterns need to be matched and numeric values retrieved from the matched parts. For this, use a regex pattern matcher to first extract parts from the string, and then convert the substrings. For example, to fetch numbers after certain keywords in a string, you could use:

   "/ assuming that the string is of the form:
   "/    <..arbitrary-text...>key1<number1><...arbitrary text...>key2<number><...arbitrary text...>
   "/ where arbitrary text may even contain key1 and key2, but not followed by a number,
   "/ we first use a regex to match, then convert the matched regex parts:

   myString := 'bla bla key1 bla key11234more bla key2 bla key29876bla bla'.

   parts := myString allRegexMatches:'((key[12]))([0-9]+)'.
   numbers := parts collect:[:eachPart | Number readFrom: (eachPart copyFrom:5) ].

notice that parts delivers the matched strings i.e. the collection ('key11234' 'key29876'), so that we have to skip over the 'keyX' prefix before extracting the numbers.

File Operations[edit]

Expecco (i.e. Smalltalk) represents file names as instances of a specialized class called "Filename". These look similar to strings, but provide an abstraction over details of the underlying file- and operating system. This makes it possible to write code which runs on Windows, Linux and even VMS, even though these systems use very different filename separators and volume naming schemes.

Thus, we recommend to use filename operations instead of string concatenation, e.g. to construct pathnames. In Smalltalk, the abstract class "Filename" is subclasses by concrete classes named "PCFilename" or "UnixFilename". You should never access those concrete classes by name explicitly! Instead, always refer to "Filename" and let Smalltalk decide, which concrete class to use.

for now, a short summary:

   aString asFilename  - to get a Filename-instance for a given String instance 
   aFilename readStream - to get a readStream on the contents of a file
   aFilename writeStream - to get a writeStream writing to a file
   aFilename appendingWriteStream - to get a writeStream appending to a file

Checking[edit]

   aFilename exists - true if the file exists
   aFilename isReadable - true if it is readable
   aFilename isWritable
   aFilename isExecutable - for folders, this means: "can be changed into"
   aFilename isExecutableProgram
   aFilename fileSize
   aFilename isDirectory
   aFilename isNonEmptyDirectory
   aFilename isRegularFile
   aFilename isSymbolicLink
   aFilename info - gets all info; follows any symbolic link. Includes ownerID, groupID, access and modification times etc.
   aFilename linkInfo - gets all info of a symbolic link itself. Includes ownerID, groupID, access and modification times etc.
   aFilename accessTime
   aFilename modificationTime
   aFilename creationTime (same as modificationTime on Unix systems)
   aFilename fileType
   aFilename mimeType

File names[edit]

   aFilename pathName
   aFilename directory - the containing directory, as a Filename instance
   aFilename directoryName - ditto, as string
   aFilename baseName
   aFilename suffix
   aFilename hasSuffix: aString
   aFilename isAbsolute
   aFilename isRelative
   aFilename / subPath - yes, "/" is an operator to construct the name of a file inside a folder

alternative:

   aFilename construct: subPath

Operations[edit]

   aFilename makeDirectory
   aFilename recursiveMakeDirectory - ensures that all needed intermediate folders along the path are also created.
   aFilename removeDirectory - raises an error, if the folder is not empty
   aFilename recursiveRemoveDirectory - removes everything below also
   aFilename moveTo: newName
   aFilename copyTo: newName
   aFilename recursiveCopyTo: destinationName


Directory Contents[edit]

   aFilename directoryContents
   aFilename directoryContentsDo:[:eachFile | ...]
   aFilename filesMatching: aGLOBPatternString

File Contents[edit]

   aFilename contents

retrieves the contents as a collection of line strings. Be careful - this should not be used for huge files.

   aFilename contentsAsString

retrieves the contents as one (possibly big) string. Be careful - this should not be used for huge files.

   aFilename binaryContentsOfEntireFile

retrieves the contents as one (possibly big) byte array. Be careful - this should not be used for huge files.

   aFilename readingLinesDo:[:eachLineString | ... ]

better use this for large files. Ensures that the file is closed.

   aFilename readingFileDo:[:stream | ... ]

or that to read inside the code block. Ensures that the file is closed.

Shared Data and Synchronization[edit]

A number of mechanisms for synchronisation and mutual access to shared data structures are available: Semaphore, RecursionLock, Monitor, SharedQueue, SharedCollection and the synchronized: method, which is understood by every object. For details, please refer to the Smalltalk/X Online Documentation.

Examples[edit]

The following code examples contain versions for Smalltalk, JavaScript, Groovy and Node.js. Notice that Groovy and Node.js actions are invoked via a remote procedure call (RPC) mechanism, which means that they are much slower due to the communication, encoding, decoding and general round trip delays. Therefore, Groovy and Node.js blocks (bridged actions in general) should only be used to access data inside the system under test or to call functions which are not available in the base system (such as additional protocols or hardware access, for which only a JAR or node- or python package is available).

Thus, most of the examples below are for demonstration purposes, not for real world scenarios (i.e. it will certainly slow down your test runs, if you do math via Groovy or even Node.js, as shown below).

Also bear in mind, that the underlying numeric representations are less flexible and more error-prone if you do math outside Smalltalk: in Groovy, because there will be no signalling of overflow/underflow situations. Especially with integer arithmetic, which may be outside the 32bit/64bit range. Also, Java has no exact fraction representations, which means that you may loose precision in floating point operations.

Things are even worse in Node.js, which represents all numbers as double precision floats, giving roughly 53 bits of precision. Thus, in Node.js the two integers 12345678901234567890 and 12345678901234567891 will be reported as being equal!

Point right.png Do NOT use Groovy or Node.js for numeric computations, if large integer values are involved.

Reading/Writing Pin Values[edit]

Assuming that the block has two input pins, named "in1" and "in2" and two output pins, named "out1" and "out2", all of String type, the following blocks write concatenated strings to both outputs:

JavaScript[edit]

(runs inside expecco when executed)

execute() {
    out1.value( in1.value() + in2.value() );
    out2.value( in2.value() + in1.value() );
}
Smalltalk[edit]

(runs inside expecco when executed)

execute
    out1 value:(in1 value , in2 value).
    out2 value:(in2 value , in1 value).

Groovy[edit]

(runs inside the JVM, which may be the system under test or another machine running Java)

def execute() {
    out1.value( in1.value() + in2.value() );
    out2.value( in2.value() + in1.value() );
}
Node[edit]

(runs inside a node VM, which may be the system under test or another machine running "node")

function execute() {
    out1.value( in1.value() + in2.value() );
    out2.value( in2.value() + in1.value() );
}
Python[edit]

(runs inside a Python interpreter, which may be the system under test or another machine running "python")

def execute():
    out1.value( in1.value() + in2.value() )
    out2.value( in2.value() + in1.value() )
C[edit]
static void execute() {
    char* s1 = stringValue(in1);
    char* s2 = stringValue(in2);
    char buffer[512];

    snprintf(buffer, sizeof(buffer), "%s%s", s1, s2);
    putString(out1, buffer);

    snprintf(buffer, sizeof(buffer), "%s%s", s2, s1);
    putString(out1, buffer);
}

Note: be aware, that the calling overhead of bridged actions is in the order of milliseconds, whereas actions executed inside expecco (Smalltalk and JavaScript) are executed typically within a few hundred nanoseconds. Thus, bridged actions are typically used to invoke longer running, complex operations or to trigger services, which run autonomous.

Reading/Writing Environment Variables[edit]

Assuming that a String-typed environment variable named "IncDec" and an Integer-typed variable named "Counter" exist in the project's environment, and are writable, the following blocks read and write to either variable:

JavaScript[edit]

(runs inside expecco, when executed)

execute() {
    if ( environmentAt("IncDec") == "inc" ) {
        environmentAt_put("Counter", environmentAt("Counter") + 1);
    } else {
        environmentAt_put("Counter", environmentAt("Counter") - 1);
    }
}
Smalltalk[edit]

(runs inside expecco, when executed)

execute
    (self environmentAt:'IncDec') = 'inc' ifTrue:[
        self environmentAt:'Counter' put:(self environmentAt:'Counter')+1.
    ] ifFalse:[
        self environmentAt:'Counter' put:(self environmentAt:'Counter')-1.
    ]
Groovy[edit]

(runs inside the JVM, which may be the system under test or another machine running Java). Notice that the environment-access from inside Groovy involves even more overhead, as those require another RPC call from Groovy back to expecco. When executed, the code below will make 4 full remote-procedure call roundtrips (1 for the call and return value, one for "environmentAt(IncDec)", one for "environmentAt(Counter)" and a fourth one for "environmentAt_put()").

def execute() {
    if ( environmentAt("IncDec") == "inc" ) {
        environmentAt_put("Counter", environmentAt("Counter") + 1);
    } else {
        environmentAt_put("Counter", environmentAt("Counter") - 1);
    }
}

Sending Messages to the Transcript[edit]

The Transcript refers to the expecco console, if it has been opened (via the "Extras" - "Tools" menu). If it is not open, "Trnscript" will refer to the standard error stream (Stderr). In place of "Transcript", the following examples also work for "Stdout" and "Stderr".

JavaScript[edit]

(runs inside expecco, when executed)

execute() {
    Transcript.showCR("---------------------");
    for (var y=1; y<=10; y++) {
        for (var x=1; x<=10; x++) {
            Transcript.show( x * y );
            Transcript.show( " " );
        }
        Transcript.cr();
    }
}
Smalltalk[edit]

(runs inside expecco, when executed)

execute
    Transcript showCR:'---------------------'.
    1 to:10 do:[:y |
        1 to:10 do:[:x |
            Transcript show:(x * y); show:' '.
        ].
        Transcript cr.
    ].
Groovy[edit]

A few of the common messages and objects for logging and printing are also made available to Groovy code. Of course, a hidden inter-process mechanism is used, which forwards those calls back to expecco (remember: the Groovy code runs inside the system under test). The Transcript object seen by Groovy code is such a proxy object which implements a subset of the Smalltalk Transcript object but passes the argument strings via IPC back to expecco. So although the code looks similar to the above, the internal implementation (and timing) is completely different, because these calls are implemented as RPC calls from Groovy back to expecco.

(runs inside JavaVM when executed)

def execute() {
    Transcript.showCR("---------------------");
    for (int y=1; y<=10; y++) {
        for (int x=1; x<=10; x++) {
            Transcript.show( x * y );
            Transcript.show( " " );
        }
        Transcript.cr();
    }
}

For compatibility with existing Java/Groovy code, the functions print() and println() can also be used (in addition to the above used show(), showCR() and cr() functions)

Node[edit]

A subset of the logging and printing functions are available to Node code. Of course, a hidden inter-process mechanism is used, which forwards those calls back to expecco (remember: the code runs inside the system under test). The Transcript object seen by Node code is such a proxy object which implements a subset of the Smalltalk Transcript object, but passes the argument strings via IPC back to expecco.

So although the code looks similar to the above, the internal implementation (and timing) is completely different, because these calls are implemented as RPC calls from Node back to expecco.

(runs inside Node when executed)

function execute() {
    Transcript.showCR("---------------------");
    Transcript.show( "hello " );
    Transcript.show( "world" );
    Transcript.cr();
    Transcript.show( "A message with embedded args: %1 and %2\n", "foo", 123);
}

For compatibility with existing Node programs, you can also use "console.log()" and "console.error()".

Square Root Block[edit]

A function, which computes the square root of its input value, could be implemented like that: (the names of the pins are: in and out, their data type is Number):

JavaScript[edit]

(runs inside expecco, when executed)

execute() {
    var inValue;

    inValue = in.value;          // Reading input pin value
    out.value( inValue.sqrt() ); // Writing to output pin
}

alternative:

execute() {
    var inValue;

    inValue = in.value;              // Reading input pin value
    out.value( Math.sqrt(inValue) ); // Writing to output pin
}
Smalltalk[edit]

(runs inside expecco, when executed)

execute
    |inValue|

    inValue := in value.       "/ Reading input pin value
    out value: inValue sqrt.   "/ Writing to output pin
Groovy[edit]

(runs inside a JVM/the SUT, when executed).
As mentioned above, this kind of operation should definitely be executed in Smalltalk/JavaScript inside expecco, and not in the system under test, due to the RPC overheads.

def execute {
    Object inValue;

    inValue = inPin.value();           // Reading input pin value
    outPin.value(Math.sqrt(inValue));   // Writing to output pin
}

(Notice: "in" is a reserved keyword in Groovy and cannot be used as pin name)

Node[edit]

(runs inside Node, when executed).

function execute {
    outPin.value(Math.sqrt(inPin.value()));  
}

(Notice: "in" is a reserved keyword in Node and cannot be used as pin name)

Random-Fail Block[edit]

A block, which randomly fails (to demonstrate exception handling), could be implemented like:

JavaScript[edit]

(runs inside expecco, when executed)

execute() {
    var dice;

    dice = Random.nextIntegerBetween_and_(1,6);
    if (dice <= 2) {
        fail();
    }
}
Smalltalk[edit]

(runs inside expecco, when executed)

execute
    |dice|

    dice := Random nextIntegerBetween:1 and:6.
    (dice <= 2) ifTrue:[
        self fail.
    ].
Groovy[edit]

(runs inside a JVM/the SUT, when executed)

execute() {
    Random rand = new Random();
    int dice = rand.nextInt(5)+1;

    if (dice <= 2) {
        fail();
    }
}

Calling other Actions[edit]

Script code can call other action blocks via the "call" method/function. Notice, that the call is "by name" or "by ID", and the called function is specified by a string argument. The following examples assume that action blocks named "Action_A" and "Action_B" are present, and that "Action_B" expects two numeric arguments and returns a value through its single output pin.

JavaScript[edit]

(runs inside expecco, when executed)

execute() {
    var result;

    call("Action_A");
    result = call("Action_B", 123.0, 9999); 
    Transcript.showCR("got result: %1", result);
}
Smalltalk[edit]

(runs inside expecco, when executed)

execute
    |result|

    self call:'Action_A'.
    result := self call:'Action_B' _:123.0 _:9999). 
    Transcript showCR:('got result: %1' bindWith:result).
Groovy[edit]

runs inside a JVM/the SUT, when executed, but the called actions are expecco actions; i.e. the call performs a remote-procedure-call back to expecco, to execute the called action, and continue in the JVM's action when finished.

execute() {
    var result;

    call("Action_A");
    result = call("Action_B", 123.0, 9999); 
    Transcript.showCR("got result: %1", result);
}
Node[edit]

runs inside the node interpreter, when executed. The call performs a remote-procedure-call back to expecco, to execute the called action, and continue in the node action when finished.

Notice, that due to the single threaded callback oriented nature of node, the value from the called function is passed back via a callback function, not as return value. This is required because the "call" is actually performing a remote procedure call into expecco (i.e. interprocess communication). It is also currently not possible to call another node action from a node action (neither directly, nor indirectly).

function execute() {
    var result;
    
    Transcript.showCR("calling from Node...");
    call("Action_A", function(err, result) {
        call("Action_B", 100, 200, function(err, result) {
            Transcript.showCR("got value: %1", result);
            success();
        });
    });
}

Using the Smalltalk/X Class Library (Smalltalk and JavaScript Blocks Only)[edit]

Please note, that besides these direct interfaces with the expecco system described here, you can also use the whole class library of the runtime system. Please check the corresponding Smalltalk/X Documentation as well as the Documentation of the Class APIs.

expecco includes a fully featured class browser, to explore the underlying system's code.

You will see the source code, if you have checked the "Install Source Code" checkbox during the installation procedure (if you did not, redo the installation, and only check this box). You can also access the source code via the public eXept CVS repository.

The class browser is opened via the main-menu's "Extras"-"Tools"-"Class Browser" item. It is documented in full detail in the Smalltalk/X Documentation.


Here are a few more examples, using that class library:

Bulk-Data Reading from a File[edit]

The following code reads a measurement data block of 100000 floating point values from file. The file was created beforehand by a recorder device, which was triggered by a dll-callout. For the demonstration, the code below does not read its values from input pins. In a real world application, the size of the file and its fileName would probably be passed in via input pins, of course.

JavaScript[edit]
execute() {
    var N;  // should be read from an input-pin
    var fileName; // should be read from an input-pin
    var dataArray;
    var fileStream;

    N = 100000;
    fileName = 'dataFile.dat';
    fileStream = fileName.asFilename().readStream();
    dataArray = Array.new(N);
    for (i=1; i<=N; i++) {
        dataArray[i] = fileStream.nextIEEESingle();
    }
    out.value(dataArray);
}
Smalltalk[edit]
execute
    |N fileName dataArray fileStream|

    N := 100000.
    fileName := 'dataFile.dat'.
    fileStream := fileName asFilename readStream.
    dataArray := (1 to:N) collect:[:i | fileStream nextIEEESingle].
    out value:dataArray.

an alternative is:

    ...
    dataArray := N timesCollect:[:i | fileStream nextIEEESingle].
    ...

timesCollect:

Opening/Using Smalltalk/X Applications, Dialogs and Components[edit]

Any existing Smalltalk/X utility (both the one's which are provided with the base system, and even those coming from additional loaded Smalltalk code) can be called and used. For example, the following uses the builtin file dialog to ask for a directory.

JavaScript[edit]
execute() {
    var answer;

    answer = Dialog.requestDirectory("Please select an output folder:");
    if ( answer.notEmptyOrNil() ) {
        out.value( answer );
    }
}
Smalltalk[edit]
execute
    |answer|

    answer := Dialog requestDirectory: 'Please select an output folder:'.
    answer notEmptyOrNil ifTrue:[
        out value: answer
    ]

Loading Additional Smalltalk/X Code[edit]

You can even create whole library or application packages, compile it as separate package, and load it dynamically to be used from within an elementary block. Assuming that a package named "myPackage.dll" (or "myPackage.so" under Unix/Linux) containing an application class named "MyApplication" has been created and is located in the expecco bin folder, the code can be loaded and executed with:

Smalltalk[edit]
execute
    |answer|

    Smalltalk loadPackage:'myPackage'. "/ checks if already loaded, to only load the first time called
    MyApplication open.

To create your own applications or addons, download the free Smalltalk/X development IDE for development and package building.

Expecco and Smalltalk API Stability[edit]

The Smalltalk/X class library has been around for more than 30 years now, and of course grew over time. We have always tried hard to keep the API backward compatible, in not removing existing methods, even when new methods appeared which provided a similar or superset functionality.

Thus, code written 30 years ago can still depend on the interface and it is still working unchanged.

We will continue to keep this backward compatibility in the future, to avoid breaking customer code, whenever possible (there were very few exceptions in the past, where obvious bugs had to be fixed, and customer code already depended on the wrong behavior).

That said, we must emphasize on that being only true for the ST/X class libraries themselves, and any officially published expecco APIs from this document.

If you use internal expecco functionality (which you may find when browsing in the class browser), we cannot guarantee backward compatibility in future versions. If you feel a need for using such an interface, please consult eXept to either get help on a cleaner solution, and/or to get your used interface be marked as "stable EXPECCO_API".

Appendix[edit]

Smalltalk Language Syntax (BNF)[edit]

See also: Smalltalk Short Description , Smalltalk Basics

 ;; notice: an elementary Smalltalk action's "execute" method is a unary method (no argument).
 ;; there can be only one single such method in an elementary action (private functions should be defined as Smalltalk blocks)

 method ::= selectorSpec body

 selectorSpec> ::= unarySelectorSpec
                   | binarySelectorSpec
                   | keywordSelectorSpec

 unarySelectorSpec ::= symbol

 binarySelectorSpec ::= binopSymbol identifier

 keywordSelectorSpec ::= ( keywordSymbol identifier )+

 body ::= [ "|" localVarList "|" ]  [statements]

 localVarList ::= ( identifier )+

 statements ::= statement
                | statement "."
                | statement "." statements

 statement ::= expression
               | returnStatement

 returnStatement ::= "^" expression

 expression ::= identifier ":=" expression
                | keywordExpression

 keywordExpression ::= binaryExpression
                       | binaryExpression ( keywordSymbolPart binaryExpression )+

 binaryExpression ::= unaryExpression
                      | unaryExpression ( binopSymbol unaryExpression )+

 unaryExpression ::= primary
                     | primary ( unarySymbol )+

 primary ::= identifier
             | literalConstant
             | braceArray
             | "(" expression ")"
             | block
             | "self"
             | "super"

 identifier ::= ( "_" | "a"-"z" | "A"-"Z")   ( "_" | "a"-"z" | "A"-"Z" | "0"-"9" )*

 literalConstant ::= integerConstant
                     | radixIntegerConstant
                     | floatConstant
                     | arrayConstant
                     | byteArrayConstant
                     | stringConstant
                     | characterConstant
                     | "true"
                     | "false"
                     | "nil"

 integerConstant ::= ( "0"-"9" )+

 radixIntegerConstant ::= base "r" baseDigits+

 base ::= integerConstant (valid are: 2-31)

 baseDigits := "0"-"9" | "A"-"Z" | "a"-"z" (valid are "0".."0"+base-1 and "a".."a"+base-10-1 and "A".."A"+base-10-1)

 floatConstant ::= [ "-" ] [ digits+ ] "." [ digits+ ] [ ("e" | "E" | "d" | "D") [ "-"] digits+ ]

 arrayConstant ::= "#(" literalConstant* ")"

 byteArrayConstant ::= "#[" integerConstant(0..255)* "]"

 stringConstant ::= " ' " character* " ' "

 characterConstant ::= "$" character

 braceArray ::= "{" [ expression | expression ( "." expression )+ ] "}"

 block ::= "[" [ blockArgList ] body "]”

 blockArgList ::= (identifier":" )*

 comment ::= " " " any-character* " " " | eolComment

 eolComment ::= " "/ " any-character-up-to-end-of-line

 unarySymbol ::= identifier

 binopSymbol ::= ( "+" | "-" | "*" | "/" | "\" | "," | "@" | "%" | "&" | "=" | "<" | ">" | "~" )+

 keywordSymbolPart ::= identifier":"

 keywordSymbol ::= keywordSymbolPart+

 symbolConstant ::= "#"unarySymbol
                    | "#"binarySymbol
                    | "#"keywordSymbol
                    | "#'"any-character*"'"

JavaScript Language Syntax (BNF)[edit]

-- incomplete w.r.t. expression syntax -- to be added

 ;; notice: an elementary JavaScript action's "execute" function must be a unary function (no argument).
 ;; however, it is possible to define additional (private) helper functions. 
 ;; These will only be visible within that single elementary action's code.

 method ::= functionSpec functionBody

 functionSpec ::= identifier "("  [argList] ")"

 functionBody ::= "{" (statement)+ "}"

 statement ::= varDeclaration
                      | ifStatement
                      | whileStatement
                      | doStatement
                      | forStatement
                      | tryCatchStatement
                      | switchStatement
                      | returnStatement
                      | "{" (statement)+ "}"
                      | expression

 varDeclaration ::= "var" identifier ( ","  identifier)* ";"

 ifStatement ::= "if" "(" expression ")" statement
                         [ "else" statement ]

 whileStatement ::= "while" "(" expression ")" statement
 
 doStatement ::= "do" statement "while" "(" expression ")"  ";"

 forStatement ::= "for" "(" [expression] ";" [expression] ";" [expression] ")" statement

 tryCatchStatement ::= "try" statement (catchClause | finallyClause | catchFinallyClause)

 catchClause ::= "catch"  "(" identifier [identifier] ")" statement

 finallyClause ::= "finally"  statement

 catchFinallyClause ::= catchClause finallyClause

 switchStatement ::= "switch" "(" expression ")" "{" ("case constantExpression ":" statement)+ [ "default:" statement+ "}"

 returnStatement ::= "return" expression [ "from" identifier ]

 expression ::= term ( ("+" | "-") term)*

 term ::= factor ( ("*" | "/" | "%" ) factor)*

 factor ::= powExpr ( "**"  powExpr)*

 powExpr ::= "typeof" "(" expression ")"
                | "!" factor
                | "-" factor
                | "++" factor
                | "--" factor
                | primary "--"
                | primary "++"

 primary ::= identifier
                   | literalConstant
                   | arrayExpression
                   | "(" expression ")"
                   | block
                   | "this"
                   | "super"
                   | "new" classIdentifier [ "(" constant ")" ]
                   | functionDefinition
                   | lambdaFunction

 functionDefinition ::= "function" [functionName] "("  [argList] ")" functionBody

 lambdaFunction ::= "("  [argList] ")" "=>" functionBody
                   | arg "=>" functionBody
                   | "("  [argList] ")" "=>" expression
                   | arg "=>" expression
 
 identifier ::= ( "_" | "a"-"z" | "A"-"Z")   ( "_" | "a"-"z" | "A"-"Z" | "0"-"9" )*

 literalConstant ::= integerConstant
                              | radixIntegerConstant
                              | floatConstant
                              | arrayConstant
                              | byteArrayConstant
                              | stringConstant
                              | characterConstant
                              | "true"
                              | "false"
                              | "null"

 integerConstant ::= ( "0"-"9" )+

 radixIntegerConstant ::= hexIntegerConstant | octalIntegerConstant | binaryIntegerConstant

 hexIntegerConstant ::= "0x"hexDigit+

 hexDigit := "0"-"9" | "A"-"F" | "a"-"f"

 octalIntegerConstant ::= "0"octalDigit+

 octalDigit := "0"-"7"

 binaryIntegerConstant ::= "0b"binaryDigit+

 binaryDigit := "0" | "1"

 floatConstant ::= [ "-" ] [ digits+ ] "." [ digits+ ] [ ("e" | "E" | "d" | "D") [ "-"] digits+ ]

 arrayConstant ::= "[" [literalConstant ("," literalConstant)*] "]"

 arrayExpression ::= "[" [expression ("," expression)*] "]"

 stringConstant ::= apos character* apos 
                  | quote character* quote

 characterConstant ::= "$" character

 apos ::= "'"' (single quote)

 quote ::= " " " (double quote)

 comment ::= "/*" any-character* " */ " | eolComment

 eolComment ::= " // " any-character-up-to-end-of-line

Notice the nonstandard extension for single character constants


Back to Online_Documentation#Code_API_Overview Online Documentation



Copyright © 2014-2018 eXept Software AG