Index

The ECMA-48 standard can be found here: https://www.ecma-international.org/publications/standards/Ecma-048.htm

The CL-ECMA-48 library was created to serve my needs when writing an interactive program that creates a more complex terminal interface. In order to clean the source of the aforementioned program a tad, I decided to split the functionality into a library. While I used a small fraction of these control functions, the library implements all 162 that the ECMA-48 standard specifies, as this was the most sensible option. The library also optimizes the control functions emitted to the most compact representation, in contrast to the preceding handwritten functions which were much less discerning and these are also designed to be efficient themselves and avoid any allocations and other costly operations. Due to all of this, the library is rather niggardly and there's no reason any of it should consume any more space once loaded nor should it perform poorly.

This library is licensed under the GNU Affero General Public License version three.

The ECMA-48 standard specifies control functions belonging to the C0, C1, Fs, and control sequence sets. The C0 control functions are simply a single character; the C1 and Fs control functions are the escape control function followed by a single character; and the control sequences are the C1 control function named CONTROL SEQUENCE INTRODUCER, any parameters, and then a sequence of one or more characters. While the standard further distinguishes control sequences by parameter purpose, this library doesn't. There are required, optional, and variadic parameters. In designing the interface, required parameters are those with no defualt value; optional parameters have a default value; and variadic parameters may or may not have a default value. Default values may be passed as NIL, whereas required parameters must be an UNSIGNED-BYTE or CHARACTER. Every function accepts a STREAM argument as the last argument, which defaults to *STANDARD-OUTPUT*. Variadic parameters are achieved by passing in a list of the parameters and this is not checked, but all elements should be an UNSIGNED-BYTE, CHARACTER, and NIL only if a default value is specified; it is also permitted to pass an UNSIGNED-BYTE or CHARACTER directly, if only a single parameter is to be used. If a parameter with a default value is given its default value, it won't be emitted by the function, as an optimization. It is purely to aid extended parameter strings that CHARACTER values are permitted for the parameters.

When defining a control function, use DEFINE-CONTROL-FUNCTION, which accepts a symbol or list of two symbols (the acronym and name), a type designation (one of :C0, :C1, :FS, or a modified lambda-list), the identity (a character, character code, or sequence of characters or character codes), and an optional documentation string. The modified lambda-list is identical to a normal lambda-list, except for that &REST may have a default argument using the same syntax as &OPTIONAL and that these two lambda-list keywords are the only two allowed and are not allowed to be used together. The DEFINE-CONTROL-FUNCTION delegates to more specific macros in its implementation and will also assign :EXPANSION and :ACRONYM keys to the plist of the symbol or symbols used to name the function.

The programmer is expected to reference the ECMA-48 standard when using this library. This library will properly function so long as the implementation provides characters for character codes zero to one hundred and twenty seven, inclusive. If the implementation uses ASCII, :ASCII is placed into *FEATURES* and the code is optimized for this case, using CL:PRINC, and provides an internal PRINC otherwise. In the common case of a program that is standard, on an implementation that makes use of a full seven bit character set, there should be no additional issues.

All of the following examples can be thought of as being expanded in the context of the COMMON-LISP package, sans a few examples that will be explained. In particular, forms containing character values are replaced by an equivalent #. expression that will produce identical behavior if loaded and all shown instances of NULL are referred to as COMMON-LISP:NULL, due to the NULL control function necessitating shadowing the symbol.

The generated functions will now be exemplified with the DEFUN forms generated. To begin, the C0, C1, and Fs functions are all rather similar, sans the particular values emitted:

(DEFINE-CONTROL-FUNCTION (CR CARRIAGE-RETURN) :C0 #X0D
  "Carriage return moves to the home or limit position of a line.")

