Skip to content

WGML Device Function Notes

Jiri Malak edited this page Feb 20, 2021 · 8 revisions
Table of Contents

Introduction

This page is concerned with two aspects of device functions in wgml:

  • Where the various Device Functions are permitted by wgml 4.0 to perform their proper function.
  • What the various Device Functions, when they are permitted by wgml 4.0 to perform their proper function, actually do.

This imposes to sets of restrictions on our wgml which, as long as it uses the version 4.0 binary device library format, will have to be able to handle any device function sequence which gendev 4.1 will create. However, only those sequences actually used in the two devices (PS and WHELP) used to produce the Open Watcom documentation need produce output in the document file identical to that produced by wgml 4.0. Function sequences which wgml 4.0 does not handle well can be handled differently by our wgml simply because none of them occur in PS or WHELP (if they did, then, generally speaking, the documents would not be produced by wgml 4.0 at all).

Device Function Interpretation

Introduction

This section explores where wgml 4.0 allows the compiled device functions to perform their intended function. Since device functions are enclosed in various blocks, the discussion inevitably will be in terms of what happens in the various blocks. After much thought and several false starts, I believe I have found a clear way to organize the material.

This section uses an abbreviated terminology for many blocks:

Term Used            Expansion
START :PAUSE         :PAUSE block with value START for attribute place
DOCUMENT :PAUSE      :PAUSE block with value DOCUMENT for attribute place
DOCUMENT_PAGE :PAUSE :PAUSE block with value DOCUMENT_PAGE for attribute place
DEVICE_PAGE :PAUSE   :PAUSE block with value DEVICE_PAGE for attribute place
START :INIT          :INIT block with value START for attribute place
DOCUMENT :INIT       :INIT block with value DOCUMENT for attribute place
DOCUMENT :FINISH     :INIT block with value DOCUMENT for attribute place
END :FINISH          :INIT block with value DOCUMENT for attribute place
:FONTPAUSE "name"    :FONTPAUSE block with value "name" for attribute type
:FONTSTYLE "name"    :FONTSTYLE block with value "name" for attribute type
:FONTSWITCH "name"   :FONTSWITCH block with value "name" for attribute type
:NEWLINE n           :NEWLINE block with numeric value n for attribute advance
:LINEPROC n          :LINEPROC block with numeric value n for attribute pass

For the :DRIVER block, two test devices were needed:

  1. One with no :ABSOLUTEADDRESS block (and so no :HLINE, :VLINE, or :DBOX block), in which the :NEWLINE block was used for positioning and the boxes were drawn with the characters specified by the :BOX block; and one with no :NEWLINE block but with an :ABSOLUTEADDRESS block (and also with :HLINE, :VLINE, and :DBOX blocks), in which :ABSOLUTEADDRSS was used for positioning and the boxes were drawn with :DBOX or with :HLINE and :VLINE.
  2. One with two :FINISH blocks (and END :FINISH block and a DOCUMENT :FINISH block), in which only the END :FINISH block was used; and one with just a DOCUMENT :FINISH block, which was used when that test device was used.

Functions Restricted by gendev 4.1

These device functions:

%textpass() %ulineon() %ulineoff()

are restricted by gendev 4.1 to the various :LINEPROC sub-blocks as discussed here. They cannot be classified as the other device functions are, for that classification is based on being able to use the device functions in every possible block.

The text driver files included :FONTSTYLE blocks which used these functions the same way they are used in the actual :DRIVER blocks available to me, and they appear to function normally when used that way.

Type Mis-matches

Some device functions take at least one parameter, which is required to be one of two types: character and numeric.

Some Type II device functions return a value, which is always one of the same two types.

This section discusses what happens when these types do not match. The observed behavior is:

  1. When a device function which expects a numeric parameter is, in fact, given a character return value, the return value is treated as a (very large) number.
  2. When a device function which expects a character parameter is, in fact, given a numeric return value, the result is either that nothing happens or that wgml 4.0 emits this message:
Abnormal program termination: Memory protection fault

and then exits. In some cases, the same function sequence has been observed behaving one way one day and the other way the next day.

A simple (but hypothetical) model can explain all three behaviors.

Suppose that the parser in wgml 4.0 uses this typedef for the functions implementing the device functions:

typedef void * (*df_function) (void);

and then uses the return value as follows:

  1. Type II device functions cast their return value to a void *; Type I device functions return NULL.
  2. Functions expecting a numeric parameter cast the void * to an int32_t.
  3. Functions expecting a character parameter cast the void * to a char *.

This would explain the observed behavior:

  1. Functions expecting a numeric parameter would always have a numeric value. The result would be rather large, especially if an int32_t is used.
  2. Functions expecting a numeric parameter would get a NULL pointer if that was returned or if the numeric value returned happened to correspond to a NULL pointer; if they got a non-NULL pointer then, if the value passed actually was a char *, all would be well, but if it was actually a numeric value, then attempting to access the memory supposedly pointed to would very likely produce the error observed.

Of all the tests done, this partial function sequence:

%subtract(%font_number(),3)

produced very interesting results depending on what it was used as a parameter for (%font_number() clearly returning "0"):

Using Sequence              Value
%binary4()                  0xfd 0xff 0xff 0xff
%image(%decimal())          -3
%image(%hex())              fffffffd

which clearly shows that a numeric return value is treated as an int32_t by %decimal() and a uint32_t by %binary4() and %hex().

Functions Treated Uniformly

Those function sequences involving literal parameters which are discussed here behave as expected in all compiled function blocks. The number of possible function sequences in this category is infinite; these examples were tested:

%binary(3)              the appropriate graphic appears
%binary1(4)             the appropriate graphic appears
%binary2(5)             the appropriate graphic appears
%binary4(6)             the appropriate graphic appears
%image("image test")    the string "image test" appears
%image(%decimal(53))    the digits "53" appear
%image(%hex(53))        the digits "35" appear
%image(%lower("SUZY"))  the string "suzy" appears
%text("text test")      the string "text test" appears
%text(%decimal(53))     the digits "53" appear
%text(%hex(53))         the digits "35" appear
%text(%lower("SUZY"))   the string "suzy" appears

An interesting phenomenon became apparent during these tests: the XP VDM window (at least) behaves as if the null characters generated by %binary2(5) and %binary4(6) were CR+LF characters. Both the binary file and a file containing the redirected screen output were examined, and neither showed actual additional CR+LF characters, extra compiled %recordbreak() functions, or blank lines in the output file, so this pretty much has to be something the VDM is doing.

All other function sequences discussed which use literal parameters are function sequences in which the parameters are encoded using parameter blocks.

These functions are presumed to work uniformly:

%font_number()
%pages()
%tab_width()
%x_address()
%x_size()
%y_address()
%y_size()

They return "0" not only in the START :PAUSE block but also in the START :INIT block, suggesting that they are initialized to "0" in wgml 4.0 and so return the appropriate value in all blocks.

These device functions were found to work uniformly when they were tested using device function %setsymbol() to store non-zero, non-empty-string values and %getnumsymbol() and %getstrsymbol() to retrieve those values as non-literal parameters:

%add()
%decimal()
%divide()
%hex()
%lower()
%remainder()
%subtract()

This device function returns the value of "0" in every block except for the :HLINE, :VLINE, and :DBOX blocks, where the corresponding attribute is defined:

%thickness()

However, rather than being ignored, it is more likely that it is reporting an actual value, which is non-zero only in those blocks that have an attribute thickness, and so is treated uniformly.

The symbol functions are treated uniformly: this function:

%setsymbol()

sets the symbol to the value indicated and these functions:

%getnumsymbol()
%getstrsymbol()

return the value of the symbol, exactly as discussed here.

The conditional functions all work uniformly: functions inclosed in any conditional-execution block are executed under these conditions:

%ifeqs() with equal character arguments
%ifnes() with unequal character arguments
%ifeqn() always; it does not matter if the arguments are character
         or numeric, equal or unequal

and functions inclosed in a conditional-execution governed by this conditional function are never executed:

%ifnen()

Note that this establishes a distinction between uniform behavior and correct behavior. Also, the function:

%endif()

works uniformly: it terminates the current (innermost) conditional-execution block.

For %ifeqs() and %ifnes(), the comparison is done taking case into account: "fred" and "Fred" trigger %ifnes(), not %ifeqs().

This function is treated uniformly:

%sleep()

of course, what it does uniformly is to hang wgml 4.0.

Functions Not Treated Uniformly

As is, in fact, documented in the WGML 4 Reference, these functions work as expected in the :DEVICE block and are completely ignored (have no effect whatsoever) in the :DRIVER block:

%clear3270()
%clearPC()
%wait()

%clear3270() performs the same action as %clearPC(), at least in an NTVDM under Windows XP.

These device functions differ only in that, in a :DEVICE block, their output appears on the terminal screen, while, in a :DRIVER file, their output appears in the output stream:

%image()
%recordbreak()
%text()

These device functions differ only in that, in a :DEVICE block, they are completely ignored (that is, nothing whatsoever happens), while, in a :DRIVER file, their output appears in the output stream:

%binary()
%binary1()
%binary2()
%binary4()

These device functions return an empty string in the INIT :PAUSE block and the appropriate value in all other blocks in both :DEVICE and :DRIVER:

%date()
%font_outname1()
%font_outname2()
%font_resident()
%time()
%wgml_header()

This is strange because:

  • %date(), %time(), and %wgml_header() do not depend on loading the binary device library and should always return a non-empty value.
  • %font_resident() reports that value of a required attribute. Since wgml clearly reports processing the binary device library before interpreting the INIT :PAUSE block, it should always return a non-empty value.
  • All fonts used for testing provided values for the attributes font_out_name1 and font_out_name2, so the functions %font_outname1() and %font_outname2() should always return a non-empty value.

It thus appears that these functions are ignored in the INIT :DEVICE block, although the actual implementation in wgml 4.0 cannot be determined.

These device functions return the value "0" in the INIT :PAUSE block and the appropriate value in all other blocks in both :DEVICE and :DRIVER:

%default_width()
%font_height()
%font_space()
%line_height()
%line_space()
%page_depth()
%page_width()

While some of the attributes whose value these functions provide are optional, and while "0" is an allowed value for them, in the test files, they were all given nonzero values, which the functions should always return. Thus, these functions also appear to be ignored in the INIT :PAUSE block.

Functions Behaving Badly

These are functions which behave badly in some blocks. The easiest form of that behavior to accept is recursion, since consideration of what the device function does and where the recursion occurs is usually enough to explain it; however, some explanation of how recursive invocations were identified is needed.

