10.6 Compile Time Functions


10.6 Compile Time Functions

HLA provides a wide range of compile time functions you can use. These functions compute values during compilation the same way a high level language function computes values at runtime. The HLA compile time language includes a wide variety of numeric, string, and symbol table functions that help you write sophisticated compile time programs.

Most of the names of the built-in compile time functions begin with the special symbol "@" and have names like @sin or @length. The use of these special identifiers prevents conflicts with common names you might want to use in your own programs (like length). The remaining compile time functions (those that do not begin with "@") are typically data conversion functions that use type names like int8 and real64. You can even create your own compile time functions using macros (which this chapter discusses a little later).

HLA organizes the compile time functions into various classes depending on the type of operation. For example, there are functions that convert constants from one form to another (e.g., string to integer conversion), there are many useful string functions, and HLA provides a full set of compile time numeric functions.

The complete list of HLA compile time functions is too lengthy to present here. Instead, a complete description of each of the compile time objects and functions appears in the HLA Reference Manual on the accompanying CDROM; this section will highlight a few of the functions in order to demonstrate their use. Later sections in this chapter, as well as future chapters, will make extensive use of the various compile time functions.

Perhaps the most important concept to understand about the compile time functions is that they are equivalent to constants in your assembly language code (i.e., the run-time program). For example, the compile time function invocation "@sin( 3.1415265358979328 )" is roughly equivalent to specifying "0.0" at that point in your program.[2] A function invocation like "@sin( x )" is legal only if x is a constant with a previous declaration at the point of the function call in the source file. In particular, x cannot be a run-time variable or other object whose value exists at runtime rather than compile time. Because HLA replaces compile time function calls with their constant result, you may ask why you should even bother with compile time functions. After all, it's probably more convenient to type "0.0" than it is to type "@sin( 3.1415265358979328 )" in your program. However, compile time functions are handy for generating look-up tables and other mathematical results that may change whenever you change a const value in your program. Later sections in this chapter will explore these ideas farther.

10.6.1 Type Conversion Compile Time Functions

Probably the most commonly used compile time functions are the type conversion functions. These functions take a single parameter of one type and convert that information to some specified type. These functions use several of the HLA built-in data type names as the function names. Functions in this category are the following:

  • boolean

  • int8, int16, int32, int64, and int128

  • uns8, uns16, uns32, uns64, and uns128

  • byte, word, dword, qword, and lword (these are effectively equivalent to uns8, uns16, uns32, uns64, and uns128)

  • real32, real64, and real80

  • char

  • string

  • cset

  • text

These functions accept a single constant expression parameter and, if at all reasonable, convert that expression's value to the type specified by the type name. For example, the following function call returns the value -128 because it converts the string constant to the corresponding integer value:

      int8( "-128" ) 

Certain conversions don't make sense or have restrictions associated with them. For example, the boolean function will accept a string parameter, but that string must be "true" or "false" or the function will generate a compile time error. Likewise, the numeric conversion functions (e.g., int8) allow a string operand, but the string operand must represent a legal numeric value. Some conversions (e.g., int8 with a character set parameter) simply don't make sense and are always illegal.

One of the most useful functions in this category is the string function. This function accepts nearly all the constant expression types, and it generates a string that represents the parameter's data. For example, the invocation string( 128 ) produces the string "128" as the return result. This function is real handy when you have a value that you wish to use where HLA requires a string. For example, the #error compile time statement only allows a single string operand. You can use the string function and the string concatenation operator ("+") to easily get around this limitation, e.g.,

 #error( "theValue (" + string( theValue ) + ") is out of range" ) 

Note that these type functions actually perform a conversion. This means that the bit pattern these functions return may be considerably different than the bit pattern you pass as an argument. For example, consider the following invocation of the real32 function:

      real32( $3F80_0000 ) 

Now it turns out that $3F80_0000 is the hexadecimal equivalent of the real32 value 1.0. However, the preceding function invocation does not return 1.0; instead it attempts to convert the integer value $3F80_0000 (1,065,353,216) to a real32 value but fails because the value is too large to exactly represent using a real32 object. Contrast this with the following constant function:

      char( 65 ) 

