Device Function Notes
From Open Watcom
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.
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 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 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.
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
Device functions %binary() and %binary1() should be implemented to insert the byte indicated into the output stream.
Device functions %binary2() and %binary4() should not be implemented: a message should appear stating that they are not supported. 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. This 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.
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
Only %ifeqs() and %endif() are needed for the Open Watcom documentation build, since those are the only functions used. Indeed, none of the others is used anywhere.
The %ifeqs() function falls logically into two parts:
- The part that determines if the parameters are or or not equal.
- The part that either interprets or skips the functions controlled by the %ifeqs() function.
Since the other %if functions clearly have the same structure and differ only in the first part, implementing them all should be nearly as simple as implementing just one.
If the numeric versions are implemented, they should be implemented to work correctly. Since they are nowhere used, this should not cause any problems with existing devices.
Document Layout
This section discusses these device functions:
%font_number() %line_height() %line_space() %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.
The values returned by device functions %line_height() and %line_space() appear to be constant throughout the document.
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 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:
- when the :ABSOLUTEADDRESS block is defined, with the first block resulting from step 1.1 of the sequence given here.
- when the :ABSOLUTEADDRESS block is not defined, this with the :NEWLINE block or blocks used to move the print head to that line.
- 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.
- "0" 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 step 1.1 of the sequence given here: 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 pass of a text line:
- If the :ABSOLUTEADDRESS block is not present, "0" in the :NEWLINE block(s).
- The initial horizontal positioning throughout step 1.1 of the sequence given 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 TextChars instance is processed in:
- the :LINEPROC block :ENDWORD block;
- the :LINEPROC block :STARTWORD block for the next TextChars 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 pass follows immediately.
- For a second or subsequent pass:
- If the :ABSOLUTEADDRESS block is not defined, the position of the last character printed (in what is now the preceding 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 TextChars instances are processed, the behavior is essentially the same as above. Of course, some TextChars instances may be tabbed over because they do not have a :LINEPROC for the current pass.
Device functions %thickness(), %x_size(), and %y_size() are associated with "special symbols" in the WGML 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 can all be implemented by integer variables, which the device functions read. The value of those variables will, of course, have to be set at the appropriate time so that the correct value is returned.
Font Values
This section discusses these device functions:
%default_width() %font_height() %font_outname1() %font_outname2() %font_resident() %font_space()
These device functions simply return the values of various attributes in the :FONT and :DEFAULTFONT blocks. The value returned, of course, depends on the value of %font_number() (that is, the values of the designated :DEFAULTFONT are returned).
Although the :FONT block has an attribute named line_height, the value returned by device function %line_height() is neither the same as nor alterable by changing the value of this attribute.
Implementation Notes
Since these functions are always bound, as it were, to the current %font_number, they can be implemented to use that value to return the corresponding values from the corresponding structs.
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 should be straightforward. Device functions %divide() and %remainder() should check their second parameter and report an error if it is "0" and some indication of where the problem occurred (as opposed to the current behavior, which is to report an "Abnormal program termination: Divide overflow" message with only the "CS:EIP" to suggest where it ocurred).
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.
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 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 can each simply return 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 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 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 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 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 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" is known. It is entirely possible that these symbols are "system symbols" only in that they are defined by wgml 4.0 itself and not by the user. At least two symbols defined by wgml 4.0, AMP and GML, however, do not start with either "$" or "SYS", so even this interpretation may be incorrect.
Implementation Notes
Insofar as the device library is concerned, the device functions %getnumsymbol(), %getstrsymbol(), and %setsymbol() should be implemented to use the global symbol table which our wgml will, presumably, create and maintain in some manner. This section contains further notes on this topic.
Since the device library is able to create and set the values of symbols that can be read by the document specification, and vice versa, it is clear that the various function blocks must be evaluated so that the global symbol table has the same content at the same point on each pass. I use "evaluate" instead of "interpret" because, if more than one pass is used, output will only be produced at specific times:
- Before or as part of the first pass for the START and DOCUMENT :INIT and :PAUSE blocks and also for the blocks involved in the implicit %enterfont().
- During the final pass for all blocks other than the START and DOCUMENT :INIT and :PAUSE blocks.
I have been using the term "interpret" in those instances where output actually occurs. This section discusses the use of "evaluate" in the WGML Reference.
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 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 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 should be maintained in our wgml.
This can be done by treating the symbols as all other global symbols are treated, whatever that turns out to be. Unless other symbols not modifiable by %setsymbol() appear, however, it would probably be better to let %setsymbol() modify the symbol "date", as SETsymbol and :SET are allowed to do. There may be some situations where this makes sense. On the other hand, if additional symbols are discovered that %setsymbol() cannot modify, then "time" as well as date should be treated as part of that group.
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. From this it is clear that the variables storing the date and time information returned by the device functions are initialized after the START :PAUSE block is interpreted and before the START :INIT block is interpreted.
Since the symbols "date" and "time" have their values even when the START :PAUSE block is interpreted, it is clear that wgml 4.0 has obtained the date and time from the system and intialized the symbols before the START :PAUSE block is interpreted.
Since the value of the symbol "date" can change between the START :PAUSE and START :INIT blocks and the DOCUMENT :PAUSE block if the layout, after being processed, requires a different format from the default, it seems most likely that the value obtained from the system is retained so that the date symbol can be reset if necessary. The alternative is to write code to convert from
January 15, 2008
to
1/15/08
if the layout specifies the format "$msn/$dsn/$ys". Direct conversion from the original, system-provided form would seem more practical.
The device functions date(), time(), and wgml_header(), then, should be implemented to return the value of a global char * variable, not as a symbol. All the changes documented above, and any others discovered in the future, should be done by changing the content of this variable.
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 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.
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
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.
Device functions %decimal(), %hex(), and %lower() can be implemented in the simplest manner possible and used in all blocks.
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.
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 theWGML 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 TextChars 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 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 should be implemented to toggle a flag, as stated above. The code which implements that font style application sequence is 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.
Other Functions
This section gathers together those device functions that fall into no other category. Each has its own subsection.
%cancel() Implementation
The WGML 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:
- Determine the current font number.
- 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.
- 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
This function is not needed at all for the Open Watcom build system. It might be needed if our wgml is released more widely; that depends on how many Diablo 630's are still in use and if any newer devices in use with wgml need it.
The implementation should not be that difficult, consisting primarily of a string comparison. Indeed, if it is implemented, it should probably be extended to interpret the :FONTSWITCH block :STARTVALUE block if the parameter matches the :FONTSWITCH name, thus restoring its intended functionality.
%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 should be pretty straightforward, now that the conditions under which device function %dotab() produces output in the blocks specified appears to have been determined here.
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.
%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, invariably performs these three steps:
- It performs the font switch "switch-to" sequence for :DEFAULTFONT 0.
- It interprets the pass 1 :LINEPROC block :STARTVALUE block for the font style associated with :DEFAULTFONT 0.
- It interprets the pass 1 :LINEPROC block :FIRSTWORD block for the font style associated with :DEFAULTFONT 0, if that block exists; if not, it interprets the pass 1 :LINEPROC block :STARTWORD block for the font style associated with :DEFAULTFONT 0.
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(): no text exists.
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. 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 font that the :FONTVALUE block is applied to -- but always using the font style associated with :DEFAULTFONT 0 to determine which blocks to interpret).
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 though it is not actually needed.
%flushpage() Implementation
The WGML 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.
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.
- 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 does not appear to affect page numbering; so far as the tests showed, it merely increases the number of device pages which each document page will occupy. It should, then, have no effect on document layout.
Implementation Notes
This device function need not be implemented initially because it is not used by the Open Watcom document build system. It will be needed if our gendev/wgml are released more broadly and if device TERM is included in our distribution (perhaps for use with a re-written tutorial).
Something very like its functionality, however, will be needed initially, since WHELPDRV does not define an :ABSOLUTEADDRESS block and so uses the :NEWLINE block to skip multiple lines. So, if device function %flushpage() can be implemented as a single function call to a function that is required anyway, it might as well be implemented initially.
%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.
Implementation Notes
Since it is never used and quite likely never was used (had it been used, it would surely have been made to work), this function can be safely deprecated. A message stating that it is not to be used is surely better than a hung program!
It can, of course, be implemented if any need is ever discovered for it.