Doing More with Identifiers


This chapter discusses in more detail the various predefined identifier types available within the Fire language.

Numeric Identifiers

A numeric identifier records a single numeric value. A numeric array records multiple numeric values. All numeric values are stored internally in floating-point double precision.

A numeric identifier is declared by the command numeric:

# A single numeric identifier (a scalar)
numeric x
# A numeric fixed-length array of 10 elements
numeric a[10]
# A numeric variable-length array, initially of zero length
numeric b[]
# A numeric variable-length array, initially of length 5
numeric nnn[-5]

Numerics are initialized to zero.

A scalar numeric is subsequently referenced by just its name.

# Print the value of x
tell x

A numeric array element is referenced by its array name and an index in square brackets:

# Print the 5th array element of a
tell a[5]
# Print the last array element of a
tell a[*]

A range of array elements is referenced by the array name and an index range, e.g.

# Print the first 10 elements of the array b
tell b[1:10]
# Print elements 5 to the last of the array b
tell b[5:*]
# Print elements 2 to the penultimate of the array b
tell b[2:*-1]
# 3 ways to print all elements of b
tell b
tell b[]
tell b[1:*]

System Numerics

There are many predefined system numerics with global scope accessible to the user. They are listed in the Reference Manual, in the section System Identifiers.

Some system numerics have values which can be changed, e.g. dec_places, the precision used when displaying numeric values. Others have read-only values, e.g. pi the constant π.

There are also predefined numeric functions which, when given parameters, yield numeric values. They are listed in the Reference Manual, in the section System Functions.

Numeric Assignments

Numeric identifiers are given values by assignments, which can be part of an identifier declaration, or a separate assignment command subsequent to the declaration:

numeric x = 10
numeric x
x = 10

Numeric arrays may be given values in a similar way but multiple items should be comma-separated within hairpin characters:

# Fixed length array
numeric xx[5] = <10,20,30,40,50>
# Variable length array
numeric a[] = <100,200>
# Assign a range (extending the array length if necessary)
a[2:4] = <25,35,45>

Numeric Expressions

A numeric expression is a combination of numeric identifiers, numeric constants, function values, parentheses, arithmetic and unary operators, and conditional expressions yielding one or more numeric values.

Numeric constants are signed numbers to the base 10. They may be integers (without a decimal point), decimal numbers (with a decimal point) or floating-point numbers in E notation:

123
31.55
62.5E8

Arithmetic operators permitted within a numeric expression are those one would expect:

+ add
- subtract
* multiply
/ divide
** exponentiate

Unary operators (+ or -) provide the sign of a numeric expression and are applied to the expression immediately following the operator:

-7
-x/2

Parentheses are used to control evaluation precedence and may enclose all or part of a numeric expression or sub-expression:

(-1)
5 * (x + y)
-(sqrt(50) + 5)

Identifiers permitted within numeric expressions are as follows:

Numeric scalar yielding one value, e.g. x
Numeric array yielding as many values as are in the array, e.g. xar
Numeric array range yielding as many values as specified, e.g. xar[2:5]
Point scalar yielding three values (the X, Y and Z coordinates), e.g. my_point
Point array yielding the array length times 3 values, e.g. parray
Point array range yielding the specified length times 3 values, e.g. parray[1:4]
Function returning one or multiple numeric values, e.g. max(a,b,c)

Point identifiers are permitted only within array assignments:

point my_point = (50,100,200)
numeric xar[3] = my_point

When multiple values result but a single value is required, only the first value is used.

Conditional expressions are expressions enclosed within square brackets [ and ] which yield a boolean value of 1 if they are true, and 0 if they are false. They are seldom used within complex expressions, but are useful in simple assignments when you require just a boolean value:

numeric gr_than_50 = [x > 50]

Consult the discussion on Conditional Expressions for more uses of numeric values within conditions.

Operator precedence within numeric expressions is: [ ], ( ), - or + (unary), **, * or /, - or +

Numeric Function Values

The number of values returned by a numeric function may vary but most system functions return one value. A complete list of in-built functions and their parameters is given in the Reference Manual, in the section System Functions.

Examples:

# Find the square root of x
numeric sx = sqrt(x)
# Find the maximum of 2 values
numeric mx = max(x,y)
# Test whether a file exists
numeric isthere = fexist('c:/temp/myfile.dat')
# Get a screen point interactively
numeric sp[]= scur()

The last function has no parameters. This is permitted when they are optional, or when the function does not require any. In such cases the empty parentheses may be omitted.

Functions may return multiple values for array assignment purposes. When they do, but only a single value is required, only the first value is used.

As well as system functions, you can write your own functions which return numeric values:

numeric_function decmin {
    args t=time
    funcval = t.minute + t.secs/60
}

This function will return the decimal version of the minutes and seconds of a time passed as a parameter.

Assignment Operators

It is a common requirement to perform an amending arithmetic operation on a numeric identifier rather than a direct replacement assignment, e.g.

this.radius = this.radius * 0.75

This will multiply the radius of a graphic primitive by 0.75, but can be expressed more concisely and efficiently by use of the assignment operator *=, e.g.

this.radius *= 0.75

The available assignment operators for numerics and the assignments which they should replace are as follows:

Operation

Equivalent to