This CTL function invocation returns the character ‘A’ (because 65 is the ASCII code for ‘A’). Notice how the char function simply uses the bit pattern of the integer argument you pass it as an ASCII code while the real32 function attempts to translate the integer argument to a floating point value. Although the semantics are quite different between these two functions, the bottom line is that they tend to do the intuitive operation, even at the expense of consistency.

Sometimes, however, you might not want these functions to do the "intuitive" thing. For example, you might want the real32 function to simply treat the bit pattern you pass it as a real32 value. To handle this situation, HLA provides a second set of type functions, which are simply the type names with an "@" prefix that simply treat the argument as a bit pattern of the final type. So if you really want to produce 1.0 from $3F80_0000 then you could use the following function invocation:

      @real32( $3F80_0000 ) 

Generally, type coercion of this form is somewhat advanced in the compile time language, so you'll probably not use it very often. However, when it is needed, it's nice to have around.

10.6.2 Numeric Compile Time Functions

The functions in this category perform standard mathematical operations at compile time. These functions are handy for generating look-up tables and "parameterizing" your source code by recalculating functions on constants defined at the beginning of your program. Functions in this category include the following:

  • @abs( n ): Absolute value of numeric argument.

  • @ceil( r ), @floor( r ): Extract integer component of floating point value.

  • @sin(r), @cos(r),@tan(r): Standard trig functions.

  • @exp(r), @log(r), @log10(r): Standard log/exponent functions.

  • @min(list),@max(list): Returns min/max value from a list of values.

  • @random, @randomize: Returns a pseudo-random int32 value.

  • @sqrt(n): Computes the square root of its numeric argument (real result).

See the HLA Reference Manual for more details on these functions.

10.6.3 Character Classification Compile Time Functions

The functions in this group all return a boolean result. They test a character (or all the characters in a string) to see if it belongs to a certain class of characters. The functions in this category include the following:

  • @isAlpha(c), @isAlphanum(c)

  • @isDigit(c), @isxDigit(c)

  • @isLower(c), @isUpper(c)

  • @isSpace(c)

In addition to these character classification functions, the HLA language provides a set of pattern matching functions that you can also use to classify character and string data. See the appropriate sections later in this chapter for the discussion of these routines.

10.6.4 Compile Time String Functions

The functions in this category operate on string parameters. Most return a string result, although a few (e.g., @length and @index) return integer results. These functions do not directly affect the values of their parameters; instead, they return an appropriate result that you can assign back to the parameter if you wish to do so.

  • @delete, @insert

  • @index, @rindex

  • @length

  • @lowercase, @uppercase

  • @strbrk, @strspan

  • @strset

  • @substr, @tokenize, @trim

For specific details concerning these functions, their parameters, and their types, see the HLA Reference Manual. Note that these are the compile time equivalents of many of the string functions found in the HLA Standard Library.

The @length function deserves a special discussion because it is probably the most popular function in this category. It returns an uns32 constant specifying the number of characters found in its string parameter. The syntax is the following:

 @length( string_expression ) 

where string_expression represents any compile time string expression. As noted, this function returns the length, in characters, of the specified expression.

10.6.5 Compile Time Pattern Matching Functions

HLA provides a very rich set of string/pattern matching functions that let you test a string to see if it begins with certain types of characters or strings. Along with the string processing functions, the pattern matching functions let you extend the HLA language and provide several other benefits as well. There are far too many pattern matching functions to list here (see the HLA Reference Manual for complete details). However, a few examples will demonstrate the power and convenience of these routines.

The pattern matching functions all return a boolean true/false result. If a function returns true, we say that the function succeeds in matching its operand. If the function returns false, then we say it fails to match its operand. An important feature of the pattern matching functions is that they do not have to match the entire string you supply as a parameter; these patterns will (usually) succeed as long as they match a prefix of the string parameter. The @matchStr function is a good example; the following function invocation always returns true:

 @matchStr( "Hello World", "Hello" ) 

