Device Function Notes
From Open Watcom
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:
- 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.
- 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
It should first be recalled that certain functions are restricted by gendev 4.1 and so the discussion of their treatment by wgml 4.0 is also restricted.
The device functions %textpass(), %ulineon(), and %ulineoff() are restricted by gendev 4.1 to the various :LINEPROC blocks as discussed here. 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:
- 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.
- 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:
- Type II device functions cast their return value to a void *; Type I device functions return NULL.
- Functions expecting a numeric parameter cast the void * to an int32_t.
- Functions expecting a character parameter cast the void * to a char *.
This would explain the observed behavior:
- Functions expecting a numeric parameter would always have a numeric value. The result would be rather large, especially if an int32_t is used.
- 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 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 which either have no parameters or in which the parameters are encoded using parameter blocks.
This device function is treated uniformly and does absolutely nothing in any context observed (so far):
%cancel()
The symbol functions are treated uniformly: this function:
%setsymbol()
set 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.
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 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() %lower(%wgml_header()) %time() %wgml_header()
Notes:
- %date(), %time(), and %wgml_header() do not depend on loading the binary device library and should always return a non-empty value.
- Since %wgml_header() should always return a non-empty value, so should %lower(%wgml_header()).
- %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 appears that these functions are, in effect, 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 appear to be ignored in the INIT :PAUSE block.
These device functions resist categorization: although they return the value "0" in the INIT :PAUSE block, they also return the value "0" in various other blocks and, in all cases where "0" is returned, it is arguably correct. On the other hand, since the above functions are ignored in the INIT :PAUSE block, it seems likely that these are as well:
%add() %divide() %font_number() %pages() %remainder() %subtract() %tab_width() %x_address() %x_size() %y_address() %y_size()
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()
Functions Handled Insanely
These are functions whose behavior in some blocks is quite strange. One element of that strangeness requires explanation: how recursive invocations were identified.
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.
%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.
%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.
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 functions in a meaningful manner. Any section lacking an "Implementation Notes" subsection contains preliminary remarks, and will be developed more fully as work proceeds.
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.
Output Records
The WGML 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 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
Since Open Watcom exists, at least in experimental form, on Linux, instead of referring to a "CRLF pair", this section refers to "the appropriate newline character sequence", where what is "appropriate" depends on the target for which our wgml is compiled. The expected technique is to use those C Library functions which either produce the appropriate newline character sequence themselves or translate "'\n'" to the appropriate newline character sequence.
The implementation of device function %recordbreak() for the :DEVICE block should be to send the appropriate newline character sequence to the screen. This should be relatively straightforward and, provided each version is used only on the appropriate target, should work without further effort.
The implementation of device function %recordbreak() for the :DRIVER block and that of the output buffer are clearly linked:
- When the number of characters in the output buffer reaches the output record length, send the contents of the buffer to the device followed by the appropriate newline character sequence pair (for record types "Text" and "Variable") or padding (for record type "Fixed").
- When %recordbreak() is interpreted, send the current contents of the buffer to the device followed by the appropriate newline character sequence pair (for record types "Text" and "Variable") or padding (for record type "Fixed").
Note that it should be possible to do this in such a way that the appropriate newline character sequence is emitted (for :DRIVER blocks) in one location only. This will make it easier, in the future, to change this behavior if necessary.
By emitting the appropriate newline character sequence in two different locations, one for :DEVICE and one for :DRIVER, it will be possible to adjust the behavior for each of them separately. Since the requirements may well differ, this should be useful.
Determening the difference between "(t:80)" and "(t:c:80)" and implementing it can be postponed until such time as our wgml has been released generally and the need arises.
Text Output
This section discusses these device functions:
%image() %text() %textpass()
and this related topic:
implicit %textpass() in :FONTSTYLE blocks
all of which relate to text output.
The WGML 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.
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 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 pass of the current segment 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.
Implementation Notes
In the :DEVICE block, only %image() and %text() can occur, and they can be implemented identically to write their output to the screen.
In the :DRIVER block, in all four instances (%image(), %text(), explicit %textpass(), implicit textpass()) the implementation must write the output to the output buffer. In all cases except %image(), output translation must be performed before the text is placed in the buffer.
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() should be implemented to clear the screen on a PC whether ANSI.SYS is loaded or not. This may require a different implementation for each target which wgml is compiled for.
Device function %wait() should be implemented to behave exactly as described above.
Device function %clear3270() should cause gendev to issue a warning message stating that it is no longer recognized but to compile it as if it were %clearPC(). 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.
Other Functions
This section gathers together those device functions that fall into no other category. Each has its own subsection.
%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 perform the font switch "switch-to" sequence for :DEFAULTFONT 0.
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 :DEFAULTFONT 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.
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 font that the :FONTVALUE block is applied to).
This section discusses other things that wgml 4.0 does in conjunction with %enterfont(0) -- and why it is clear that they are not part of executing %enterfont(0).
Implementation Notes
The situation here is almost humorous: we have here a function that is not used in the Open Watcom document build system and so which does not need to be implemented, which is done implicitly in every output file, including those produced by the Open Watcom document build system, so that it's action must be implemented.
Since the action of the function must be implemented, there would seem to be no reason not to implement the function itself. Even if it is not actually needed.
%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:
:ENDVALUE (of a :FONTSWITCH block) :ENDVALUE (of a :LINEPROC block) :ENDWORD :FIRSTWORD :STARTVALUE (of a :FONTSWITCH block) :STARTVALUE (of a :LINEPROC block) :STARTWORD
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).
Since, as shown above, device function %dotab() is in fact used in the :ENDWORD and :ENDVALUE blocks of :LINEPROC blocks found in actual :DRIVER blocks, and since, as shown here, including device function %dotab in those blocks produces no horizontal positioning, two possibilities exist:
- There are situations where it can produce spaces or use the :ABSOLUTEADDRESS block when included in these blocks.
- Device function %dotab() has another function, yet to be discovered.
Of course, both statements may be correct.
Implementation Notes
The implementation should be pretty straightforward, once the conditions under which device function %dotab() actually produces output in the blocks specified here have been determined.
As to the variation in the interpretation of the :ABSOLUTEADDRESS block with respect to the interpretation of the :FONTPAUSE block: 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.
wgml Memory Exhaustion
It frequently happened, while testing device function %dotab(), that wgml 4.0 would emit this error message:
SY--001: Memory exhausted!!!!!!!!!!!
while processing the device library.
Tempting though it might be to explore this, it seems to me that, since this presumably involves excessively-large CodeBlocks (or groups of CodeBlocks), it might make more sense to take this approach:
- Since gendev 4.0 uses a 16-bit integer for the size of the compiled code, having our gendev object when the code for a given function block or group of function blocks becomes larger than a uint16_t can hold would make more sense, thus exposing the problem at its source.
- This should allow our wgml to allocate whatever amount of space is needed: since it is compiled only for 32-bit targets, and there are only a finite number of CodeBlocks, this should not be a problem.
Fontstyle Functions
This section discusses the effects of these device functions:
%textpass() %ulineoff() %ulineon()
As noted here, these functions can only appear in certain places.
The effect of %textpass() is to insert the current text into the output buffer.
The effect of %ulineon() and %ulineoff() is reminiscent of the behavior documented in theWGML Reference for the keyword font styles "underline/uline" and "underscore/uscore".
Prelimary tests showed far more underline characters being produced than were needed. Other functions may be needed to control this behavior.
%cancel()
%cancel() is documented to take, as its parameter, "bold", "uline", or "the name of a font switch method"; both cases are found in the available :DRIVER blocks. Since it appears in :NEWLINE blocks, it is likely that the device functionality indicated by the parameter is only switched on if it was, in fact, switched off within the same function block. Unless, of course, the one device using %cancel() (D630DRV.PCD) always uses "bold" and "proportional" printing. More detailed testing will be needed.
%endif()
It is not necessary to explicitly use %endif() in one situation: if the %endif() would be the last function in the block, then it may be omitted (in effect, the end tag of the block containing the function block acts as an %endif() function in this situation).
%flushpage()
The function %flushpage() did nothing in the simple tests used so far. It may be that headers and/or footers must be defined for its action to become visible.
Symbol Table Functions
This section discusses these functions:
%getnumsymbol() %getstrsymbol() %setsymbol()
It currently consists of a series of notes in no particular order.
The only documentation of these functions is in the README file produceable from the WGML 3.33 Update is:
%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 would be.
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 testing 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.