Using %setsymbol() and %ifeqs() blocks, the test files were instrumented to allow each test block to be output exactly once. (The :VALUE blocks of :INIT and :FINISH, since they are only output once by wgml 4.0 anyway, needed no instrumentation.) However, resetting the symbol for the block was deferred until all processing was done. Thus, for example, in INIT :PAUSE, there was:

%setsymbol("dodbox","true")

and in :DBOX there was:

%ifeqs(%getstrsymbol("dodbox"),"true")
   %recordbreak()
   %image("*DBOX block*")
   %recordbreak()
<test code goes here>
   %image("end DBOX test output")
   %recordbreak()
   %setsymbol("dodbox","false")
%endif()

The net effect is that multiple instances of, say, "*DBOX block*" could only occur if recursion was taking place. Thus, multiple instances of the banner for a given block shows recursion, and the phrase "shows recursion" means that multiple instances of the banner for the block(s) being discussed appeared in the output file.

The abbreviation "MPF" refers to the emission of this message:

Abnormal program termination: Memory protection fault

followed by program termination.

Whenever "recursion" is said to occur but no MPF is noted, then wgml 4.0 appeared to loop endlessly; the session had to be terminated. It is possible that, had the session been allowed to run longer, an MPF would eventually have occurred.

Finally, some variation in results was observed: the more bizaare messages do not always appear, sometimes an MPF occurs without any other output to the screen. There is, of course, no practical difference between a simple MPF and an MPF preceded by an inappropriate error message plus a bizaare additional message referencing "remote ''": the function is clearly misbehaving in the block indicated.

%cancel() Usage

This function was tested with :FONTSWITCH names as its parameter. Preliminary testing with :FONTSTYLE names suggests that it may not work with :FONTSTYLE blocks.

Device function %cancel() produces recursion when placed in the :FONTSWITCH block :STARTVALUE block of the :FONTSWITCH block whose attribute type has the same value as the parameter of device function %cancel(). This is only to be expected. In most cases, the MPF is produced; however, one instance in which this message appeared was found:

 Abnormal program termination: Stack fault

Of course, it makes no practical difference which message appears.

This device function did nothing in the :FONTSWITCH block :STARTVALUE block when the value of attribute type was not the same as the parameter of device function %cancel(). This is probably because, in that case, the :FONTSWITCH associated with current font does not match the parameter.

In the middle of a line, the :LINEPROC block :ENDVALUE block did something very interesting when followed by a font switch: device function %cancel() produced output if the font being switched to matched the parameter of the function. This suggests that the font number is changed a bit earlier than previously thought, something that will need to be investigated.

In a font switch, device function %cancel() produced output if the font being switched to matched the parameter of the function in these blocks:

:FONTSTYLE :ENDVALUE
:FONTSWITCH :ENDVALUE

Device function %cancel() produced output if current font matched the parameter of the function in these blocks:

:ABSOLUTEADDRESS
:DBOX
:FINISH
:FONTSTYLE :STARTVALUE
:HLINE
:HTAB
:INIT :FONTVALUE
:INIT :VALUE
:LINEPROC :ENDVALUE (except as noted above)
:LINEPROC :ENDWORD
:LINEPROC :FIRSTWORD
:LINEPROC :STARTVALUE
:LINEPROC :STARTWORD
:NEWLINE
:NEWPAGE
:VLINE

This is, of course, the expected behavior.

%dotab() Usage

Device function %dotab() does nothing in these blocks:

all :DEVICE blocks
:DBOX
:FINISH
:FONTSTYLE :LINEPROC :ENDVALUE
:FONTSTYLE :LINEPROC :ENDWORD
:FONTSTYLE :LINEPROC :FIRSTWORD
:FONTSTYLE :LINEPROC :STARTVALUE
:FONTSTYLE :LINEPROC :STARTWORD
:FONTSTYLE :STARTVALUE
:FONTSWITCH :STARTVALUE
:HLINE
:INIT
:NEWLINE
:NEWPAGE
:VLINE

but does produce spaces (and so evidently does a horizontal tab) in these blocks:

:HTAB
:FONTSTYLE :ENDVALUE
:FONTSWITCH :ENDVALUE

It is, of course, possible that, at least in the :DRIVER sub-blocks, no spaces are produced because no horizontal tab was needed at that point in processing the test document specification.

In the :ABSOLUTEADDRESS block, device function %dotab() produces this error message:

IO--006: GML interrupted by ATTN key
         Line 2126592 of remote ''

followed by an MPF. It must be understood that it did this even though no keys were pressed. The second line is also quite intriguing, albeit inexplicable. Finally, examination of the output file shows recursion. This is not surprising when the action of %dotab() is considered: it causes :ABSOLUTEADDRESS to be interpreted when available, so recursion if used in that block is only to be expected.

%enterfont() Usage

Device function %enterfont() works normally (that is, causes the :STARTVALUE of :FONTSWITCH 0 to be interpreted) in these blocks:

:ABSOLUTEADDRESS
:DBOX
:FINISH
:FONTSTYLE :ENDVALUE
:FONTSTYLE :LINEPROC :ENDVALUE
:FONTSTYLE :LINEPROC :ENDWORD
:FONTSTYLE :LINEPROC :STARTVALUE
:FONTSTYLE :STARTVALUE
:FONTSWITCH :ENDVALUE
:HLINE
:HTAB
:INIT
:NEWLINE
:NEWPAGE
:VLINE

In a :PAUSE block, device function %enterfont()causes wgml to emit this message:

IO--004: System message is 'Permission denied'
         Error number is 6
         Output operation failed

followed by this question:

Do you want to continue(Yes/No)?

If "y" is entered in answer to the question, then an MPF results. If "n" is entered in answer to the question, then the program ends with no additional message.

In a :FONTPAUSE block which is not the first to be interpreted (that is, is not associated with :DEFAULTFONT 0), these effects are seen when device function %enterfont() is tested:

  • the :FONTPAUSE message only appears once
  • all reference to font style associated with it in its :DEFAULTFONT block is missing from the output file
  • when the font associated with :DEFAULTFONT 0 is in use and the :FONTPAUSE containing %enterfont() is interpreted, then, instead of the switching to fro normally seen, what is seen is that, for the :FONTSWITCH block associated with :DEFAULTFONT 0, the :ENDVALUE block is interpreted once followed immediately by the :STARTVALUE block, twice.

This is the only instance in which any device function interpreted in any part of the :DEVICE block had any effect on the output file (except truncation if an MPF occurred).

In a :FONTPAUSE block which associated with :DEFAULTFONT 0, these effects are seen when device function %enterfont() is tested:

  • the :FONTPAUSE message clearly shows recursion (44 instances of the message were observed)
  • wgml 4.0 then reports an MPF
  • the IO--004 message shown above never appears
  • the output file only contains the START :INIT block (truncation)

In a :FONTSWITCH :STARTVALUE block device function %enterfont() produces:

IO--006: GML interrupted by ATTN key
         Line 277155 of remote ''

followed by the MPF. It must be understood that it did this even though no keys were pressed. The second line is also quite intriguing, albeit inexplicable. Finally, examination of the output file shows recursion. This, of course, is only to be expected for the :FONTSWITCH associated with the default font.

In a :FONTSTYLE :LINEPROC :FIRSTWORD block (for font style "uline", which was not associated with :DEFAULTFONT 0), device function %enterfont() produces recursion in the output file.

In a :FONTSTYLE :LINEPROC :STARTWORD block (for font style "uscore", which was not associated with :DEFAULTFONT 0) device function %enterfont() produces an interesting form of recursion in the output file; in this case (only), pressing "Ctl+C" produces this message:

IO--006: GML interrupted by ATTN key

and then stops. The output file shows an unending sequence of underscore characters, broken into lines corresponding to the specified line length.

%flushpage() Usage

Device function %flushpage() produced no output at all in these blocks:

all :DEVICE blocks
:DBOX
:FINISH
:FONTSTYLE :ENDVALUE
:FONTSTYLE :LINEPROC :STARTVALUE
:FONTSTYLE :STARTVALUE
:FONTSWITCH :ENDVALUE
:FONTSWITCH :STARTVALUE
:HLINE
:INIT
:NEWPAGE
:VLINE

It is, of course, possible that, at least in the :DRIVER sub-blocks, no output was produced because none was needed at that point in processing the test document specification.

In the :HTAB block, device function %flushpage() caused what may have been the start of a new page to appear in the file. Something definitely appeared; however, given the state of the test file at this point, it is hard to be certain just what it was. Research will continue.

In the :ABSOLUTEADDRESS block, device function %flushpage() shows the error message

IO--006: GML interrupted by ATTN key

and then produces an MPF. It must be understood that it did this even though no keys were pressed. The output file shows recursion.

In these blocks:

:FONTSTYLE :LINEPROC :FIRSTWORD
:FONTSTYLE :LINEPROC :STARTWORD

device function %flushpage() produced recursion in the output file.

In these blocks:

:FONTSTYLE :LINEPROC :ENDVALUE
:FONTSTYLE :LINEPROC :ENDWORD
:NEWLINE

device function %flushpage() produced recursion in the output file and an MPF.

General Notes

Introduction

This section discusses what the various device functions do. Although some mention of their behavior in specific blocks is unavoidable, the focus is on what they do when they are permitted by wgml 4.0 to actually perform their intended function. An attempt has been made to group the device functions in a meaningful manner. The "Implementation Notes" subsections are currently being updated as each group of device functions is implemented to explain what was done. A section on parameters, which applies to the implementations of all device function which consume or produce paramters.

This discussion will focus, to some extent, on features needed to implement wgml for use in the Open Watcom document build system. Some features will be specifically identified as eligible to be postponed until wgml is released more generally, or as candidates for elimination from our gendev/wgml altogether.

Parameter Implementation Notes

All parameters are returned as values of type "void *". The device function using the parameter casts the value to the appropriate type.

Literal parameters embedded in a parameter block are accessed by "device function" implementations which have the same function signature, correspond to a byte code in the function tables, and return the literal parameter in the same way that the implementations of actual device functions do.

All numerical parameters are returned by value and are cast to type "uintptr_t". They may be downcast to types of smaller widths if necessary. It is possible that wgml 4.0 casts them to "uint16_t" instead. If this is the case and it causes discrepancies, then our code can be modified as needed.

Device functions %image() and %text() can take a literal character parameter which is not embedded in a parameter block. No other device function is used with this sort of parameter. Device functions %image() and %text() handle such parameters directly, not through the function table.

All other character parameters are returned by reference and are cast to type "char *". Although it is theoretically possible for someone to use an editor that would allow them to embed a null character inside the data, this is neither likely nor practical. Since these parameters all exist as fixed values (some in a parameter block, some in a symbol table, some in a variable), it might be thought that they could be returned as a pointer to that fixed value. However, if a device function sequence fragment such as