The first parameter of all the pattern matching functions ("Hello World" in this example) is the string to match. The matching functions will attempt to match the characters at the beginning of the string with the other parameters supplied for that particular function. In the @matchStr example above, the function succeeds if the first parameter begins with the string specified as the second parameter (which it does). The fact that the "Hello World" string contains additional characters beyond "Hello" is irrelevant; it only needs to begin with the string "Hello" is doesn't require equality with "Hello".

Most of the compile time pattern matching functions support two optional parameters. The functions store additional data into the val objects specified by these two parameters if the function is successful (conversely, if the function fails, it does not modify these objects). The first parameter is where the function stores the remainder. The remainder, after the execution of a pattern matching function, comprises those characters that follow the matched characters in the string. In the example above, the remainder would be "World". If you wanted to capture this remainder data, you would add a third parameter to the @matchStr function invocation:

 @matchStr( "Hello World", "Hello", World ) 

This function invocation would leave "World" sitting in the World val object. Note that World must be predeclared as a string in the val section (or via the "?" statement) prior to the invocation of this function, e.g., you could use code like the following to define World:

 ?World : string; // No assignment, default is empty string. 

By using the conjunction operator ("&") you can combine several pattern matching functions into a single expression, e.g.,

 @matchStr( "Hello There World", "Hello ", tw ) & @matchStr( tw, "There ", World ) 

This full expression returns true and leaves "World" sitting in the World variable. It also leaves "There World" sitting in tw, although tw is probably a temporary object whose value has no meaning beyond this expression. Of course, the preceding could be more efficiently implemented as follows:

 @matchStr( "Hello There World", "Hello There", World ) 

However, keep in mind that you can combine different pattern matching functions using conjunction; they needn't all be calls to @matchStr.

The second optional parameter to most pattern matching functions holds a copy of the text that the function matched. E.g., the following call to @matchStr returns "Hello" in the Hello val object:[3]

 @matchStr( "Hello World", "Hello", World, Hello ) 

For more information on these pattern matching functions please consult the HLA Reference Manual. The HLA CTL provides dozens of different compile time pattern matching functions; alas, space restrictions prevent duplicating that information here.

10.6.6 Compile Time Symbol Information

During compilation HLA maintains an internal database known as the symbol table. The symbol table contains lots of useful information concerning all the identifiers you've defined up to a given point in the program. In order to generate machine code output, HLA needs to query this database to determine how to treat certain symbols. In your compile time programs, it is often necessary to query the symbol table to determine how to handle an identifier or expression in your code. The HLA compile time symbol information functions handle this task.

Many of the compile time symbol information functions are well beyond the scope of this text. This chapter will present a few of the functions and later chapters will add to this list. For a complete list of the compile time symbol table functions, see the HLA Reference Manual. The functions we will consider in this chapter include the following:

  • @size

  • @defined

  • @typeName

  • @elements

  • @elementSize

Without question, the @size function is probably the most important function in this group. Indeed, previous chapters have made use of this function already. The @size function accepts a single HLA identifier or constant expression as a parameter. It returns the size, in bytes, of the data type of that object (or expression). If you supply an identifier, it can be a constant, type, or variable identifier. As you've seen in previous chapters, this function is invaluable for allocating storage via malloc and allocating storage for arrays.

Another very useful function in this group is the @defined function. This function accepts a single HLA identifier as a parameter, e.g.,

 @defined( MyIdentifier ) 

This function returns true if the identifier is defined at that point in the program; it returns false otherwise.

The @typeName function returns a string specifying the type name of the identifier or expression you supply as a parameter. For example, if i32 is an int32 object, then "@typeName( i32 )" returns the string "int32". This function is useful for testing the types of objects you are processing in your compile time programs.

The @elements function requires an array identifier or expression. It returns the total number of array elements as the function result. Note that for multidimensional arrays this function returns the product of all the array dimensions.[4]