x++ x = x + 1
x-- x = x - 1
x += 5 x = x + 5
x -= 5 x = x - 5
x *= 5 x = x * 5
x /= 5 x = x / 5
x **= 5 x = x ** 5

Note: The increment and decrement operators ++ and -- may not be included within numeric expressions, but must only be used in isolation as single command statements. For example the following statement would produce a syntax error.

numeric x = y++

Assignment operations are also permitted on arrays, where the number of values on the right hand side of the assignment are applied to the corresponding number of array elements on the left-hand side. Consider this:

numeric x[] = <2,2,2,2>
x **= <2,4,8,16>

This would result in x having the values: 4,16,256,32768

To perform a constant operation on all elements of an array, known as a block assignment, the set command may be used, e.g.

numeric x[] = <2,3,4,5>
set x *= 2

This would result in x having the values: 4,6,8,10

Numeric Text Output

When numeric values are converted to text, for display or print purposes, the precision of the output is determined by the value of the system numeric identifier dec_places. Trailing zeros are suppressed.

numeric x = (10.7777)
dec_places = 2
tell x

The above will display 10.78.

The system string function ns will do the same:

numeric x = (10.7777)
dec_places = 2
tell ns(x,0)

This will display 11. As you can see values get rounded appropriately. The second parameter can be omitted, in which case dec_places is used to determine the output precision.

A similar system function fmt will create output without suppressing trailing zeros:

numeric x = 12.1
tell fmt(x,3)

The above will display 12.100.

A numeric expression may also be used within caret substitution, e.g.

!My number is ^(x)

This will use a numeric value in a quick message statement.

When the system starts, dec_places is given an initial value of 4.

String Identifiers

A string identifier stores a set of characters as a single text value. A string array records multiple text values.

A string identifier is declared by the command string:

# A single string identifier (a scalar)
string s
# A string fixed-length array of 10 elements
string sa[10]
# A string variable-length array, initially of zero length
string ss[]
# A string variable-length array, initially of length 3
string svar[-3]

Strings are initialized to text of zero length, i.e. "". In theory they have no maximum length (within reason).

A scalar string is subsequently referenced by just its name.

# Print the value of s
tell s

A string array element is referenced by its array name and an index in square brackets:

# Print the 5th array element of sa
tell sa[5]
# Print the last array element of sa
tell sa[*]

A range of array elements is referenced by the array name and an index range, e.g.

# Print the first 10 elements of the array ss
tell ss[1:10]
# Print elements 5 to the last of the array ss
tell ss[5:*]
# Print elements 2 to the penultimate of the array ss
tell ss[2:*-1]
# 3 ways to print all elements of ss
tell ss
tell ss[]
tell ss[1:*]

A sub-string is a part of the text value of a string. It is referenced by appending the % operator and a character range to the string name:

# Print the first 3 characters of s
tell s%1:3
# Print characters 8 to the last of the 4th array element of ss
tell ss[4]%8:*

It is important not to confuse string arrays with sub-strings.

System Strings

There are many predefined system strings with global scope accessible to the user. They are listed in the Reference Manual, in the section System Identifiers.

Some system strings have values which can be changed, e.g. current_dir, the name of the current working file directory or folder. Others have read-only values, e.g. machine the workstation type.

There are also predefined string functions which, when given parameters, yield string values. They are listed in the Reference Manual, in the section System Functions.

String Assignments

String identifiers are given values by assignments, which can be part of an identifier declaration, or a separate assignment command subsequent to the declaration:

string s = "Hello World"
string s
s = "Hello World"

String arrays may be given values in a similar way but multiple items should be comma-separated within hairpin characters:

# Fixed length array
string sa[5] = <"One", "Two", "Three">
# Variable length array
string espan[] = <"Uno", "Dos">
# Assign a range (extending the array length if necessary)
espan[2:4] = <"Dos", "Tres", "Quatro">

Note: When defining a string and you need the text to wrap to a new line, use \n to split the string. For example:

# Create a panel window with one 2-line button
window mywin = wpanel {
    b: button 'Button with \n Long text'
}

String Expressions

A string expression is a combination of string identifiers, string constants, function values, parentheses, string operators, and conditional expressions yielding one or more string values.

String constants are text items within quotes. Both single and double quotes are permitted but they must match in type:

'Hello World'
"Hello World"
'Hello World"

The last one is invalid because the quotes do not match.

If a character the same as the enclosing quote character is required within a string it should be escaped by a \:

'It even ain\'t'

Identifiers permitted within string expressions are as follows:

String scalar yielding one value, e.g. s
String array yielding as many values as are in the array, e.g. sarray
String array range yielding as many values as specified, e.g. sarray[2:5]
Sub-string yielding one text value, e.g. s%1:5
Function returning one or multiple string values, e.g. strim(mys)

When multiple values result but a single value is required, only the first value is used.

A stanza identifier is a variation of a string identifier and can be used wherever a string is used. Stanzas are discussed in a later section.

Consult the discussion on Conditional Expressions for uses of string values within conditional statements.

The string operator | permits strings within a string expression to be concatenated:

'abc' | 'def'

This produces the value abcdef.

The string operator & permits strings within a string expression to be concatenated with a space separator,

'abc' & 'def'

This produces the value abc def.

The string operator % permits the preceding string value in a string expression to be modified into a range of characters. We have already seen examples of this to define a substring:

s%1:4

Parentheses are used to control evaluation precedence and may enclose all or part of a string expression or sub-expression:

('abc'|'def') % 1:4

This produces the value abcd.

Operator precedence within string expressions is: ( ), %, | or &

String Function Values

The number of values yielded by a string function may vary but most system functions return one value. A complete list of in-built functions and their parameters is given in the Reference Manual, in the section System Functions.

Examples:

# Convert a string value to upper case
string supp = upp(s)
# Find the value of the environment variable $TERM (Unix), %TERM% (Windows)
string senv = envval(TERM)
# Read a single character from the keyboard
string c = kbchar()

The last function has no parameters. This is permitted when they are optional, or when the function does not require any. In such cases the empty parentheses may be omitted.

Functions may return multiple values for array assignment purposes. When they do, but only a single value is required, only the first value is used.

As well as system functions, you can write your own functions which return string values:

string_function validfile {
    args deflt=string
    funcval = deflt
    ask "Give Filename",funcval,-d=funcval
    if (fexist(funcval)) return
    tell <funcval,"does not exist">
    funcval = null
}

This function will return the name of a file obtained from the user via an on-screen ask prompt. The file is checked for validity and returned if it exists. A default file name is supplied as a parameter to the function.

Assignment Operators

It is a common requirement to perform an amending lexical operation on a string identifier rather than a direct replacement assignment, e.g.

this.text[1] = this.text[1] & 'approx.'

This will append a string constant to the first array element of this.text, but can be expressed more concisely and efficiently by use of the assignment operator &=, e.g.

this.text[1] &= 'approx.'

The available assignment operators for strings and the assignments which they should replace are as follows:

Operation

Equivalent to

s |= 'more' s = s | 'more'
s &= 'more' s = s & 'more'

Assignment operations are also permitted on arrays, where the number of values on the right hand side of the assignment are applied to the corresponding number of array elements on the left-hand side. Consider this:

string ss[] = <'A','A','A','A'>
ss |= <'A','B','C','D'>

This would result in ss having the values: AA, AB, AC, AD

To perform a constant operation on all elements of an array, known as a block assignment, the set command may be used, e.g.

string ss[] = <'A','A','A','A'>
set ss |= 'B'

This would result in ss having the values: AB, AB, AB, AB

Caret Substitution

As well as primarily for recording text data, string identifiers may be used anywhere during the language for literal text substitution. Examples:

string s = '*.dat'
dir ^(s)

This will execute the statement: dir *.dat. A more obscure version of executing the same thing is:

string s = 'dir *.dat'.
^(s)

As can be seen, the caret operator ^ introduces a phrase within parentheses. The parentheses are necessary to delimit the phrase from other characters on the command line. Although we are currently discussing string identifiers, the expression within parentheses may be any identifier whose value can be printed, e.g. a numeric identifier, a point identifier and so on.

Caret substitution is done before any other command parsing, and is done once when the command line is read, not before each of possibly multiple statements on the command line is executed. Consequently the following command line will execute the loop 10 times printing the value of s[1] each time:

for i=1,10; tell ^(s[i])

In contrast the following 2 sets of commands also loop 10 times but each prints the values of s[1], s[2] ... s[10], because there is an inherent line break associated with the { and } characters:

for i=1,10 {
    tell ^(s[i])
}

Multiple string expansion is also available, with a comma substituted between expanded strings, e.g.

string s[] = <'file1', 'file2', 'file3'>
dir ^(s)

This gets expanded into:

dir file1,file2,file3

Although overuse of this facility tends to make macros unreadable and difficult to debug, it can be a very powerful feature.

When a caret character is itself required within a string constant, it should be escaped, otherwise a string substitution will be attempted.

string s = 'Hello'
string newt = '\^(s)'

newt now contains ^(s).

Caret substitution is recursive. If an expanded string itself contains another ^ phrase, substitution will continue until there are no more carets to expand. If the expression within parentheses cannot be evaluated, no expansion is performed.

Note: One exception to the caret substitution rules occurs during the definition of a user function or procedure. Within the meat of the function, caret phrases are not expanded, but are expanded when the function or procedure gets executed. If you want a caret phrase to be expanded during definition, the command line must start with a '/' character.

Encode and Decode

Often it is useful to be able to format values into a string identifier. The encode command is useful for such a purpose, enabling a message line, discussed in the section Identifiers, to be written to a string identifier:

numeric x=45
string s
encode s,<"X is",x,"Its square root is",sqrt(x)>

The value of s will now be X is 45 Its square root is 6.7082.

Encoding into string arrays is also possible, e.g.

numeric x=45
string s[]
encode s,"X is",x,"Its square root is",sqrt(x)

s will now be a 4-element array with values: X is, 45, Its square root is and 6.7082

The encode command can also use C printf-style format statements to encode string values. Consult the Reference Manual for details.

The converse of encode is the decode command which reads values from a string identifier and assigns them to other identifiers accordingly. Consider the following:

string s = "First 20 Second 40"
string name1, name2
numeric value1, value2
decode s,name1,value1,name2,value2

Both encode and decode have a -del switch available for the specification of delimiters. Consult the Reference Manual for full details.

Time Identifiers

A time identifier records a time/date value. A time array records multiple time/date values. The recorded value is to the nearest second, unless the date is pre-1970 or post-2037 in which case the time to the nearest day is recorded.

A time identifier is declared by the command time:

# A single time identifier (a scalar)
time t
# A time fixed-length array of 10 elements
time ta[10]
# A time variable-length array, initially of zero length
time tb[]
# A time variable-length array, initially of length 5
time ttt[-5]

New time identifiers are initialized to the current date and time.

A scalar time is subsequently referenced by just its name.

# Print the value of t
tell t

A time array element is referenced by its array name and an index in square brackets:

# Print the 5th array element of ta
tell ta[5]
# Print the last array element of ta
tell ta[*]

A range of array elements is referenced by the array name and an index range, e.g.

# Print the first 10 elements of the array tb
tell tb[1:10]
# Print elements 5 to the last of the array tb
tell tb[5:*]
# Print elements 2 to the penultimate of the array tb
tell tb[2:*-1]
# 3 ways to print all elements of b
tell tb
tell tb[]
tell tb[1:*]

System Times

There are several predefined system times with global scope accessible to the user. They are listed in the Reference Manual, in the section System Identifiers.

System times have values which cannot be changed, e.g. now, the current date and time, or session_start, the time when the current Fire session was kicked off.

There are also predefined time functions which, when given parameters, yield time values. They are listed in the Reference Manual, in the section System Functions.

Time Assignments

Time identifiers are given values by assignments, which can be part of an identifier declaration, or a separate assignment command subsequent to the declaration:

time t = "14-9-1988"
time t
t = "14-9-1988"

Time arrays may be given values in a similar way but multiple items should be comma-separated within hairpin characters:

# Fixed length array
time ta[3] = < "15-10-1989", "15-11-1989", now >
# Variable length array
time tb[] = <"15-1-1990","16-1-1190">
# Assign a range (extending the array length if necessary)
tb[2:4] = < yesterday, session_start >

A time identifier may be given a null value, e.g.

time t = null

This will have the effect of setting its value to undefined (aka unset). Contrast this with not giving it a value at declaration time, which sets its value to the current time.

Time Expressions

A time expression is either a time identifier, time constant or time function value, yielding one time value.

Time constants are text strings within quotes. Both single and double quotes are permitted but they must match in type. The contents of the text string must be a date and/or time in one of the following forms:

Format

Description

Example

MM/DD/{YY}YY

Date in US form (month day year)

'1/23/1998'

DD-MM-{YY}YY

Date in European form (day month year)

'23-1-1998'

YYYY-MM-DD

Date in reverse (year month day)

'1998-1-23'

YYYY/MM/DD

Date in reverse (year month day)

'1998/1/23'

DD-MMM-{YY}YY

Date in English form (day month year)

'23-Jan-1998'

HH:MM{:SS}

Time (hour minute {second})

'14:03:59'

The date and time components may be specified in any order, separated by white space or and/or a comma, e.g.

time t = '23-Jan-1998, 14:03:59'

If the time field is omitted, then 0:0:0 is assumed. If the date field is omitted then the current date is used. Dates before 1970 (aka the epoch), or after 2037 (aka timeageddon), have no accessible time value.

Identifiers permitted within time expressions are as follows:

Time scalar yielding one value, e.g. t
Time array yielding as many values as are in the array, e.g. tarray
Time array range yielding as many values as specified, e.g. tarray[2:5]
Function returning one or multiple time values, e.g. ftime(myfile)

When multiple values result but a single value is required, only the first value is used.

Consult the discussion on Conditional Expressions for uses of time values within conditional statements.

Time Function Values

The number of values yielded by a time function may vary but most system functions return one value. A complete list of in-built functions and their parameters is given in the Reference Manual, in the section System Functions.

Examples:

# Get the last modification data of a file
time t = ftime('c:/temp/myfile.dat')

Some functions have no parameters. This is permitted when they are optional, or when the function does not require any. In such cases empty parentheses may be omitted.

Functions may return multiple values for array assignment purposes. When they do, but only a single value is required, only the first value is used.

As well as system functions, you can write your own functions which return time values:

time_function latest(f1,f2) {
   args f1=string, f2=string
   time t1 = ftime(f1), t2 = ftime(f2)
   if (t1 > t2) funcval = t1
   else funcval = t2
}

This function will return the time of the most-recently modified of two files passed as parameters.

The Time Structure

A time identifier is a system structure:

structure time {
    numeric year
    numeric month
    numeric day
    numeric weekday
    numeric hour
    numeric minute
    numeric second
    numeric ticks
    string  granularity
}

As a structure, its individual component values can be referenced in isolation. For example, today.month is the month attribute of the system time today.

Most individual attribute values may be changed:

# Create a time identifier (initialized to the current date/time)
time t
# Add 28 days to it
t.day += 28

Changing attribute values to nonsensical values may have strange consequences, but the system propagates changes up to the next attribute in the hierarchy whenever possible. For example, changing a time’s month to 13, will result in incrementing the year by 1 and setting the month to 1.

The attributes of system time identifiers (e.g. today) cannot be changed, because system identifiers have read-only values.

Most of the time structure attributes are self-explanatory, but mention should be made of the attribute time.ticks, which is a general attribute recording the time value as a single numeric value. Its value is actually the number of seconds after the epoch, which is January 1st 1970. For times before that date or beyond 2037 the time.ticks value is a negative value combining day, month and year using the following formula:

- (day + (month - 1 + (year * 12)) * 32)