%lower(%wgml_header())

is considered, then it is clear that, if device function %wgml_header() returns a pointer to a fixed value, then device function %lower() must return a newly-allocated buffer, since it would hardly do to have the string used by device function %wgml_header() permanently changed to all-lower-case. However, the function receiving the return value of device function %lower() cannot tell if the value is from device function %lower() or some other device function. Thus, all character values returned must be in a newly-allocated buffer, which the device function receiving the parameter must free. Ironically, since %lower() is now known to be receiving a temporary copy, it can modify that copy and pass it on rather than making another copy.

Binary Output

This section is concerned with these device functions:

%binary() %binary1() %binary2() %binary4()

The behavior of these functions with literal parameters (that is, how gendev 4.1 treats them) is discussed here. Some examples of their use with device function return values as parameters (that is, how wgml 4.0 treats them) can be found here.

Device functions %binary() and %binary1() are identical: the same byte code is used for either of them, so wgml 4.0 sees them as a single function. These functions are used whenever a device needs binary output, that is, a numerical value not expressed as a string of decimal (or hexadecimal) digits. They produce one byte per use; if the parameter is larger than $FF, then only the lower byte is output.

Device function %binary2() is only used with the literal parameter "0". This causes gendev 4.1 to produce what wgml 4.0 sees as an %image() byte code with two nulls as the "character string" to be output.

Device function %binary4() is not used in any device file available to me.

Device functions %binary2() and %binary4() share a problem with endian: the endian required by the device does not change just because the endian of the computer issuing the data stream changes. In fact, when an :HTAB block needs to emit a two-byte value, it does it this way (DR850DRV.PCD):