(DEFUN CARRIAGE-RETURN (&OPTIONAL (STREAM *STANDARD-OUTPUT*) &AUX (*STANDARD-OUTPUT* STREAM))
  "Carriage return moves to the home or limit position of a line."
  (DECLARE (IGNORABLE STREAM))
  (WRITE-CHAR #.(CODE-CHAR #X0D))
  (VALUES))

(DEFINE-CONTROL-FUNCTION (CSI CONTROL-SEQUENCE-INTRODUCER) :C1 #X5B
  "Introduce a control sequence.")

(DEFUN CONTROL-SEQUENCE-INTRODUCER
    (&OPTIONAL (STREAM *STANDARD-OUTPUT*) &AUX (*STANDARD-OUTPUT* STREAM))
  "Introduce a control sequence."
  (DECLARE (IGNORABLE STREAM))
  (WRITE-STRING #.(FORMAT NIL "~C~C" (CODE-CHAR #X1B) (CODE-CHAR #X5B)))
  (VALUES))

(DEFINE-CONTROL-FUNCTION (DMI DISABLE-MANUAL-INPUT) :FS #X60
  "Disable the manual input facilities of a device.")

(DEFUN DISABLE-MANUAL-INPUT (&OPTIONAL (STREAM *STANDARD-OUTPUT*) &AUX (*STANDARD-OUTPUT* STREAM))
  "Disable the manual input facilities of a device."
  (DECLARE (IGNORABLE STREAM))
  (WRITE-STRING #.(FORMAT NIL "~C~C" (CODE-CHAR #X1B) (CODE-CHAR #X60)))
  (VALUES))

The majority of the machinery of this library concerns itself with the control sequences. To begin, control sequences with only required parameters produce rather similar and straightforward code, regardless of the number of parameters:

(DEFINE-CONTROL-FUNCTION (SLH SET-LINE-HOME) (N) (#X20 #X55)
  "Establish position n of the active line as the line home.")

(DEFUN SET-LINE-HOME (N &OPTIONAL (STREAM *STANDARD-OUTPUT*)
                      &AUX (*STANDARD-OUTPUT* STREAM) (*PRINT-BASE* 10) *PRINT-RADIX*)
  "Establish position n of the active line as the line home."
  (DECLARE (IGNORABLE STREAM) (TYPE (OR UNSIGNED-BYTE CHARACTER) N))
  (CONTROL-SEQUENCE-INTRODUCER)
  (PRINC N)
  (WRITE-STRING #.(FORMAT NIL "~C~C" (CODE-CHAR #X20) (CODE-CHAR #X55)))
  (VALUES))

(DEFINE-CONTROL-FUNCTION (SPI SPACING-INCREMENT) (LINE CHARACTER) (#X20 #X47)
  "Establish line and character spacing in terms of SSU.")

(DEFUN SPACING-INCREMENT (LINE CHARACTER &OPTIONAL (STREAM *STANDARD-OUTPUT*)
                          &AUX (*STANDARD-OUTPUT* STREAM) (*PRINT-BASE* 10) *PRINT-RADIX*)
  "Establish line and character spacing in terms of SSU."
  (DECLARE (IGNORABLE STREAM)
           (TYPE (OR UNSIGNED-BYTE CHARACTER) LINE CHARACTER))
  (CONTROL-SEQUENCE-INTRODUCER)
  (PRINC LINE)
  (WRITE-CHAR #.(CODE-CHAR #X3B))
  (PRINC CHARACTER)
  (WRITE-STRING #.(FORMAT NIL "~C~C" (CODE-CHAR #X20) (CODE-CHAR #X47)))
  (VALUES))

Control sequences containing optional parameters required the most interesting and complex optimization mechanisms and, in an effort to produce code as an intelligent programmer would, further optimization in the case of only one or two optional parameters, which encompasses all standard control sequences, was performed:

(DEFINE-CONTROL-FUNCTION (CUU CURSOR-UP) (&OPTIONAL (COUNT 1)) #X41
  "Move the cursor up count times.")

(DEFUN CURSOR-UP (&OPTIONAL (COUNT 1) (STREAM *STANDARD-OUTPUT*)
                  &AUX (*STANDARD-OUTPUT* STREAM) (*PRINT-BASE* 10) *PRINT-RADIX*)
  "Move the cursor up count times."
  (DECLARE (IGNORABLE STREAM)
           (TYPE (OR NULL UNSIGNED-BYTE CHARACTER) COUNT))
  (CONTROL-SEQUENCE-INTRODUCER)
  (OR (NULL COUNT) (EQL COUNT 1) (PRINC COUNT))
  (WRITE-CHAR #.(CODE-CHAR #X41))
  (VALUES))

(DEFINE-CONTROL-FUNCTION (CUP CURSOR-POSITION) (&OPTIONAL (LINE 1) (CHARACTER 1)) #X48
  "Reposition the cursor at line and character.")

(DEFUN CURSOR-POSITION (&OPTIONAL (LINE 1) (CHARACTER 1) (STREAM *STANDARD-OUTPUT*)
                        &AUX (*STANDARD-OUTPUT* STREAM) (*PRINT-BASE* 10) *PRINT-RADIX*)
  "Reposition the cursor at line and character."
  (DECLARE (IGNORABLE STREAM)
           (TYPE (OR NULL UNSIGNED-BYTE CHARACTER) LINE CHARACTER))
  (CONTROL-SEQUENCE-INTRODUCER)
  (OR (NULL LINE) (EQL LINE 1) (PRINC LINE))
  (OR (NULL CHARACTER)
      (EQL CHARACTER 1)
      (PROGN (WRITE-CHAR #.(CODE-CHAR #X3B)) (PRINC CHARACTER)))
  (WRITE-CHAR #.(CODE-CHAR #X48))
  (VALUES))

There is a single control sequence, TABULATION CENTERED ON CHARACTER, which is responsible for the library permitting a combination of required and optional or variadic parameters and care was taken to generate code as an intelligent programmer would, even in this case:

(DEFINE-CONTROL-FUNCTION (TCC TABULATION-CENTERED-ON-CHARACTER)
 (LINE &OPTIONAL (CHARACTER 32)) (#X20 #X63)
 #|Documentation string omitted here because of length.|#)

(DEFUN TABULATION-CENTERED-ON-CHARACTER
    (LINE &OPTIONAL (CHARACTER 32) (STREAM *STANDARD-OUTPUT*)
     &AUX (*STANDARD-OUTPUT* STREAM) (*PRINT-BASE* 10) *PRINT-RADIX*)
  #|Documentation string omitted here because of length.|#
  (DECLARE (IGNORABLE STREAM)
           (TYPE (OR UNSIGNED-BYTE CHARACTER) LINE)
           (TYPE (OR NULL UNSIGNED-BYTE CHARACTER) CHARACTER))
  (CONTROL-SEQUENCE-INTRODUCER)
  (PRINC LINE)
  (OR (NULL CHARACTER) (EQL CHARACTER 32)
      (PROGN (WRITE-CHAR #.(CODE-CHAR #X3B)) (PRINC CHARACTER)))
  (WRITE-STRING #.(FORMAT NIL "~C~C" (CODE-CHAR #X20) (CODE-CHAR #X63)))
  (VALUES))

Further care was taken to generate efficient code in the case that more than two optional parameters were used, which is not the case for any standard control function; similar code is generated in the case that required and optional parameters are used when there's more than a single optional parameter, but this is not shown in this document:

(DEFINE-CONTROL-FUNCTION EXAMPLE (&OPTIONAL (W 1) X (Y 3) Z) #X54)

(DEFUN EXAMPLE (&OPTIONAL (W 1) X (Y 3) Z (STREAM *STANDARD-OUTPUT*)
                &AUX (*STANDARD-OUTPUT* STREAM) (*PRINT-BASE* 10) *PRINT-RADIX*)
  (DECLARE (IGNORABLE STREAM)
           (TYPE (OR NULL UNSIGNED-BYTE CHARACTER) W X Y Z))
  (CONTROL-SEQUENCE-INTRODUCER)
  (OR (NULL W) (EQL W 1) (PRINC W))
  (LET ((#1=#:X680 (OR (NULL X)))
        (#2=#:Y681 (OR (NULL Y) (EQL Y 3)))
        (#3=#:Z682 (OR (NULL Z))))
    (DECLARE (TYPE BOOLEAN #1# #2# #3#)
             (DYNAMIC-EXTENT #1# #2# #3#))
    (OR (AND #1# #2# #3#) (WRITE-CHAR #.(CODE-CHAR #X3B)))
    (OR #1# (PRINC X))
    (OR (AND #2# #3#) (WRITE-CHAR #.(CODE-CHAR #X3B)))
    (OR #2# (PRINC Y))
    (OR #3# (PROGN (WRITE-CHAR #.(CODE-CHAR #X3B)) (PRINC Z))))
  (WRITE-CHAR #.(CODE-CHAR #X54))
  (VALUES))

There is a single control function without differing acronym and name, QUAD, which is responsible for permitting a symbol to be used in place of the acronym and name list; this control sequence also shows the code generated in the case of a variadic parameter:

(DEFINE-CONTROL-FUNCTION QUAD (&REST (N 0)) (#X20 #X48)
  "Position string with layout as specified.  See ECMA-48.")

(DEFUN QUAD (&OPTIONAL N (STREAM *STANDARD-OUTPUT*)
             &AUX (*STANDARD-OUTPUT* STREAM) (*PRINT-BASE* 10) *PRINT-RADIX*)
  "Position string with layout as specified.  See ECMA-48."
  (DECLARE (IGNORABLE STREAM)
           (TYPE (OR LIST UNSIGNED-BYTE CHARACTER) N))
  (CONTROL-SEQUENCE-INTRODUCER)
  (IF (LISTP N)
      (MAPL (LAMBDA (LIST &AUX (ELT (CAR LIST)))
              (OR (NULL ELT) (EQL ELT 0) (PRINC ELT))
              (IF (CDR LIST) (WRITE-CHAR #.(CODE-CHAR #X3B))))
            N)
      (OR (NULL N) (EQL N 0) (PRINC N)))
  (WRITE-STRING #.(FORMAT NIL "~C~C" (CODE-CHAR #X20) (CODE-CHAR #X48)))
  (VALUES))

Correcting any flaws found are the only expected changes to be made.

Here is the source, the ASDF system definition, and the documentation.