Because of dates outside the epoch, it is not guaranteed that the time.ticks attribute for any two time values will be in correct chronological order. For this reason chronological comparisons should be done on the identifiers using a conditional expression, e.g.

if (t1 > t2) whatever ...

The weekday attribute is a number in the range 1 through 7 (representing Monday through Sunday) and cannot be changed. This attribute is not available for times outside the epoch and for such identifiers has a zero value.

The granularity attribute of a time identifier determines a base to which its value is always truncated. For example setting a time's granularity to hour, ensures that its minute and second attributes are always zero.

Possible values for time.granularity are second (the default), minute, hour, day, month or year.

The identifier value is rounded to this granularity whenever it changes. Values outside the epoch (pre 1970 or post 2037) have second, minute and hour values of 0 irrespective of granularity.

Time Text Output

When time values are converted to text, for display or print purposes, the format of the output is determined by the value of a system numeric identifier date_mode, which can have one of 9 values which correspond to the following formats:

date_mode

Output Format

Example

1

DD-MM-YYYY

21-11-1990

2

MM/DD/YYYY

11/21/1990

3

Day, DD MMM, YYYY

Fri 21 Nov, 1990

4

DD-MMM-YYYY

21-Nov-1990

5

DD-MMM-YY

21-Nov-90

6

YYYY-MM-DD

1990-11-21

7

YYYY/MM/DD

1990/11/21

8

Current locale date

variable

9

User format date_format

variable

For styles which display months or days in alphabetic form, e.g. styles 3,4 and 5 above, names will be reflect the language of the current locale, which may not necessarily be English.

Style 9 (User format) uses a format specified by the system string identifier date_format. This enables full customization of the output. The format is of the form used by the C function strftime. Refer to your C/C++ reference manual for details.

The text value may also be used within caret substitution, e.g.

date_mode = 3
string s = "^(now)"

This will convert a time text value into a string.

When the system starts, date_mode has an initial value of 1, and date_format is undefined.

Alias Identifiers

An alias identifier is used as an alternative name for another identifier, or identifier expression. It may be used within the language wherever an identifier is permitted. One use for an alias is to accommodate foreign languages, but is also useful when a shorthand form of an expression is required.

An alias may be used for any type of identifier with the exception of a label. It is defined and assigned a value in a similar way to a string identifier, i.e.

alias heute = 'current_date'

Whenever the alias is used in an identifier context, the system replaces it with its text value. The following 2 statements are therefore equivalent, e.g.

time t = heute
time t = current_date

There are several system aliases set up at system startup, including the following:

alias true = 'on'
alias false = 'off'
alias cd = 'path'

In addition to simple name substitution, an alias may represent an identifier expression involving structure members and/or array elements, e.g.

alias right_edge = mywin.outrect[3]

This provides an alias for the x coordinate of the right hand edge of a window mywin. In consequence the next 2 statements are equivalent:

myin2.outrect[1] = right_edge
myin2.outrect[1] = mywin.outrect[3]]

The scope of an alias may be local, application or global, but unlike string identifiers, arrays of aliases are not permitted.

An alias may reference another alias, but care should be taken to avoid self_referencing. The results are unpredictable if this is attempted.

Sometimes it is useful to be able to suppress alias expansion on a command line. This can be achieved by escaping the first character on the command line, e.g.

\time t = heute

This will not check to see if heute is an alias, so unless heute is defined elsewhere as some other time identifier this will result in a syntax error.

Deleting the identifier to which an alias refers will not delete the alias, but will result in an error the next time the alias is referenced because its alter-ego no longer exists. Deleting an alias itself is a bit tricky, and the statement must be escaped, otherwise the identifier to which the alias refers will be deleted. This is typically an undesirable result. Consider this:

# Declare a point identifier
point top_corner
# Declare an alias for it
alias p = 'top_corner'
# Delete the identifier to which p refers, i.e. top_corner
delid p
# Deletes the alias p, but does not delete top corner
\delid p

The use of an alias is very similar to caret substitution but the resulting code is usually more readable. However, when more complex substitution is required, for example multiple commands or numeric expressions, either stanza identifiers or caret substitution can usually achieve the desired result.

Stanza Identifiers

A stanza identifier provides a shorthand for one or more commands. It is defined and manipulated just like a string identifier, but can be executed just like a command and can have parameters which are substituted at execution time.

Its declaration is just like that of a string, e.g.

stanza stanza file_window = '$1 = walpha $2'

Dummy parameters are indicated in the stanza value by using $n, where n is the parameter number (1 through 9). Such parameters will then be substituted in-line when the stanza is executed. The above example declares a stanza which displays a text file in an existing alpha window. Its parameters are the window to allocate ($1) and the name of the text file ($2).

An example of its subsequent use might be:

file_window my_win, file.dat

As can be seen, invoking a stanza is very similar to executing a command with comma-separated parameters.

A concatenation of all parameters (separated by commas) may be used within the stanza by using the phrase $*, e.g.

stanza deltemps = 'path /tmp; delete $*'

This example declares a stanza which deletes specified files from a temporary directory, and might be invoked as follows:

deltemps file1, file2, file3

Similar to a string identifier, the scope of a stanza may be local, application or global. There is no differentiation made between upper and lower case characters in a stanza name. A stanza may contain other stanzas within it, but one should be careful to avoid self-referencing.