%binary1(27)%text('\')
%binary1(%remainder(%tab_width(),256))
%binary1(%divide(%tab_width(),256))

This will emit the bytes in the required order regardless of the endian of the computer on which wgml is running.

Implementation Notes

In the :DEVICE block interpreter, these functions are all ignored.

In the :DRIVER block interpreter, device functions %binary() and %binary1() insert the byte obtained into the output stream.

In the :DRIVER block interpreter, device functions %binary2() and %binary4() are ignored. This will not affect the Open Watcom document build in any way and, indeed, will not affect any document generated using any of the known devices, since %binary4() is never used and %binary2() is only used with a literal and so is not presented to wgml 4.0 by gendev 4.1 as a %binary2() invocation at all. Except for %binary2(0), use of these functions should be deprecated and they should be documented as not supported.

The deprecation of device functions %binary2() and %binary4() can be reconsidered if our gendev and wgml are released more widely and a need for either or both is found; however, the endian issue will have to be addressed: for example, an "endian" attribute (required, with values "big" or "little") could be added to the :DEVICE block and these device functions implemented to use it. Note that the "endian" that matters is the endian of the device the document is to appear on, not of the CPU running wgml.

Conditionals

This section is concerned with these functions:

%ifeqn() %ifeqs() %ifnen() %ifnes()
%endif()

The discussion found here adequately covers these functions.

Neither gendev 4.1 nor wgml 4.0 requires an explicit %endif() at the end of a function block: compilation or interpretation simply ends when gendev 4.1 or wgml 4.0 runs out of device functions at the end of the block.

Implementation Notes

The device function interpreter is written so that it moves through the linked list of Type I device functions. What this means is that, if a conditional function determines that the device functions it controls are to be interpreted, it need merely return and the interpreter will do so automatically.

On the other hand, if the controlled functions are to be skipped, a function must be invoked to do so, returning when %endif() is detected or when the last device function (which is to be skipped) is reached. Because the final offset (0xFFFF) is detected before the final device function is interpreted, a flag is used by the interpreter to detect that the current device function is the last. If the function which skips the controlled device functions reaches the final device function, it must set the same flag so that the interpreter will know not to interpret it.

The functions implementing %ifeqn(), %ifeqs(), %ifnen(), and %ifnes() assemble their two parameters, make the appropriate comparison and decide whether to skip the controlled device functions or not. The function implementing %endif() does nothing: if the set of controlled device functions it terminates is skipped, it is not even invoked; if that set is interpreted, encountering %endif() has no particular meaning.

Note that all four of the tests work correctly in our wgml.

Document Layout

This section discusses these device functions:

%font_number() %pages() %tab_width() %thickness() %x_address()
%x_size() %y_address() %y_size()

Device function %font_number() returns:

  • "0" during the START :INIT block :VALUE blocks, the DOCUMENT :INIT block :VALUE blocks, and the block immediately following the last instance of each :FONTVALUE block.
  • The values of the "selected fonts" (discussed here) during the instances of each :FONTVALUE block.
  • "0" from the first block after the DOCUMENT :INIT block to the first :LINEPROC block :ENDVALUE block which preceeds a font switch from :DEFAULTFONT 0.
  • The value then remains the same until the :LINEPROC block :ENDVALUE block preceeding the next font switch, where it changes to the number of the font being switched to.

This implies a connection between the :LINEPROC block :ENDVALUE block and font switching; however, the :LINEPROC block :ENDVALUE block appears regularly in other contexts as well.

Device function %pages() returns:

  • "0" during the START :INIT block, the DOCUMENT :INIT block, the implicit %enterfont(0), and the :LINEPROC block :STARTVALUE block and :FIRSTWORD block which follow it.
  • "1" during the immediately following block:
    • when the :ABSOLUTEADDRESS block is defined, this is a :LINEPROC block :ENDVALUE block.
    • when the :ABSOLUTEADDRESS block is not defined, this is a :NEWLINE block.
  • The value is constant through the :NEWPAGE block.
  • If this is a "device page", then the value does not change after the :NEWPAGE block.
  • If this is a "document page", then the value is incremented in the first block following the :NEWPAGE block.

which should be easy enough to implement.

Device function %tab_width() is documented in the WGML 4 Reference Section 15.7.30 TAB_WIDTH in part as:

When WATCOM Script/GML uses tabbing to produce white space in a
horizontal direction, the result of this device function is a
numeric value which represents the amount of space that is being
tabbed over.

In the :DRIVER blocks available to me, it is used only in the :HTAB block, and there is it used to determine how much space to skip: the function, then, actually returns the "amount of space to be tabbed over".

Device functions %x_address() and %y_address() are used, in some contexts, to state where the print head is supposed to be, and, in other contexts, to state where it currently is. The sections on outputting text lines and applying font styles have some information, but the topic has not yet been sufficiently clarified to be more specific. The following sections are part of an effort to be more specific.

Device function %y_address() returns:

  • "0" during the START :INIT block, the DOCUMENT :INIT block, the implicit %enterfont(0), and the :LINEPROC block :STARTVALUE block and :FIRSTWORD block which follow it.
  • The value changes to that of the first print line with the immediately following block:
    • when the :ABSOLUTEADDRESS block is defined, this is a :LINEPROC block :ENDVALUE block.
    • when the :ABSOLUTEADDRESS block is not defined, this is a :NEWLINE block.
  • After that, the value changes to that of the next print line as part of the normal vertical positioning.
  • During a :NEWPAGE block, whether associated with a "device page" or a "document page", the value is still that of the line just printed out. The value changes as stated directly above, the only difference being (of course) that the line is positioned at the top of the new page.

Device function %x_address() returns:

  • "0" during the START :INIT block, the DOCUMENT :INIT block, the implicit %enterfont(0), and the :LINEPROC block :STARTVALUE block and :FIRSTWORD block which follow it.
  • The value of the :PAGESTART block attribute x_start during the following block(s):
    • when the :ABSOLUTEADDRESS block is defined: the immediately following :LINEPROC block :ENDVALUE block.
    • when the :ABSOLUTEADDRESS block is not defined: the immediately following :NEWLINE block(s) and the :LINEPROC block :ENDVALUE block which follows the :NEWLINE block(s).
  • The position of the left margin in the immediately following :LINEPROC block :STARTVALUE block.
  • If there is neither a title page, nor a header, nor an initial :H0 heading:
    • The position of the left margin during the following :LINEPROC block :FIRSTWORD block and :ENDVALUE block.
    • The initial horizontal positioning (the left margin and the indent specified for the first line of a paragraph) with the following :LINEPROC block :STARTVALUE block.
    • The initial horizontal positioning through all following blocks until the initial horizontal positioning occurs, as discussed here, unless device function %dotab() is encountered.
  • The position of the left margin if there is a title page, or a header, or an initial :H0 heading (either or both) for the following :LINEPROC block :FIRSTWORD block. What happens after that depends on the first line pass sequence: that is, the first line of the title page, or header, or :H0 heading appears, using the normal sequencing.
  • Device function %dotab(), when encountered in any of the :LINEPROC block sub-blocks which appear (including the :ENDVALUE block) results in the initial horizontal positioning occurring, thus positioning the print head at the point returned by device function %x_address(), whether this reflects the left margin, or, the left margin having already been moved over, the indent itself, or, the left margin not having been moved over yet, both together.
  • On the first line pass:
    • If the :ABSOLUTEADDRESS block is not present, "0" in the :NEWLINE block(s).
    • The initial horizontal positioning as discussed here.
    • If the :ABSOLUTEADDRESS block is defined, or if it is not but the :HTAB block is present and defined and used, then the value returned by %x_address() will reflect the initial horizontal positioning.
    • The position of the last character printed within the line as each text_chars instance is processed in:
      • the :LINEPROC block :ENDWORD block;
      • the :LINEPROC block :STARTWORD block for the next text_chars instance, if there is one with the same font number; LINEPROC block :ENDVALUE block if the font number changes, as discussed here.
    • The position of the last character printed in the last :LINEPROC block :ENDVALUE block of the text line; the next line pass follows immediately.
  • For a second or subsequent line pass:
    • If the :ABSOLUTEADDRESS block is not defined, the position of the last character printed (in what is now the preceding line pass) in the :NEWLINE block.
    • "0" during the :LINEPROC block :STARTVALUE, :FIRSTWORD and :STARTWORD blocks.
    • If the :ABSOLUTEADDRESS block is defined, or if it is not but the :HTAB block is present and defined and used, then the value returned by %x_address() will reflect the initial horizontal positioning.
    • As the text_chars instances are processed, the behavior is essentially the same as above. Of course, some text_chars instances may be tabbed over because they do not have a :LINEPROC for the current line pass.

Device functions %thickness(), %x_size(), and %y_size() are associated with "special symbols" in the WGML 4 Reference; for example, Section 15.9.14 HLINE Block states in part:

The special symbols %x_size and %thickness are defined prior to
processing the hline block.

The character "%" is not allowed in a symbol name by wgml 4.0; using command-line option SETsymbol and the "special symbols" without the "%" has no effect on the value returned.

The usage, however, is quite clear:

  • %thickness() returns the value of the attribute thickness for the corresponding block in the :DRIVER block.
  • %x_size() returns the length of the horizontal line (:HLINE block) or the horizontal size of the box (:DBOX block).
  • %y_size() returns the length of the vertical line (:VLINE block) or the vertical size of the box (:DBOX block).

It is not the case that these functions return "0" in other blocks; rather, it is the case that the values these function return are only meaningful in these blocks. Much the same can be said of device function %tab_width(): the value returned in only meaningful in an :HTAB block.

Implementation Notes

These were all implemented by integer variables, which the device functions read and return. The value of those variables will, of course, have to be set at the appropriate time so that the correct value is returned.

This may change when the concepts discussed here are implemented.

Font Values

This section discusses these device functions:

%default_width() %font_height() %font_outname1() %font_outname2()
%font_resident() %font_space() %line_height() %line_space()

In all cases, the value returned by these device functions depends on the current font number, which is used to select the appropriate available font.

Four of these device functions simply return the values of various attributes in the :FONT and :DEFAULTFONT blocks:

%font_height() %font_outname1() %font_outname2() %font_space()

This device function returns a value based on the value of the corresponding attribute in the :DEVICEFONT block:

%font_resident()

This device function returns the value "n" if the value of the attribute was "no", and "y" if it was "yes". Since the value of the attribute font_resident is encoded as a number in the binary file, as discussed here, this involves some translation, which is done when the wgml_font instance is initialized.

The remaining device functions return values that may or may not match the values of the attributes they appear to correspond to:

%default_width() %line_height() %line_space()

The details of how the values returned by these device function are computed are discussed here for device function %default_width() and here for device functions %line_height() and %line_space().

Implementation Notes

These functions all use the current value of font_number (the value of which is returned by device function %font_number()) to locate the proper wgml_font instance. All except device function %font_resident then simply return the value found in the corresponding field.

Device function %font_resident() is a bit more complicated than the others because it turns a single character ('y' or 'n') into a dynamically-allocated character string.

Math Functions

This section is concerned with these device functions:

%add() %divide() %remainder() %subtract()

all of which work as their names imply.

Implementation Notes

The implementations are straightforward. Device functions %divide() and %remainder() check their second parameter and report an error if it is "0".

Currently, the only "context" is the device function name. This is still better than the behavior of wgml 4.0, which is to report an "Abnormal program termination: Divide overflow" message with only the "CS:EIP" to suggest where it ocurred, but some work on providing more of the context might be helpful.

Output Records

The WGML 4 Reference has a lot of information on input records, and some information about output records.

Section 15.7.26 RECORDBREAK:

WATCOM Script/GML forms a line of output for a device. With some
devices, it is desirable to send several of these output lines
together as one record. With other devices, each line and even some
control sequences must be sent as separate records. WATCOM
Script/GML assumes that each record may contain several output
lines. The device function RECORDBREAK instructs WATCOM Script/GML
to send the information in the current record to the output device.

Section 15.9.1.3 REC_SPEC Attribute:

The rec_spec attribute specifies a record specification value (for
example, either (f:80) or (f:c:80) are allowed) for the output
device. The attribute value must be a valid record specification
(see "Files" on page 281).

It might be thought that page 281 contains an intelligible discussion of record specifications, but this is not the case. For example, no example of a record specification for the "Text" record type is provided, although examples are not hard to find, since this type is used in some drivers. From the driver source files and the examples given in the WGML 4 Reference, this appears to be the situation:

  • Record type "Text" is indicated by "t" (source files).
  • Record type "Fixed" is indicated by "F" (manual) or "f" (source files).
  • Record type "Variable" is indicated by "V" (manual).

Case does not appear to matter.

This extract from the description of record type "Text" is relevant here:

Two special characters are used to signify the end of a record. The
CR (carriage return) and LF (line feed) characters separate records
in a text file. These characters are automatically added to the end
of each record, and should not be accounted for when determining
the appropriate record size for the file. The record size of a text
file specifies the size of the largest record which may be read
from or written to that file.

as is this extract from the description of record type "Variable":

A 16 bit number at the beginning of the record specifies the length
of each record. This number is automatically added to the beginning
of each record, and should not be accounted for when determining
the appropriate record size for the file. The record size of a
variable file specifies the size of the largest record which may be
read from or written to that file.

and this extract from the description of record type "Fixed":

The record size of a fixed file specifies the size of each record
read from or written to that file.

What is not explained is the use of "c", as in the example "(f:c:80)" given above. In the existing device source files, "c" is also found with "t" (so the example could just as well have been "(t:c:80)"). However, none of the compiled drivers in the Open Watcom repository use it.

The record length is the numerical part of the record specification.

When used in a :DEVICE block, there is no concept of "output record": the device functions %image() and %text() write their text directly to the screen. Similarly, when used in a :DEVICE block, the device function %recordbreak() sends a CRLF pair to the screen, as can be seen by redirecting the output to a file and using wdump to examine the file in binary. It does this regardless of the record type or record length specified for the output file.

When used in a :DRIVER block, the observed behavior is consistent with the device function %recordbreak() forcing the output buffer to be flushed (as stated above), which also sends a CRLF pair to the device for record type "Text". Examination of an output file produced with a record type of "Variable" show the same result: no preceding record lengths, but a CRLF pair follows each record. The output file produced with a record type of "Fixed", however, had the expected characteristics: no CRLF pair, but the value of attribute fill_char was used to pad each record out to the designated length (for testing, the value of fill_char was set to "." rather than the more usual " ").

From the material quoted above, it is clear that the use of device function %recordbreak() in the various function blocks will depend on the requirements of the device. The record length will also depend on the requirements of the device, as a too-small value will cause CRLF pairs to be inserted into control sequences when the output buffer reaches the specified length, and this may not work very well. No doubt the record length is intended to be the same as the length specified in the manual for the device, if a maximum is specified.

As noted in the section on the :FINISH block, every :DRIVER block available to me has a :FINISH block with a :VALUE block with at least %recordbreak() in it. The documentation generally states that this is intended to ensure that the last line of output is sent to the device.

Implementation Notes

First, some terminology: "the appropriate newline character sequence" will be used instead of "CRLF pair" because Linux does not use the "CRLF pair".

The implementation of device function %recordbreak() for the :DEVICE block simply emits '\n' to the screen in order to produce the appropriate newline character sequence.

The output buffer and the functions for inserting one byte into it, inserting a block of bytes into it, and flushing it exist. The PS augmentations that are best handled as part of these functions are, mostly, done. The character output translation was thought to be done, but is currently being revised to reflect this behavior:

  • If a multi-character translation exists, it is never split up.
  • If it will not fit in the current output record, the buffer is flushed and it is put at the start of the next output record.
  • If is too long to fit into an output record, the behavior is very strange.

Before discussing what happens when a multi-byte output translation will not fit into an output record, it should be pointed out that, in practice, the multi-byte output translations have, generally, less than 5 characters, and the output records have, generally multiple 10s of characters. So, for an actual device, each of the output translations can be expected to fit in an otherwise-empty output record. Also, it should be noted that both the output translations and the output record length are controllable by the implementor of the files defining the device, who should be able to ameliorate any problems. This is important because some devices very probably go to a new line when an output record is flushed, and doing this in mid-word is not likely to produce the desired effect.

What happens when an output translation contains more characters than an output record can hold appears to be:

  • Up to a point (56 characters for the output of device function %text(), 92 characters for actual document text) a longer-than-usual output record is produced. Of course, if the output record length is the maximum the device can accept, this will, at best, result in the record being truncated by the device and could cause the device to misbehave.
  • Beyond that point, the characters may or may not be emitted (for PS they were not, but for a character-based test device they were), but, in either case, wgml 4.0 halts with an access or out-of-memory error.

Given the unlikeliness of actually encountering this situation, the problems emitting an over-large output record might cause, and the plain fact that some limit must exist because computer memory, even in a modern computer with 32-bit addressing and gigabyte swap files, is finite, the implementation is planned to treat an over-large output translation as an error.

The implementation of device function %recordbreak() for the :DRIVER block is very simple: it invokes the output-buffer function which flushes the output buffer, which actually does all the work. This function originally sent '\n' to the output file in order to produce the appropriate newline character sequence, however, it turns out that this does not work with binary files. This behavior is documented in the C Library Reference ("Stream I/O Functions").

Examination of the existing drivers shows that:

  • None of them use "v" in their record specification.
  • At least one of those using "f" in their record specification uses device function %binary1() to emit the hex value "$0a". Since this is the same a '\n', the output file for these record specifications must be opened in binary mode, since, in text mode, the "$0a" would be converted to "$0d$0a", presumably causing problems with the device.
  • Although none of those using "t" in their record specification uses device function %binary1() to emit "$0a", the WHELP device does use %binary(); this shows that this form of record specification is not limited to text, but may include any byte value.

The conclusion is that all output files, regardless of record type, should be opened as binary, not as text.

The function which flushes the buffer has, therefore, been implemented to emit "\n" for Linux and "\r\n" in other cases (that is, for DOS, OS/2, and Windows). Testing and correction, if needed, of the Linux code will have to be done by someone who can test the code under Linux.

At present, all output record specifications from the device library must be enclosed in parentheses, as wgml 4.0 requires. The only recognized output record specifications are those that begin with "t:" and then consist only of decimal digits; "f", "v", "t:c", "f:c", and "v:c" are not allowed. They may be added in the future if our wgml is released more widely. This would affect two locations:

  • The point where the output record specification is validated and, if not valid, replaced with the default specification.
  • The function which flushes the output buffer. The differences between the output record types reduce to differences in the way this function performs its duties.

Page Description

This section discusses these device functions:

%page_depth() %page_width()

These device functions are documented to return the values of the attributes page_depth and page_width in the :DEVICE block.

The WGML 4 Reference, in Sections 15.10.1.6 PAGE_WIDTH Attribute and 15.10.1.7 PAGE_DEPTH Attribute states that the layout can specify a value smaller than the value of the page_width attribute, and either smaller or larger than the value of the page_depth attribute. It also states:

If the page depth in the document layout is larger than this value,
WATCOM Script/GML will produce one document page over several of
the device pages.

which suggests that the :DEVICE block attributes deal with the physical page size and the layout with the logical page size.

Examination of Section 12.3.53 PAGE, however, suggests a slightly different interpretation:

  • The page width is not explicitly given; instead, the left and right margins are specified (in horizontal space units from the left edge of the page). Of course, the right margin might well be required by wgml 4.0 to be within the value of attribute page_width, and wgml 4.0 might also reasonably require the left margin to be to the left of the right margin, or even to leave a certain amount of space for the output text to appear in.
  • There is an attribute depth; however, it's useage is interesting:
Output text starts at the top margin and ends at the bottom margin
of the page. The bottom margin is the sum of the top_margin and
depth attribute values.

So this is not the same concept as the :DEVICE block attribute page_depth: the :DEVICE block attribute page_depth describes the entire (physical) page; the layout attribute depth only describes the area between the top and bottom margins. The depth of the bottom margin is, presumably, the value of the :DEVICE block attribute page_depth minus first the value of the layout attribute top_margin and then the value of the layout attribute depth. Clearly, the result must at least be non-negative for the logical page ("document page") to fit on the physical page ("device page"). Whether the result must be greater than zero for this to happen remains to be determined; it is not clear whether the "output text" referred includes the footers; if it does not, then space for the footers would also have to be taken into account.

Implementation Notes

There is no indication that the values reported by device functions %page_depth() and %page_width() can ever be changed in the course of processing a document specification. Thus, these device functions each simply returns the value from the appropriate field of the struct resulting from parsing the binary device library.

Symbol Table Functions

This section discusses these functions:

%getnumsymbol() %getstrsymbol() %setsymbol()

The only documentation of these functions is in the README file produceable from the WGML 3.33 Update:

%getstrsymbol(p1) - returns the string value of the symbol passed
%getnumsymbol(p1) - returns the number value of the symbol passed
                    returns zero if the symbol is not a valid number
%setsymbol(p1,p2) - sets the value of the symbol(p1) to the string p2

One consequence of this is that a function block like this:

%setsymbol("sal",15)
%image(%hex(%getnumsymbol("sal")))

will produce a result of "0", since the numeric parameter is treated as providing no value for the symbol at all, just as an empty string is.

Symbols are documented, in several locations, in the WGML 4 Reference. The focus here is on how the device functions affected actually work. This is a broader topic than it may appear to be initially.

First, some terminology:

  • A symbol is said to be defined when it is assigned a string value. In the document specification, this is done by :SET (and more specialized tags, such as :DATE); on the command line, by the SETsymbol option; and, in the device library, by the device function %setsymbol().
  • A symbol is said to be accessed when its value is obtained. In a document specification, this is done by prefixing the symbol name with & (which, of course, always results in a character string); in the device library, by the device functions %getnumsymbol() and %getstrsymbol(). If the symbol is not defined, in a document specification, the string starting with "&" and continuing with the symbol name is printed out; in the device library, an empty string is used.

The general situation is quite clear:

  • A symbol defined in a document specification can be accessed in a device library file.
  • A symbol defined in a device library file can be accessed in a document specification.

There are, however, some restrictions on the general situation. For example, consider this statement from the WGML 4 Reference:

If an asterisk is specified immediately before the symbol name (ie
symbol=’*prodname’ or &*prodname.), then the symbol is local. Local
symbols may not be referenced outside the file or macro in which
they are defined. If an undefined local symbol is referenced in a
macro, it is replaced with an empty value.

Now we have "local" symbols and, by tradition if not by usage in the WGML 4 Reference, "global" symbols.

Testing shows this:

  • If a "local" symbol (one starting with "*") is used with device function %setsymbol(), then gendev 4.1 raises no objections but wgml 4.0 produces:
SN--073: For the symbol '*devtestsym'
         The character '*' is not valid

This reflects a general pattern: gendev 4.1 does not check symbol names for validity, but wgml 4.0 enforces the rules given in the WGML 4 Reference.

  • If a "local" symbol (one starting with "*") is used with device functions %getnumsymbol() and %getstrsymbol(), then neither gendev 4.1 nor wgml 4.0 objects in any way; however, even if the document specification has defined such a symbol, its value is not available to the device library file.

So, the net result is that the general situation only applies to global symbols.

Some uses of some symbols are very strange and explored elsewhere; thus, the symbols date and time are explored here because of their alleged relation to device functions %date() and %time().

The WGML 4 Reference, in Section 8.6 Symbolic Substitution, contains this paragraph:

The symbol name should not have a length greater than ten
characters, and may only contain letters, numbers, and the
characters @, #, $ and underscore(_). Specifying the letters SYS as
the first three characters of the symbol name is equivalent to
specifying a dollar($) sign.

Testing verifies all of this; the question here is whether "system" symbols, those starting with "SYS" or "$", are treated any differently from other symbols.

So far as I have been able to determine, they are not. Further testing, however, will be needed once a list of the all of the symbols beginning with "$" or "SYS" actually used by wgml 4.0 is known. Examination of those given in the manual suggests that these were literally "system symbols" when wgml was used on an IBM mainframe and/or VAX, which would make them similar to DOS environment variables, although there is no indication that, for wgml 4.1 under DOS or OS/2, they have any relation to environment variables any longer.

Implementation Notes

These device functions were all implemented in terms of the functions provided for access to the global symbol table within our wgml.

Uses in Testing

In the device library, a symbol which is defined in the START :PAUSE block has that value in all other blocks. This fact was used in the testing which produced the initial results reported here to simplify the output file to show only one group of outputs from each block in the :DRIVER block: in the START :PAUSE block a series of symbols were defined:

%setsymbol("dohtab","true")

and, in the :HTAB block, the framework

%if(getstrsymbol("dohtab","true")
   <test code>
   %setsymbol("dohtab","false")
%endif()

was used to restrict :HTAB output to the first time it was used.

From time to time, in testing, it was useful to be able to tell which invocation of a block I was looking at. This set of device function sequences:

%setsymbol("count",%decimal(%add(1,%getnumsymbol("count"))))
%image("Instance: ")
%image(%getstrsymbol("count"))
%recordbreak()

outputs "Instance: " followed by a number from 1 to however many times the block is used. Each block, of course, must use its own symbol name, or the counts will be intermingled since the blocks will share the common symbol.

The order of some pairs of blocks was determined by defining a symbol in one and outputting its value in the other: when this works, the block defining the symbol was used before the block outputting its value. Thus, a symbol defined in the START :PAUSE block has its value in the START :INIT block, but a symbol defined in the START :INIT block has no value assigned to it in the START :PAUSE block. This idea was later greatly expanded to associate a value of a symbol with each :FONTPAUSE block instance, thus allowing not only the determination of when the :FONTPAUSE block is interpreted, but also which blocks appearing in the output file were associated with each instance.

Systemic Functions

This section discusses these functions:

%date() %time() %wgml_header()

They are "systemic" in the sense that they return a value that, by default, is established independently of the document specification, device library, or command line.

The WGML 4 Reference Section 15.7.8 DATE describes device function %date() in this way:

The result of this device function is a character value
representing the current date. If the symbol &date is defined, the
value of this symbol is returned.

and Section 15.7.33 TIME describes device function %time() in this way:

The result of this device function is a character value
representing the current time of day. If the symbol &time is
defined, the value of this symbol is returned.

No such claim is made for device function %wgml_header().

If these device functions are inserted in the END :FINISH block:

%image(%date()) %image(' ') %image(%time()) %image(' ')
%image(%getstrsymbol("date")) %image(' ')
%image(%getstrsymbol("time"))

then the output varies, not so much by the date and time, as by whether or not the tags :DATE and :SET, the command-line option SETsymbol (which appears below as "set"), and the device function %setsymbol() are used.

Using these forms of those items:

%setsymbol('date','99:99:88')
%setsymbol('time','00:00:11')
( set 'date' '99:99:99'
( set 'time' '00:00:00'
:SET symbol='date' value='88:88:66'.
:SET symbol='time' value='00:00:22'.
:DATE.88:88:77

produces a wide variety of results.

If only one item at (:SET, :DATE, %setsymbol(), set) is used, we get:

Item Used    Result
none         July 28, 19108 11:02:00 July 28, 19108 11:02:00
:SET         July 28, 19108 11:02:00 88:88:66 00:00:22
:DATE        July 28, 19108 11:02:00 88:88:77 11:02:00
set          99:99:99 00:00:00 99:99:99 00:00:00
%setsymbol() July 28, 19108 11:16:42 July 28, 19108 00:00:11

which suggests these conclusions:

  • The device functions %date() and %time() are most certainly not returning the value of the symbols "date" and "time".
  • Device function %setsymbol() cannot set the symbol "date".
  • Tag :SET can set both the symbol "date" and the symbol "time".
  • Tag :DATE can set the symbol "date", as it is documented to do.
  • Command-line option SETsymbol can set not only the symbols "date" and "time" but also whatever items the device functions actually are reporting the value of.

For device function %time(), at least, by using a large file and a large number of document passes, it was possible to compare the value returned in the START :INIT block and the END :FINISH block when none of the items tested were in use and the file took several seconds to process. The values reported were identical. For device function %time(), then, the value reported is obtained from the system clock and stored at some point during initialization, and then does not change. It seems reasonable to believe that device function %date() works the same way.

As to the form in which the values of %date() and %time() are stored, all that can be said with certainty is that system symbols ("$date" and "$time") are not used: testing with the latter symbols produced the same effect as the "none" line above. Since the command-line option can change the values they return, something like a symbol containing a character string must be what is used (as opposed, say, to storing and converting the numeric value obtained from the system each time it is needed).

The symbol names were shown with a preceding "&": this is not an allowed character in symbol names. When SETsymbol or %setsymbol() were used with "&date" and "&time", wgml 4.0 produced this message:

SN--073: For the symbol '&date'
         The character '&' is not valid

When :SET was used, this message was produced:

SN--073: For the symbol 'July 29, 19108'
         The character ' ' is not valid

which shows that the symbol "date" was expanded inside the :SET line.

The format of the date, but not of the time, can be specified in the :LAYOUT section. This only affects the symbol: the value returned by device function %date() is not affected. As might be expected, the format specified in the :LAYOUT section, if different from the default format, is not used in the START :PAUSE block or in the START :INIT block, since the layout has not been processed at that point, but does appear in the DOCUMENT :PAUSE block, the DOCUMENT :INIT block, and the :FINISH block, and so very likely throughout the other blocks as well, all of which are interpreted after the layout has been processed. Note that, in the document itself, the specified format will always be used, since the layout is processed before the rest of the document specification is.

There is no indication that %wgml_header() ever returns anything other than:

V4.0 PC/DOS

making it a relatively dull, if easily-implemented, device function.

Implementation Notes

The separation between device functions %date() and %time() and the symbols "date" and "time" is the difference between the date and time of the document content and the date and time of the document production by wgml. The device functions allow each output file produced to be uniquely identified; the symbols allow each copy of the same content, whenever produced, to use the same date and time, thus eliminating the confusion that might arise if the same content bore, on its cover page or in its headers or footers, different dates or times depending on when each particular copy was produced. This distinction is maintained in our wgml.

This is done by first setting the symbols from the system clock on startup, and then applying command-line option SETSYMBOL, if used with either symbol, during command-line processing. During the loading of the device library, variables are initialized to the value of the symbols at that point. The device functions return the values of these symbols. Thus, from this point on, changes to the values of the symbols produced by the document specification will not affect the values returned by the device functions, which are, in fact, invariant.

When the START :PAUSE block is interpreted, device function %date() and %time() return an empty string even when SETsymbol has been used on the command line. This is implemented by setting up the appropriate function table to call a function that returns an empty string.

Text Output

This section discusses these device functions:

%decimal() %hex() %image() %lower() %text() %textpass()

and this related topic:

implicit %textpass() in :FONTSTYLE blocks

all of which relate to text output.

The WGML 4 Reference provides some information on two of these device functions:

Section 15.7.20 IMAGE

The required parameter must be a character value. The result of
this device function is a character value representing the given
parameter, and is a final value. The result of this device function
may not be used as a parameter of another device function, and is
not translated when sent to the output device.

Section 15.7.31 TEXT

The required parameter must be a character value. The result of
this device function is a character value representing the given
parameter. The result of this device function is a final value, and
may not be used as a parameter of another device function. The
result is translated when sent to the output device.

Although the statements above imply that the translation occurs when the result is sent to the device, it is far more likely that it is done when it is put into the output buffer since, once in the buffer, it would take extra effort to distinguish translatable characters from untranslatable characters (those inserted by %image()).

In the :DEVICE block, %image() and %text() cannot be distinguished: both display the "character value representing the given parameter" to the screen. There is no indication of any buffering by wgml 4.0; and there is definitely no output translation in either case.

In the :DRIVER block, however, testing shows that it is true that %image() does not have its output translated, while %text() does have its output translated.

Testing to determine which font's :OUTTRANS table was used with %text() in the :DRIVER block in which contexts produced an interesting result: when a new major document section separates two fonts other than the default font, the default font is nonetheless used for the :OUTTRANS table, although the value returned by device function %font_number() is not "0" during the initial vertical positioning. Further, the normal font switch occurs in the new major document section: that is to say, the default font is neither switched to nor switched from (unless it normally would be). Research shows that the font number used matches the value returned by %font_number() in all other instances.

Although the very name of device function %textpass(), and the appearance of (output-tranlated) text in the output file would suggest that %textpass() actually translates and outputs text, this turns out not to be quite true. When :FONTSTYLE "plain" was implemented explicitly with a :LINEPROC 1 block :STARTVALUE block containing not only a %textpass() but also with %image(':') preceding %textpass() and %image(';') following it, it turned out that the entire :STARTVALUE block is interpreted before any text output appears; that is, ":;" appeared before the text for that text segment appeared.

So, if %textpass() were documented in the WGML 4 Reference (which it is not), this is what might be found:

This device function takes no parameter. This device function
tells wgml that the current segment of document text is to be
output. The result of this device function may not be used as a
parameter of another device function, and is translated when sent
to the output device.

In effect, then, %textpass() sets a flag which wgml 4.0 uses to determine whether or not the text associated with the current line pass for the current text_chars instance is to be output or not.

However, it is clearly not necessary to use %textpass() to cause text to be inserted into the output buffer. This is clear from the discussion toward the end of the :FONTSTYLE block of the font style "plain": wgml 4.0 is (in all existing binary driver files) presented with an empty ShortFontStyle, yet this is the default style for any document, and wgml 4.0 clearly outputs the text assigned to it. The question, then, is: under what conditions will wgml 4.0 do an implicit %textpass()?

The answer is quite clear: whenever there are no :LINEPROC instances. When a :LINEPROC instance is present, then at least one of the :LINEPROC instances must contain a %textpass() in a function block. Note that the :STARTVALUE and :ENDVALUE blocks that pertain to :FONTSTYLE (that, that are not found within any :LINEPROC block) may be present (either or both), or not present, without affecting this behavior.

Device functions %decimal() and %hex() are used to convert numeric values to character strings, which can then be used with device functions %image() and %text(). They can also be used with %setsymbol(); however, %getnumsymbol() will only return a numeric value if the symbol contains a decimal number in character string form, so using %hex() with %setsymbol() produces a symbol that cannot be treated as containing a numeric value. Device function %decimal() does not use separators to break up the character string in any way. Device function %hex() does not mark its output in any way: no "0x", no "$", no "H", just the hex digits which represent the input value.

Device function %lower() is used to convert a character string to all lower case. A typical use is with a conditional device function, where it is used to convert the value of a symbol to lower case so that it can be compared to a lower-case literal to determine whether or not one or more device functions are to be interpreted or not. Given the age of gendev 4.1 and wgml 4.0, it is entirely likely that this function only works on the strict 7-bit ASCII character set.

Implementation Notes

Device functions %decimal(), %hex(), and %lower() were implemented in the most obvious manner possible and used in all blocks.

Device function %textpass() was implemented to set a boolean variable (initialized to "false") to "true". Further work will be needed to implement the implicit %textpass() (which will also set the variable to "true") and the actual output of the document text (which will, among other things, set the variable back to "false").

For the :DEVICE block, %image() and %text() were implemented with a single function, which writes the parameter to the screen.

For the :DRIVER block, the obvious implementations of %image() and %text() would have been almost identical, differing only in a flag passed to the function used to insert the text into the output buffer. For this reason, a single function was created to implement their actions, reducing the functions used in the function table to calling the common function with different parameter values, which it then passed on to the output buffer function. There are two parameters involved:

  • out_trans indicates whether or not output translation is to be performed; and
  • out_text indicates whether or not the block being inserted into the buffer is intended to appear in the finished document or if it contains device control information used to configure the device to produce the document.

The first is set to "false" by the function implementing device function %image(), and to "true" by the function implementing device function %text(), and can be expected to be set to "true" by whatever function outputs the document text. Only this latter function can be expected to set the second to "true".

The output buffer function, in addition to the two flags, must also take into account a flag indicating whether or not the document is being prepared for the PS device. This will make it quite complicated, when it is fully implemented.

User Interaction Device Functions

This section discusses these device functions:

%clear3270() %clearPC() %wait()

These device functions are used with %image() or %text() to communicate with the user.

When tested, these functions behave pretty much as documented:

  • They do nothing at all in a :DRIVER block.
  • In a :DEVICE block, %clearPC() clears the screen of an NTVDM under Windows XP.
  • In a :DEVICE block, %clear3270() also clears the screen of an NTVDM under Windows XP. This was unexpected.
  • In a :DEVICE block, %wait() causes wgml to wait until the user presses the Enter key (only).

Whether %clear3270() also clears an OS/2 MDOS window or genuine DOS has not been determined (%clearPC(), presumably, does). It is also not clear whether ANSI.SYS is needed for %clearPC() and/or %clear3270() to work: even redirecting the output to a file is not definitive, since the window does clear and so ANSI.SYS could be intercepting the character codes. At some point, testing under actual DOS and under OS/2 may be needed.

Since %wait() only responds to the Enter key, the intent must be for the messages to instruct the user to perform such actions as:

  • turning on the printer
  • putting paper in the printer
  • putting the next page into the printer
  • physically changing the font (cartridge or daisy-wheel)

and then to press the Enter key when the action has been done. The only :DEVICE available to me that actually uses them (in a :PAUSE block) is TERM, which displays a document to the screen. No examples of a :FONTPAUSE exist in any of the source files available to me.

Implementation Notes

Device function %clearPC() was implemented following some of the advice received on the contributor's newsgroup and email from my co-implementor. Since "system( "cls" );" works for all variants of DOS, Windows XP, and OS/2 Warp 4 tested, that is used for most cases. Since Linux does not support the "cls" command but does support the ANSI command sequences, code to output "\033[2J" under Linux was added. This should allow device TERM, the only device using device function %clearPC(), to work normally.

OS-specific solutions presented were:

  • for MS-DOS itself (more correctly, I suspect, this is for the PC BIOS):
This was written for Microsoft Fortran &c some time ago.  SAVE and
RESTORE are macros for registers used by MS HLLs in 16 bit real
mode code.)
;
cls            proc     far
clears         label        far
clearscreen    label        far
               public       cls,clears,clearscreen
               save
               mov      ah,0fh
               int      10h
               mov      ah,0
               int      10h
               mov      dx,0
               mov      ah,0
               restore
               ret
               db       '(c) 1986-1993 Kevin G. Rhoads, all rights reserved.'
cls            endp
;
  • for OS/2:
For OS/2 use the Vio functions:

- VioGetMode to get the number of rows and columns
- VioWrtNChar to write a space form starting row/col 0/0 with a
  count computed from the rows and columns from VioGetMode.
- VioSetCurPos to set cursor row/col to 0/0

and:

from the OS/2 toolkit examples in samples\rexx\api\rexxutil\rexxutil.c:
/******************************************************************
* Function: SysCls                                                *
* Syntax:   call SysCls                                           *
*                                                                 *
* Return:    NO_UTIL_ERROR -Successful.                          *
*******************************************************************/

ULONG SysCls(PUCHAR name, ULONG numargs, RXSTRING args[],
             PSZ queuename, RXSTRING *retstr)
{
  BYTE bCell[2];                        /* Char/Attribute array */

  if (numargs)                        /* arguments specified? */
    return INVALID_ROUTINE;                /* raise the error */

  bCell[0] = 0x20;                         /* Space Character */
  bCell[1] = 0x07;                          /* Default Attrib */
  VioScrollDn( 0, 0, (USHORT)0xFFFF, (USHORT)0XFFFF,
              (USHORT)0xFFFF, bCell, (HVIO) 0); /* CLS */
  VioSetCurPos(0, 0, (HVIO) 0);                   /* Pos cursor */
  BUILDRXSTRING(retstr, NO_UTIL_ERROR); /* pass back result */
  return VALID_ROUTINE;                    /* no error on all */
}

and:

In REGUTIL (a REXXutil for Winxx) are versions for WInxx and *nix.

Homepage is http://pages.interlog.com/~ptjm/
  • for Windows:
For Win32, check http://support.microsoft.com/kb/99261.  The system
function method should work on DOS, too.

This, of course, is the method adopted for everything except Linux.

Nothing specific was proposed for Linux, but one poster did state that the ANSI command string should work in most cases. Another did state (with regard to Linux):

A possible source for this information may be
http://pdcurses.sourceforge.net/index.html
It should implement a clear_screen() for all platforms mentioned,
and the source is in the public domain.

Any or all of these suggestions may be pursued in the future if the current solution does not work.

Device function %wait() was implemented to accept any key pressed by the user without echo. Because, in wgml 4.0, only a return key is accepted and it is then echoed, the implementation of device function %recordbreak() for the :DEVICE block is then invoked. This should allow device TERM, the only device using device function %wait(), to work normally.

Device function %clear3270() should cause gendev to issue an error message stating that it is no longer recognized, and halt processing. If gendev/wgml is released, device function %clear3270() should restored only if a user needs it and is willing to help test the implementation to ensure that it works correctly.

Using :UNDERSCORE

This section discusses the effects of these device functions:

%ulineoff() %ulineon()

As noted here, these functions can only appear in certain places.

The effect of %ulineon() and %ulineoff() is reminiscent of the behavior documented in the WGML 4 Reference for the keyword font styles "underline/uline" and "underscore/uscore".

The examples that follow are based on these conditions:

  • The file "default.opt" contains one or the other of these lines:
( font 0 tfon01 uline 9.0 9.0
( font 0 tfon01 uscore 9.0 9.0

depending on which font style is to be used, plus this line:

( font 1 tfon02 plain 9.0 9.0

The effect is to turn text using the default font into text using either font style "uline" or font style "uscore", and text marked with :HP1. into text using font style "plain".

  • The first paragraph of the text file used starts:
:P.This is the :HP1.first sentence:eHP1.

This phrase, through the word "first" (that is, not including "paragraph") is the first output line when the test device is used. As a result, "This is the " (note trailing space) becomes a useful example of how the device functions %ulineon() and %ulineoff() work.

  • The default layout, which turns justification "on", is used. This is why the single spaces in the test phrase become multiple spaces in the output.
  • The :DEVICE block contained :OUTTRANS blocks converting spaces to "|" characters, which will appear in the examples shown.
  • The examples use the output from a :DRIVER block that does not provide an :ABSOLUTEADDRESS block.

When these functions were tested by themselves, that is, without device function %dotab(), the result was:

||||||This||||is|||the|||
_________________________

This resulted whether the font style was "uline" or "uscore". It is incorrect because the left margin is underlined. The left margin is underlined regardless of which sub-block device function %ulineon() is placed in.

Device function %dotab() invariably precedes device function %ulineon() and usually precedes device function %ulineoff() in actual :DRIVER blocks. Testing confirmed that the presence or absence of device function %dotab() before device function %ulineoff() made no difference whatsoever to the result.

When device function %dotab() is added to the :FIRSTWORD or :STARTWORD block before device function %ulineon(), however, these results were obtained:

  • for font style "uline":
||||||This||||is|||the|||
||||||___________________
  • for font style "uscore:
||||||This||||is|||the|||
||||||____||||__|||___|||

both of which are correct.

If the device function %ulineon() is not the last device function in the function block, that function block is still interpreted before the underscore characters appear. This suggests that %ulineon() and %ulineoff(), like %textpass(), work by manipulating a flag: %ulineon() turns it on, %ulineoff() turns it off, and wgml 4.0 inserts underscore characters instead of what each text_chars block would normally produce while the flag is on.

Testing other pairs of :LINEPROC block sub-blocks produced these results:

  • for :STARTVALUE block and :ENDVALUE block:
||||||This||||is|||the|||
||||||___________________
  • for :STARTVALUE block and :ENDWORD block:
||||||This||||is|||the|||
||||||____
  • for :FIRSTWORD block and :ENDWORD block:
||||||This||||is||||||
||||||____
  • for :STARTWORD block and :ENDVALUE block:
||||||This||||is|||the|||
||||||____||||__|||___|||

The last result appears to contradict the "flag on/off" model; however, examination of the markers shows that ":ev2us" (that is, the :ENDVALUE block of the second line pass of font style "uscore", see the introduction to this section) shows that, at least when the :UNDERSCORE block used a character string as the value of its font attribute, the :ENDVALUE block is interpreted before the underscore characters are output. Other types of values for this attribute may produce different results.

Implementation Notes

This section is concerned with the implementation of device functions %ulineon() and %ulineoff(), not of the various :LINEPROC sub-blocks or how font styles are applied.

These functions were implemented to toggle a flag. The code which implements that font style application sequence will be the place to handle how the flag is interpreted, as well as any variations dependent on the type of value used for the font attribute of the :UNDERSCORE block.

Implementation of the actual output of underscores resulted in the discovery that this occurs even when the first pass suppresses the text (as this :LINEPROC does). In terms of the flags, the effects of the flag set by device function %textpass() and of the flag manipulated by device functions %ulineon() and %ulineoff() are independent of each other.

Other Functions

This section gathers together those device functions that fall into no other category. Each has its own subsection.

%cancel() Implementation

The WGML 4 Reference, section 15.7.5 CANCEL, has this to say about device function %cancel():

Some devices may cancel more than one operation with a single
control sequence. For example, some devices may stop underlining
when bolding is turned off. The cancel device function specifies
the type of device operation that has been cancelled. WATCOM
Script/GML will then re-establish the cancelled operation. The
possible values of the required character parameter are bold,
underline, and the name of a font switch method. The name of a
font switch method is specified when the device automatically
switches to a default font.

The available device library source files contain one :DRIVER block (D630DRV.PCD) using device function %cancel(); it is used in the :NEWLINE and :NEWPAGE blocks.

Using the "plain" text file described at the end of this section, but with device function %cancel() inserted into the :NEWLINE blocks with the font switch for the font used as its argument, the corresponding :FONTSWITCH block :STARTVALUE block does indeed appear within the :LINEPROC output. If a different font switch is specified, no :FONTSWITCH block :STARTVALUE block appears within the :LINEPROC output.

From this, it appears that the device functionality indicated by the parameter is only switched on if it was in effect before the :NEWLINE block was interpreted.

This result was then extended to produce the information presented here. The net result is quite simple: in :DEVICE blocks, nothing happens; in :DRIVER blocks, more-or-less the expected output appears, and the main difference involves using the "to" font rather than the "from" font in the "switch-from" part of a font switch. However, if it is kept in mind that the value returned by device function %font_number() is the "to" font throughout the font switch, this simply means that wgml 4.0 is actually doing this:

  1. Determine the current font number.
  2. Compare the parameter of device function %cancel() with the value of the type attribute of the :FONTSWITCH block associated with the font number (the :DEFAULTFONT block) through the :DEVICEFONT block.
  3. If they are equal, then interpret the :FONTSWITCH block :STARTVALUE block.

In other words, it does not actually check to see if the indicated font has, from the viewpoint of the device, been switched to or not.

Of course, from the viewpoint of the implementor, device function %cancel() works as intended, for it is only intended to be used after a command string is issued to the device that cancels something that, if in effect, should not have been cancelled and so needs to be restored.

Using the same test framework with a font style name (that is, a value matching the value of the type attribute of a :FONTSTYLE block) produced no results whatsoever, regardless of the value returned by device function %font_number(). Apparently, this function was intended to work with the pre-3.33 "directives" and not with the :FONTSTYLE block, and was never updated to do so.

Implementation Notes

The implementation was very simple, since it consisted primarily of string comparisons. It was implemented to check both the :FONTSTYLE name and the :FONTSWITCH name, and to interpret :STARTVALUE block of the matching block, if any. A recursion check was also implemented, since it makes no sense for %cancel() to be used in the :STARTVALUE block of the very same :FONTSTYLE block or :FONTSWITCH block which its parameter names. Having gendev catch these cases in the source code should be considered.

Extending this function was quite safe since it is not needed at all for the Open Watcom build system and so cannot affect it.

%dotab() Implementation

Documentation of this device function appears to be non-existent, so it is necessary to investigate it in some detail.

This device function is seen in these function blocks in the :DRIVER blocks available to me:

:LINEPROC
    :STARTVALUE
    :FIRSTWORD
    :STARTWORD
    :ENDWORD
    :ENDVALUE
:FONTSWITCH
    :STARTVALUE
    :ENDVALUE

It is not used in any of the :DEVICE blocks or :FONT blocks available to me.

As documented above, device function %dotab() appeared to do nothing in most blocks when the context of interpretation was explored, to behave quite oddly in the :ABSOLUTEADDRESS block, and to emit spaces in three blocks:

:HTAB
:FONTSTYLE :ENDVALUE
:FONTSWITCH :ENDVALUE

Consider first this :HTAB block:

:HTAB
   :VALUE
      %dotab()
   :eVALUE
:eHTAB

Retesting with the space-to-"|" conversion added confirmed that it did produce spaces. As might be expected, it did this when the :HTAB block was interpreted.

The results of testing device function %dotab() in the blocks shown above are summarized here. The only additional comments needed are:

  • Not allowing the execution of %dotab() to result in the use of the :HTAB block avoids any possibility of recursion.
  • The above version of the :HTAB block is almost like having no :HTAB block at all; the only difference is that the :ABSOLUTEADDRESS block, if available, will be used within an output line.

In exploring the :FONTSWITCH block :ENDVALUE block, a curious variation was found: if device function %dotab() is followed by any device function, except a second %dotab(), and the :ABSOLUTEADDRESS block is available, then the :ABSOLUTEADDRESS block has the same instance as the :FONTSWITCH block :ENDVALUE block (that is, this occurs before the :FONTPAUSE block is interpreted). One (or two) device function %dotab() invocations at the end of the :FONTSWITCH block :ENDVALUE block results in the :ABSOLUTEADDRESS block having the same instance as the following :FONTSWITCH block :STARTVALUE block (that is, this occurs after the :FONTPAUSE block).

Although device function %dotab() is used in most of the :ENDWORD and :ENDVALUE blocks of :LINEPROC blocks which also contain device function %ulineoff(), the discussion here shows that this has no effect and the discussion here shows that nothing is emitted because of device function %dotab() in these blocks. This is because these blocks are generally interpreted after the text is output, at which point no further movement of the print head is needed. At the start of the output file, however, it is possible for the :ENDVALUE block to encounter a situation where the print head is not at the position reflected by the value returned by device function %x_address(), and, in this context, device function %dotab() does result in the emission of spaces or the use of the :ABSOLUTEADDRESS block. The :ENDWORD block does not ever occur in the proper context to test whether or not device function %dotab() would emit spaces or use the :ABSOLUTEADDRESS block, but it seems likely that it would if any horizontal positioning needed to be done at that point.

So, what does this function do, that is, why does it exist? This function causes the horizontal positioning to be done before any device functions following it in that block are executed. As shown here, this makes all the difference between failure and success in implementing the font styles "uline" and "uscore".

Implementation Notes

The implementation, at present, interprets the :ABSOLUTEADDRESS block if it exists and, if it does not, uses a local buffer to contruct a set of spaces of the desired length. If the device is PS, this set of spaces is preceded by "(" and followed by ")|shwd|", as discussed here. When the buffer is used, it is then inserted into the output buffer using the block insertion function configured for both applying any :OUTTRANS block and for text intended to appear in the finished document. Since the block insertion function is itself in a provisional form, some adjustment may be needed in the future.

Parts of this code may be moved once the code for outputting text is created.

The variation in the interpretation of the :ABSOLUTEADDRESS block with respect to the interpretation of the :FONTPAUSE block is ignored: since none of the devices available have a :FONTPAUSE block, and since the :FONTPAUSE block is (almost certainly) not intended to affect the output file, then, so long as the horizontal positioning is done correctly, there should be no reason to implement this peculiarity. Unless we release wgml to a wider audience and someone complains, of course.

%enterfont() Implementation

The device function %enterfont() is required to have a parameter, but the type is not documented anywhere. This is how it is used in the source files available to me: it appears as the last line of a :VALUE block inside a START :INIT block. The corresponding comment is:

Enter font zero.

and the actual invocation is

%enterfont(0)

This strongly suggests that it takes a numeric parameter. It's actual behavior, however, whether the invocation is implicit or explicit, is to interpret various blocks associated with available font "0":

  1. The :FONTPAUSE block.
  2. The :FONSTSWITCH block :STARTVALUE block.
  3. The :FONTSTYLE block :STARTVALUE block.
  4. These blocks from the line pass 1 :LINEPROC block within the :FONTSTYLE block:
    1. The :STARTVALUE block.
    2. The :FIRSTWORD block, if that block exists; if not, the :STARTWORD block.

Within these blocks, the values returned by device functions %font_number(), %pages(), %x_address() and %y_address() are always "0". No text appears even if the :LINEPROC block :STARTVALUE block contains device function %textpass(): this is not part of any text output sequence.

It might be thought that gendev 4.1 was silently replacing the parameter given with "0", but in point of fact gendev 4.1 places the parameter entered, whether numeric or character, regardless of value, in the compiled form; it is wgml, not gendev, that is responsible for ignoring the parameter and always applying %enterfont() to available font "0".

An implicit %enterfont(0) is performed by wgml 4.0 immediately after the DOCUMENT :INIT block, so the effect of putting an explicit %enterfont(0) as the last line of a :VALUE block inside a START :INIT block is to cause this to be done both before and after the DOCUMENT :INIT block. This would be very useful if the DOCUMENT :INIT block for a particular device emits instructions to the device that require the initial font to have already been selected, provided, of course, that the blocks interpreted as a result of using %enterfont() actually cause a font to be selected by the device.

The number of %enterfont(0) interpretations in an output file is limited only by the total number of explicit invocations plus one (for the implicit invocation). It does not matter which :INIT block it is placed in, or whether it is in a :VALUE block or a :FONTVALUE block (except, of course, that in a :FONTVALUE block it will be interpreted once for each available font -- but always using available font "0" to determine which blocks to interpret).

Implementation Notes

The action of this device function was implemented separately ("fb_enterfont()") so that it could be used for the implicit invocation of the device function.

The device function itself was implemented to ignore its parameter and invoke fb_enterfont().

%flushpage() Implementation

The WGML 4 Reference, section 15.7.12 FLUSHPAGE, has this to say about device function %flushpage():

This device function causes WATCOM Script/GML to flush the current
page to the output device. The page flush is obtained by printing
enough blank lines to fill the current page. If the size of the
document page is greater than the size of the output device page,
the page flush will print enough blank lines to flush the current
device page. If no data has been output to the device, and the page
is the first page in the document, the current page will not be
flushed.

That last statement is only correct when it is understood to mean "before the initial vertical positioning": device function %flushpage is indeed ignored prior to that point, but it is active from the start of the initial vertical positioning onwards.

Examination of the available :DRIVER blocks shows that device function %flushpage() is used in two places:

  • In TERMDRV, in the :NEWPAGE block.
  • In TTYDRV, in the :FINISH block.

As noted here, preliminary tests showed possible output when this device function was placed in the :HTAB block, and recursion in several other blocks.

More detailed testing revealed these facts:

  • The above statement appears to be correct for :DRIVER blocks which do not define an :ABSOLUTEADDRESS block.
  • When the device function has a visible effect, that effect is a sequence of :NEWLINE blocks sufficient to move the print head to the bottom of the page.
  • This function may invoke the :LINEPROC block :ENDVALUE block for line pass 1 of available font 0. When invoked within the the initial vertical positioning, this is always done after the final :NEWLINE block. In all other contexts, this is done before the first :NEWLINE block if the flag text_output is set to "true".
  • If an :ABSOLUTEADDRESS block is defined, the only place device function %flushpage() has any effect is in that block, and that effect is recursion plus bizaare error messages.
  • If an :ABSOLUTEADDRESS block is not defined, then using device function %flushpage() in the various :NEWLINE blocks can cause recursion -- if the result of the %flushpage causes that same :NEWLINE block to be used.
  • The :LINEPROC recursions can all be attributed to the fact that device function %flushpage() was also placed in the :ABSOLUTEADDRESS block or :NEWLINE blocks; it appears to be those blocks that actually caused the problem.
  • The :HTAB block did not show anything to suggest that a new page was triggered; however, it did, in some cases, halt processing, apparently because the line number (the value returned by device function %y_address()) became to large (it reached 200, when the device page length was 24).

Interesting as the details may be, this function can be characterized as intended to be used in :NEWPAGE and :FINISH. Indeed, for devices that have no actual "new page" (or "form feed") command sequence, this :NEWPAGE block:

:NEWPAGE
   :VALUE
      %flushpage()
   :eVALUE
:eNEWPAGE

should produce the correct result, although the example in TERMDRV does preceed device function %flushpage() with device function %recordbreak(), apparently to ensure that the output buffer is flushed first. As might be expected, the :FINISH block in TTYDRV follows device function %flushpage() with with device function %recordbreak(), to ensure that the output buffer is flushed at the end of the document.

This device function has no effect on the value returned by device function %pages() but it merely increases the number of device pages which each document page will occupy. This is one more indication that the binary device library code cannot affect the document layout code, except through the use of %setsymbol() and the use of the symbol value defined by %setsymbol() in the document specification.

Implementation Notes

This function is ignored initially by wgml 4.0. Initially, then, it is implemented by a function that does nothing. At the appropriate point, the actual implementing function is inserted into the interpreter's function table.

The implementation is focused on producing the correct behavior when device function %flushpage() is used in the :NEWPAGE block and in a :FINISH block. Our gendev should probably restrict it to those blocks, as it really makes no sense to use it anywhere else.

To implement the treatment of the :LINEPROC block :ENDVALUE block for line pass 1 of available font 0, these steps are taken:

  1. The text_output flag is checked before interpreting any :NEWLINE blocks and the :LINEPROC block :ENDVALUE block for line pass 1 of available font 0 is interpreted if it is "true"; the flag is then set to "false".
  2. The at_start flag is checked after interpreting all :NEWLINE blocks and the :LINEPROC block :ENDVALUE block for line pass 1 of available font 0 is interpreted if it is "true"; the flag is then set to "false".

Prior to deciding whether or not to interpret any :NEWLINE blocks, these values are set:

  1. The value returned by device function %x_address() is set to the value of the :PAGESTART block attribute x_start.
  2. The value returned by device function %y_address() is set to the value of the attribute page_depth.
  3. The value of desired_state.y_address is set as discussed next.

The value of desired_state.y_address depends on two items:

  1. The value of the attribute y_positive of the :PAGEADDRESS block.
  2. A peculiarity of integer arithmetic: if the value of current_state.y_address equals an even multiple of the value of the attribute page_depth, then dividing the former by the latter will produce a result which is larger by "1" than it should be.

The net result is that the value of desired_state.y_address is set in either of two ways:

  • If the value of the attribute y_positive is "yes", then the line numbers are formed by addition, and this formula is used:
(((current_state.y_address + 1) mod page_depth) + 1) * page_depth
  • If the value of the attribute y_positive is "no", then the line numbers are formed by subtraction, and this formula is used:
((current_state.y_address + 1) mod page_depth) * page_depth

The implementation then determines whether or not to interpret any :NEWLINE blocks. If the :ABSOLUTEADDRESS block is defined, it does not interpret any :NEWLINE blocks but it also does not interpret the :ABSOLUTEADDRESS block because testing shows that device function %flushpage() clearly does not do so in wgml 4.0. If the :ABSOLUTEADDRESS block is not defined, then the implementation interprets :NEWLINE blocks sufficient to move the print head to the start of the last line on the page, so that the next line output will be at the top of the next page. When a :NEWPAGE block is being interpreted, this may leave additional lines to be done on the new page.

After this, these values are set to reflect the current position of the print head:

  1. The value returned by device function %x_address() is still set the value of the :PAGESTART block attribute x_start.
  2. The value returned by device function %y_address() is set to the value of the :PAGESTART block attribute y_start.
  3. The value of the field current_state.y_address is set to the vertical position immediately before the first line of the current page.
  4. The value of the field desired_state.y_address is set to the value it had when the function was entered.

The first two are necessary to produce the correct effect in the :FINISH block or the :NEWPAGE block:

  • If the :FINISH block or the :NEWPAGE block does not contain device function %flushpage(), then the values returned by device functions %x_address() and %y_address() are the same as the last block before the :FINISH block or the :NEWPAGE block.
  • If the :FINISH block or the the :NEWPAGE block does contain device function %flushpage(), then the values returned by device functions %x_address() and %y_address() are the values of the :PAGESTART block attributes x_start and y_start.

The last two are necessary so that the vertical positions in current_state and desired_state are set correctly for any final vertical positioning on the next device page after the :NEWPAGE block has been done.

%sleep() Implementation

This function is supposed to cause wgml 4.0 to "sleep" for a given number of seconds. In fact, it hangs either gendev 4.1 (if the parameter is literal) or wgml 4.0 (if the parameter is a device function return value). This makes it difficult to say anything about its actual behavior. As might be suspected, no known device uses it.

However, the problem in wgml 4.0 at least may be related to bad coding on the part of gendev 4.1, as noted here. It is possible that, when our gendev produces a properly-formatted coding for this function, it may be possible to determine how wgml 4.0 treats it.

Implementation Notes

This device function was originally implemented to overcome gendev 4.1's mistake and extract the parameter. Unfortunately, it turned out that, if that parameter itself has a parameter, the offset to the parameter's parameter will be wrong. Since it is not possible to correct for this without adding a great deal of code to the existing functions, this device function now uses the offset provided. This will cause "FF" to be picked up as a byte code, and that will trigger an error message, since it is out-of-bounds. This is certainly better than the looping seen in wgml 4.0!

The C Library function sleep() was used to implement its functionality. Before the implementation was changed to work similarly to all the others (and so to fail because of gendev 4.1's mistake), it was tested and does work as intended.

Clone this wiki locally