The @elementSize function returns the size, in bytes, of an element of an array whose name you pass as a parameter. This function is extremely valuable for computing indices into an array (i.e., this function computes the element_size component of the array index calculation; see Chapter 4 for more details).

10.6.7 Miscellaneous Compile Time Functions

The HLA compile time language contains several additional functions that don't fall into one of the categories above. Some of the more useful miscellaneous functions include

  • @odd

  • @lineNumber

  • @text

The @odd function takes an ordinal value (i.e., non-real numeric or character) as a parameter and returns true if the value is odd, false if it is even. The @lineNumber function requires no parameters; it returns the current line number in the source file. This function is quite useful for debugging compile time (and runtime!) programs.

The @text function is probably the most useful function in this group. It requires a single string parameter. It expands that string as text in place of the @text function call. This function is quite useful in conjunction with the compile time string processing functions. You can build an instruction (or a portion of an instruction) using the string manipulation functions and then convert that string to program source code using the @text function. The following is a trivial example of this function in operation:

 ?id1:string := "eax"; ?id2:string := "i32"; @text( "mov( " + id1 + ", " + id2 + ");" ) 

The preceding sequence compiles to

 mov( eax, i32 ); 

10.6.8 Compile Time Type Conversions of TEXT Objects

Once you create a text constant in your program, it's difficult to manipulate that object. The following example demonstrates a programmer's desire to change the definition of a text symbol within a program:

 val      t:text := "stdout.put";           .           .           .      ?t:text := "fileio.put"; 

The basic idea in this example is that the symbol "t" expands to "stdout.put" in the first half of the code and it expands to "fileio.put" in the second half of the program. Unfortunately, this simple example will not work. The problem is that HLA will expand a text symbol in place almost anywhere it finds that symbol. This includes occurrences of t within this "?" statement. Therefore, the previous code expands to the following (incorrect) text:

 val      t:text := "stdout.put";           .           .           .      ?stdout.put:text := "fileio.put"; 

HLA doesn't know how to deal with this "?" statement, so it generates a syntax error.

At times you may not want HLA to expand a text object. Your code may want to process the string data held by the text object. HLA provides a couple of ways to deal with these two problems:

  • @string:identifier (also @string( identifier ) )

  • @toString:identifier

The @string:identifier operator consists of @string, immediately followed by a colon and a text identifier (with no interleaving spaces or other characters). HLA returns a string constant corresponding to the text data associated with the text object. In other words, this operator lets you treat a text object as though it were a string constant within an expression. Note that @string( identifier ) serves the same purpose (this functionality of @string was added to HLA long after @string:identifier, so HLA has to support both forms; the @string( identifier ) form is preferable, though).

Unfortunately, the @string operator converts a text object to a string constant, not a string identifier. Therefore, you cannot say something like

 ?@string:t := "Hello" 

This doesn't work because @string:t replaces itself with the string constant associated with the text object t. Given the former assignment to t, this statement expands to

 ?"stdout.put" := "Hello"; 

This statement is still illegal.

The @toString:identifier operator comes to the rescue in this case. The @toString operator requires a text object as the associated identifier. It converts this text object to a string object (still maintaining the same string data) and then returns the identifier. Because the identifier is now a string object, you can assign a value to it (and change its type to something else, e.g., text, if that's what you need). To achieve the original goal, therefore, you'd use code like the following:

 val      t:text := "stdout.put";           .           .           .      ?@tostring:t : text := "fileio.put"; 

[2]Actually, because @sin's parameter in this example is not exactly π, you will get a small positive number instead of zero as the function result, but in theory you should get zero.

[3]Strictly speaking, this example is rather contrived because we generally know the string that @matchStr matches. However, for other pattern matching functions this is not the case.

[4]There is an @dim function that returns an array specifying the bounds on each dimension of a multidimensional array. See the CD-ROM documentation for more details if you're interested in this function.




The Art of Assembly Language
The Art of Assembly Language
ISBN: 1593272073
EAN: 2147483647
Year: 2005
Pages: 246
Authors: Randall Hyde

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net