As well as having special execution status, a stanza identifier is very similar to a string identifier and may be used wherever a string can be used. Some common stanzas, which are often defined at startup to simulate a unix environment are:

stanza pwd = 'tell current_dir'
stanza up = 'path ..; tell current_dir'
stanza edit = ':vi $1'

Note: The user must defined these, they are not supplied by the system.

In some of the above examples, a stanza is assigned with multiple commands to be executed. For language block purposes, such combinations are treated as one command. This is achieved by enclosing the commands within block braces { and }. This is done automatically, and the braces do not need to be included within the stanza. Consider this:

stanza setcolor = 'string s;ask "Which color",s;if (s) $1.color=\^(s)'

This will set a new color for a graphic entity. An example of its use might be as follows:

# The user selects a graphic entity interactively
this = epick
# If valid change its color
if (this) setcolor this

A user-defined function or procedure can do the same job as a stanza but in terms of execution, a stanza will always be faster although sometimes at the expense of readability. For single command name substitution without parameters, aliases are usually used. For more complex string substitution, the string identifier ^ feature (known as caret substitution) used in our last example is quite powerful and should be checked out.

Point Identifiers

A point identifier records a single 3-D point value. A point array records multiple point values. All point values are stored internally in floating-point double precision and have 3 components: x, y and z.

A point identifier is declared by the command point:

# A single point identifier (a scalar)
point p
# A point fixed-length array of 10 elements
point pp[10]
# A point variable-length array, initially of zero length
point pv[]
# A point variable-length array, initially of length 5
point ppp[-5]

Points are initialized to the global coordinate origin (0,0,0).

A scalar point is subsequently referenced by just its name.

# Print the value of p
tell p

A point array element is referenced by its array name and an index in square brackets:

# Print the 5th array element of pp
tell pp[5]
# Print the last array element of pp
tell pp[*]

A range of array elements is referenced by the array name and an index range, e.g.

# Print the first 10 elements of the array pp
tell pp[1:10]
# Print elements 5 to the last of the array pp
tell pp[5:*]
# Print elements 2 to the penultimate of the array pp
tell pp[2:*-1]
# 3 ways to print all elements of pp
tell pp
tell pp[]
tell pp[1:*]

System Points

There are many predefined system points with global scope accessible to the user. They are listed in the Reference Manual, in the section System Identifiers.

Some system points have values which can be changed, e.g. gc, the current graphic control position. Others have read-only values, e.g. p0 the modelling origin (0,0,0).

There are also predefined point functions which, when given parameters, yield point values. They are listed in the Reference Manual, in the section System Functions.

Point Assignments

Point identifiers are given values by assignments, which can be part of an identifier declaration, or a separate assignment command subsequent to the declaration:

point p = (10,50,80)
point p
p = (10,50,80)

If 2-D only points are required, the z component may be omitted. 0 is then assumed, e.g.

point p = (10,50)

Point arrays may be given values in a similar way but multiple items should be comma-separated within hairpin characters:

# Fixed length array
point pp[3] = <(10,5,5),(7,9,2),(6.5,0,0)>
# Variable length array
point pv[] = <(1,0,0),(0,1,0)>
# Assign a range (extending the array length if necessary)
pv[2:4] = <(7,10,2),(7.5,0,0),(-1.7,15,3)>

Point Phrases

Point phrases provide a means of constructing point values from other points, directions, distances and angles. They can be combined within simple or convoluted point expressions (which we discuss in the next sub-section). A point phrase is a combination of point identifiers, point constants, point phrases, function values, parentheses and operators, yielding one or more point values.

Point phrases permit the description of points whose absolute coordinates are not fully known. They often use direction characters to indicate directions along specific axes:

Character

Direction

Movement

E

East

positive movement along the X axis

W

West

negative movement along the X axis

N

North

positive movement along the Y axis

S

South

negative movement along the Y axis

U

Up

positive movement along the Z axis

D

Down

negative movement along the Z axis

Point phrases often change one or two of the three ordinates of a point while leaving others unchanged. There is thus the concept of the current graphic control position (aka gc) used during point expression evaluation. This current position is updated after every constituent point phrase within the point expression. For the initial point phrase, the current position is usually assumed to be the value of the system point identifier gc. At system start-up, this has the value (0,0,0).

We shall give you a quick run down of the various point phrases available when constructing a point expression and will then show how these can be combined to produce a resulting point value.

An Absolute Coordinate Point Phrase is a simple definition of X,Y or X,Y,Z Cartesian coordinates in 3-D space. Each coordinate may be any numeric expression, and they are enclosed by parentheses and separated by commas, e.g.

(10,203,30)
(sqrt(45),x+55,91) ;# x is a numeric
(15,25)

In the last example, only two values are supplied.

A Relative Directional Point Phrase comprises a direction, a colon character and a distance, and describes relative movement along one of the Cartesian axes, e.g.

# Increment the X ordinate of the current position by +500
E:500
# Increment the Y ordinate of the current position by +3000
n:3000
# Decrement the Z ordinate of the current position by √17.8
d:sqrt(17.8)

An Absolute Directional Ordinate Point Phrase comprises a direction and an ordinate value within parentheses, and describes absolute movement along one Cartesian axis, e.g.

# Set the X ordinate of the current position to 500
E(500)
# Set the Y ordinate of the current position to the value of
#   the numeric identifier xval
N(xval)

An Absolute Directional Point Phrase comprises a direction and a point expression within parentheses, and like the previous phrase type describes absolute movement along a Cartesian axis, e.g.

# Set the X ordinate of the current position to the
#   X ordinate of the point identifier my_point.
E(my_point)

An Identifier Point Phrase comprises a simple reference to a point identifier or point identifier array element, e.g.

# A point scalar (yielding one point value)
my_point
# A point array element (yielding one point value)
parray[2]
# A point array (yielding as many points as are in the array)
parray[]
parray
parray[2:5]

The effect of an identifier point phrase is similar to an absolute coordinates point phrase, in that the current position is updated to the value of the identifier. Multiple values are often required for array assignment purposes, e.g.

point pb[] = parray[2:10]

but within complex point expressions, when only a single point value is being computed, only the first value is used.

A Plan Angle Directional Point Phrase comprises a direction, a colon character, a distance, an @ symbol and an angle, and describes relative movement in the plan (XY) plane in a given direction (defined by the angle), e.g.

# Increment the X and Y ordinates of the current position
E:100@45

In Pythagorean terms, the distance (100 in this case) is the length of the hypotenuse. If current control was initially (0,0,0), this would produce the value (70.7107,70.7107,0). The initial direction character merely identifies the phrase to the language parser as a point phrase and is not actually used. Conventionally we use E. The angle is assumed to be in the current angle_mode units, typically degrees.

An elevation may also be applied by appending an additional @ symbol and angle. This then describes a 3-D point by two angles, e.g.

E:100@45@30

which increments all three ordinate components of the current position, and would give a point value of (61.2372,61.2372,50) when starting at (0,0,0).

A 3-D Relative Directional Point Phrase comprises a direction, a colon character, a distance, an @ symbol and a 3-D direction phrase, and describes relative movement in a given direction (defined by the 3-D direction), e.g.

E:100@E&N&U

The leading direction character merely identifies the phrase to the language parser as a point phrase and is not used. The actual direction of movement is defined by the direction phase, in this case E&N&U. Conventionally we use E. This example when starting at (0,0,0) would produce the value (57.735,57.735,57.735). The next example computes a point in a direction using another point which when normalized gives direction cosines, e.g.

E:100@(0.8660254,0.70710678,0.70710678)

This would produce the value (65.4654,53.4522,53.4522), again assuming a start point of (0,0,0).

A Function Point Phrase comprises a point function name and parameters within parentheses separated by commas, and yields one or more point values. There are various system point functions available and you can also write your own. This is discussed later.

# The mid-point between 2 two points
midp(my_point,p0)
# A point specified by an interactive mouse operation
pcur()

Point Expressions

A point expression is a combination, in fact a concatenation, of constituent point phrases which gets evaluated into a final point value. The operator & is used between point phrases, e.g.

# Produce a diagonal
point p1 = (100,100)
point p2 = p1 & e:500 & n:500
# Get an interactive point and ensure its Z ordinate is zero
point p = pcur() & d(0)

Any sequence of point phrases may be combined in this way. Parentheses are used to indicate evaluation precedence and may enclose all or part of a point expression or sub-expression.

In addition to the concatenate operator &, there is a length operator % permitting the last defined point of the expression to be modified. There are two effects of this operator depending on the syntax used.

Length modification, where the last point phrase is used merely as a 3-D direction and a vector length is supplied after the % operator, e.g.

(100,100,50) % 37.5

This describes a point of length 37.5 measured from the current position in the direction of the point (100,100,50). Another example

(E:3 & N:4) % 10

describes a point of length 10 from the current position in the direction tan(4/3). Note the use of the parentheses.

Length and direction modification, where the most recent point computed in the point expression is offset orthogonally in a given direction at a given angle, e.g.

E:100 % N@30

This yields a point (100,100 * tan(30),0) assuming a start point of (0,0,0). In Pythagorean terms, this computes another side of a right-angle triangle given one side and an angle.

Point Function Values

The number of values returned by a point function may vary but most system functions return one value. A complete list of in-built functions and their parameters is given in the Reference Manual, in the section System Functions.

Examples:

# Find the point of intersection of 2 lines
point px = intll(p1,p2,q1,q2)
# Get a 3-D point interactively
point pt = pcur()

The last function has no parameters. This is permitted when they are optional, or when the function does not require any. In such cases the empty parentheses may be omitted.

Functions may return multiple values for array assignment purposes. When they do, but only a single value is required, only the first value is used.

As well as system functions, you can write your own functions which return point values:

point_function third_way {
   args p1=point, p2=point
   funcval.x = (p1.x + p2.x) / 3
   funcval.y = (p1.y + p2.y) / 3
   funcval.z = (p1.z + p2.z) / 3
}

This function will return a point a third of the way between two other points.

The Point Structure

A point identifier is a system structure:

structure point {
   numeric x
   numeric y
   numeric z
}

Thus its individual ordinate values can be referenced in isolation. For example, gc.y is the y coordinate of the system point gc (the graphic control position).

Note: The x, y and z attributes are available on point identifiers only and are not permitted on point functions or expressions, e.g. (pcur & n:100).y is not permitted.

Assignment Operators

Often it is useful to increment an existing point identifier via a relative point phrase. Consider

gc = gc & e:500

which will increment the X coordinate of gc by 500.

This can be expressed more concisely and efficiently by use of the assignment operator &=, i.e.

gc &= e:500

and could also been achieved by the following:

gc.x += 500

Note that the simple assignment gc = e:500 will yield the value (500,0,0) irrespective of the initial value of gc.

Unlike numeric and string identifiers, assignment operations are not permitted on point arrays, but you can perform a constant operation on all elements of a point array, known as a block assignment, with the set command, e.g.

# Define 4 points in a square
point myps[] = <(10,10),(20,10),(20,20),(10,20)>
# Shift them all 500 in the X direction
set myps &= e:500

This would result in myps having the values: (510,10,0) (520,10,0) (520,20,0) (510,20,0).

Point Text Output

When point values are converted to text, for display or print purposes, the format of the output is (x,y,z) where the precision of the x, y and z components is determined by the value of the system numeric identifier dec_places. Trailing zeros are suppressed.

point p = (10.33333,25,32.7)
dec_places = 2
tell p

The above will display (10.33,25,32.7).

The system string function ps will do the same:

tell ps(p,0)

This will display (10,25,32). As you can see values get rounded appropriately. The second parameter can be omitted, in which case dec_places is used to determine the output precision.

A similar system function pnob will create output output without the punctuation symbols
but with space delimitation:

tell pnob(p,3)

The above will display 10.333 25 32.7.

A point expression may also be used within caret substitution, e.g.

!My point is ^(p)

This will use a point value in a quick message statement.

When the system starts, dec_places has an initial value of 4.

Doing More with Arrays

We have already discussed how to access array elements either singularly (array[i]) or in a range (array[i:j]).

For more advanced applications, combinations of different array elements are permitted to form dynamic collections. These are known as discrete arrays.

Consider the following:

array[1,8,4] is a dynamic array comprising 3 values,

array[8:10,4,15:*] is another more complex set built up from value ranges,

array[*:1] is another built up from all elements in reverse order.

Value assignments can be made to discrete arrays, e.g.

set array[1,8,4]=5

This sets the 3 elements of the array to the value 5. Since the language is interpreted, this will execute faster than 3 individual element assignments.

Discrete index lists can be created for repeated use. Imagine we have a set of numeric array elements which need to be referenced often. We could set up a list of array indices which satisfy a particular condition, e.g.

numeric array[]
#  ... populate the array somehow
numeric pos[]
splitid array > 0,-on1=pos
tell <'Positive values',array[pos]>

Here we use the splitid command to create an index array whose elements in the target array have positive values. Then the relevant values from the target array are printed using a discrete array specification using the index array.

The above example code could of course be written thus:

numeric vals[]
for i=1,array.length {
   if (array[i] > 0) vals =: array[i]
}
tell <'Positive values',vals>

but the first method is faster, with the advantage that we now have a pre-cooked list of the elements with positive values for subsequent re-use.

We might want to multiply the elements by pi, e.g.

set array[pos] *= pi

or we might want to delete such elements, so

delid array[pos]

would delete all the elements specified in the pos index array.

Index lists (like the array pos in the above example) are useful for sorting and trimming arrays.

To get the sort order of an array the order function function may be used to produce an index list. Sorting can be performed on numeric, string and time arrays, e.g.

string sss[]
# ... populate the array sss somehow
numeric idxs[] = order(sss)

There is also a sortid command when more complex element comparisons are required within the sort. The sortid command can be used to sort an array in place or to just calculate an index list, similar to that produced by the order function.

Once a sort order is known a target array can be re-ordered using the index list. For this we use the reorder command:

reorder myarray,idxs

The elements of an array can also be reversed with the reverse command:

reverse myarray

Discrete arrays can be sorted, re-ordered and reversed in the same way as full arrays, e.g.

reverse myarray[1,2,*-1,*]

although the analysis of such discrete re-ordering can hurt the head somewhat.

Aggregate Arrays

Aggegrate array facilities enable iterative operations to be performed on structures without the need for loops. Such operations will improve performance.

Consider the following simple example:

# Define a structure class ...
atable myatable
structure myatable thing_t {
   numeric nvalue
   string svalue
   point pvalue
}
# Create a 50-element array (variable length) of such objects
~myatable.thing_t mystruct[-50]
for i=1,mystruct.alength {
   mystruct[i].nvalue = 10
}

The last part of the above code can be replaced with:

set mystruct[1:*].nvalue = 10

and will execute much faster than a loop due to the interpretive native of the language.

Aggregate definitions are treated as if they are single arrays, so

# Find the first structure element whose .svalue has the value Test
numeric answer
for i=1,mystruct.alength {
   if (mystruct[i].svalue) == 'Test') {
      answer = i
      break
   }
}
if (answer) tell <'Structure Index:',answer>
else tell <'Not found'>

can be replaced with:

numeric answer = aindex('Test',mystruct[1:*].svalue)
if (answer) tell <'Structure Index:',answer>
else tell <'Not found'>

Other examples include:

Set with multiple values:

mystruct[2:6].nvalue = <3,7,9,15,22>

Apply a shift in x and y to all points

set mystruct[1:*].pvalue &= e:25&n:25

Testing multiple values

if (mystruct[1:5].nvalue == <5,4,8,10,12>) !We have equality

Assign sparse values using discrete indexing

mystruct[2,4,6,8].nvalue = <20,45,90,117>

Prev Chapter    Next Chapter