1 Introduction
2 Ground Rules

Building a File System
3 File Systems
4 File Content Data Structure
5 Allocation Cluster Manager
6 Exceptions and Emancipation
7 Base Classes, Testing, and More
8 File Meta Data
9 Native File Class
10 Our File System
11 Allocation Table
12 File System Support Code
13 Initializing the File System
14 Contiguous Files
15 Rebuilding the File System
16 Native File System Support Methods
17 Lookups, Wildcards, and Unicode, Oh My
18 Finishing the File System Class

The Init Program
19 Hardware Abstraction and UOS Architecture
20 Init Command Mode
21 Using Our File System
22 Hardware and Device Lists
23 Fun with Stores: Partitions
24 Fun with Stores: RAID
25 Fun with Stores: RAM Disks
26 Init wrap-up

The Executive
27 Overview of The Executive
28 Starting the Kernel
29 The Kernel
30 Making a Store Bootable
31 The MMC
32 The HMC
33 Loading the components
34 Using the File Processor
35 Symbols and the SSC
36 The File Processor and Device Management
37 The File Processor and File System Management
38 Finishing Executive Startup

Users and Security
39 Introduction to Users and Security
40 More Fun With Stores: File Heaps
41 File Heaps, part 2
42 SysUAF
43 TUser
44 SysUAF API

Terminal I/O
45 Shells and UCL
46 UOS API, the Application Side
47 UOS API, the Executive Side
48 I/O Devices
49 Streams
50 Terminal Output Filters
51 The TTerminal Class
52 Handles
53 Putting it All Together
54 Getting Terminal Input
55 QIO
56 Cooking Terminal Input
57 Putting it all together, part 2
58 Quotas and I/O

UCL
59 UCL Basics
60 Symbol Substitution
61 UCL Command execution
62 UCL Command execution, part 2
63 UCL Command Abbreviation
64 ASTs
65 UCL Expressions, Part 1
66 UCL Expressions, Part 2: Support code
67 UCL Expressions, part 3: Parsing
68 SYS_GETJPIW and SYS_TRNLNM
69 UCL Expressions, part 4: Evaluation
70 PROCESS_SCAN

Glossary/Index


Download sources
Download binaries

Cooking Terminal Input

Just as a TOutput_Filter instance is used to cook data output to the terminal, likewise a TInput_Filter instance is used to cook data being input from the terminal. Because terminal input is interactive (unlike input from a store), the input filter does a lot on behalf of the user in order to ease the use of the software. On some ancient computers, the application program was responsible for handling all input considerations, including echoing the characters back to the terminal, handling special conditions, and allowing editing of the input (if nothing else, at least processing the delete key). Modern operating systems make it easy on the application programs by handling each keystroke and then presenting an entire line of information to the application. UOS also provides a means of allowing a program to do all of the input processing, if the program so desires, but the vast majority of programs can make use of the input cooking that UOS does. Here is the Input_Filter method for the TTerminal class.

function TTerminal.Input_Filter : TInput_Filter ;

begin
    if( _Input_Filter = nil ) then
    begin
        _Input_Filter := TDefault_Input_Filter.Create ;
        _Input_Filter.Term := self ;
    end ;
    Result := _Input_Filter ;
end ;
When someone requests the Input filter from the terminal, it constructs an instance of the default if necessary. Then it returns the instance.
Here is the abstract TInput_Filter class definition.
TInput_Filter = class
                    public // Instance data...
                         Flags : cardinal ;
                         Term : TTerminal ;
                         Name : string ;

                    public // API...
                        procedure Clear_Input ; virtual ; abstract ;
                        function Peek_Char : string ; virtual ; abstract ;
                        function Key_Available : boolean ;
                            virtual ; abstract ;
                        function Buffer_Length : cardinal ;
                            virtual ; abstract ;
                        procedure Get( Dst : PChar ; Len : cardinal ) ;
                            virtual ; abstract ;
                        function Force_Input( const S : string ) : TUnified_Exception ;
                            virtual ; abstract ;
                        procedure New_Character( Key : cardinal ) ;
                            virtual ; abstract ;
                        function Read( _Size : cardinal ) : string ;
                            virtual ; abstract ;
                        function Has_Space( _Size : cardinal ) : boolean ;
                            virtual ; abstract ;
                end ;

Here is the definition of the descendant class.

type TDefault_Input_Filter = class( TInput_Filter )
                                 public // Constructor and destructors...
                                     constructor Create ;
                                     destructor Destroy ; override ;

                                 protected // Buffer...
                                     _Buffer : TByte_Queue ; // Default buffer
                                       
                                 public // API...
                                     procedure Clear_Input ; override ;
                                     procedure New_Character( Key : cardinal ) ;
                                         override ;
                                     function Peek_Char : string ; override ;
                                     function Read( _Size : cardinal ) : string ;
                                         override ;
                                     procedure Get( Dst : PChar ; Len : cardinal ) ;
                                         override ;
                                     function Buffer_Length : cardinal ;
                                         override ;
                                     function Key_Available : boolean ;
                                         override ;
                                     function Force_Input( const S : string ) : TUnified_Exception ;
                                         override ;
                                     function Has_Space( _Size : cardinal ) : boolean ;
                                         override ;
                             end ;
Like the output filter, we use a byte queue to buffer the data. Let's look at the utility routines.
constructor TDefault_Input_Filter.Create ;

begin
    inherited Create ;

    _Buffer := TByte_Ring_Queue.Create ;
end ;


destructor TDefault_Input_Filter.Destroy ;

begin
    _Buffer.Free ;

    inherited Destroy ;
end ;


procedure TDefault_Input_Filter.Clear_Input ;

begin
    _Buffer.Clear ;
end ;


function TDefault_Input_Filter.Buffer_Length : cardinal ;

begin
    Result := _Buffer.Length ;
end ;


function TDefault_Input_Filter.Key_Available : boolean ;

begin
    Result := _Buffer.Length > 0 ;
end ;


function TDefault_Input_Filter.Has_Space( _Size : cardinal ) : boolean ;

begin
    Result := _Buffer.Length + _Size < _Buffer.Size ;
end ;
These methods are straightforward so we won't go into any detail here.

Most of the work of the input filter is in the Read method. Its purpose is to process the keystrokes from the terminal. One basic function is to echo any received characters - except for special cases. The terminal can deliver input at any point, even when a program is busy and not awaiting input. But we don't act on any received input until a program is ready for it, otherwise we risk echoing characters in the middle of a chart being printed on the terminal or - even worse - we might echo a password that would have been accepted without echo when the program requested input. The ring buffer will gather all input from the terminal until the program is ready for input, at which time the Read method will be called.

There are several functions involved with the input filter, beyond merely echoing characters to the terminal. One function is line editing. No human is perfect and there is a need for the human operating the terminal keyboard to be able to correct any mistakes he makes. The simplest example of line editing is the ability to delete a character. But VMS and UOS provide additional editing capabilities, which we will discuss in a moment.

The input filter has to deal with different types of terminals. The two main examples are hardcopy (printing) terminals, and video terminals. Video terminals are of many different types. We will discuss some of the more fancy ones in a future article. For now, we will consider video terminals to be "glass teletypes" - which are the least capable kind of video terminal. The cursor positioning capabilities are limited to moving to the beginning of a line or moving left one character. All other movement has to do with moving right with each output character and moving down one line with a linefeed character (which are the same as what is supported by a hardcopy terminal). Of course, a hardcopy terminal can also backspace one character to the left or return to the beginning of the line, but whereas a new character overwrites the previous glyph on a video terminal, a hardcopy terminal overprints the glyphs - which can lead to a lot of garbage being printed on the paper. Consider that when we delete a character on a video terminal, we can backspace, output a space to erase the existing glyph, and then backspace to position the next character where the deleted character was. But this cannot be done on a hardcopy terminal, so we have to take another tack. For hardcopy, we will output a backslash (\) and the character being deleted. This gives the user visual feedback as to the deleted characters. If multiple characters are deleted (the delete key pressed multiple consecutive times), then they are all shown. When the deletion ends (which happens when a non-delete character is typed), a closing backslash is output to indicate that we are no longer in delete "mode".

Standard UOS line editing includes the following features:
CharacterDescription
Control AToggle insert mode
Control EMove to end of input line
Control DMove current position left one character
Control FMove current position right one character
Control HMove to beginning of input line (note: see comments on the TIFF_BSasDEL flag beflow)
Line editing means that there is always an input position in the line. Usually this position is at the end of the input line. However, the position can be moved left or right as desired by the user. When new input occurs, it applies to the current position. There are two possibilities when the position is not at the end of the line: either the character is inserted into the input or the character at the position is overwritten by the new character. By default, each input operation starts with the input filter in insert mode, but the user can toggle to overwrite mode, and back, as desired.

One further complication is that some characters are echoed as multiple characters - especially control characters (ASCII 1 through 30). Except for a few exceptions, control characters are echoed as a circumflex (^) followed by a letter - for instance: ^F. In this case, this means that when we delete a character on a video terminal, we need to erase two characters.

Finally, there is the issue of "flow control". Hard-wired terminals, and some network terminal protocols, have a means of communicating when the terminal's buffer is full. We don't want to keep outputting data if the terminal cannot accept more input - otherwise data will be lost. However, some serial interfaces (for instance, those over dial-up lines) and some devices do not support a distinct flow control protocol. Rather, they use special ASCII control values to relay this information within the input stream itself. The XOFF control code is used to suspend output to the terminal and the XON code is used to resume output to the terminal. The user can also type Control S (XOFF) and Control Q (XON) to manually pause/unpause output from the computer. Some terminals even have special keys that can be used to toggle the output pause between on and off. Either way can be used to allow the user to pause output so he can read it.

As output filters have flags, so do input filters. Here are input filter flags:

// Terminal input filter flags...
const TIFF_Binary = 1 ; // Don't cook input
const TIFF_Noecho = 2 ; // Don't echo
const TIFF_NoControl = 4 ; // Control codes are echoed exactly
const TIFF_Notypeahead = 8 ; // Don't buffer unsolicited input (no input is buffered while this is set)
const TIFF_NoTermXON = 16 ; // UOS treats XON/XOFF sent by terminal as data rather than start/stop output
const TIFF_NoLineEdit = 32 ; // Don't support line editing
const TIFF_BSasDEL = 64 ; // Treat backspace as Delete
const TIFF_NoControlT = 128 ; // Don't provide status for ^T
const TIFF_NoControlC = 256 ; // Don't interrupt for ^C
const TIFF_NoControlY = 512 ; // Don't interrupt for ^Y
Let's review the flags.
  • TIFF_Binary: This means that we will not cook the input at all - it will simply be placed into the buffer with no side-effects.
  • TIFF_Noecho: This prevents the input filter from echoing characters back to the terminal. As mentioned above, this can be important for security reasons.
  • TIFF_NoControl: This turns off cooking of control characters. They are echoed exactly as they are received rather than with the circumflex (^) prefixed letters.
  • TIFF_Notypeahead: This prevents an unsolicited input from being buffered. Except while a program is awaiting the completion of a read operation from the terminal, all input is discarded rather than buffered.
  • TIFF_NoTermXON: The XON/XOFF flow control is handled by the input filter whether or not there is hardware flow control - the hardware flow control is based solely on the terminal's buffer, whereas XON/XOFF is at the user's discretion. However, if this flag is set, the input filter treats XON and XOFF as normal characters.
  • TIFF_NoLineEdit: This flag indicates that all line editing control codes (see above) are treated as normal characters. This does not affect the Delete, Control R, Control U, or Control X characters.
  • TIFF_BSasDEL: Most terminal devices have a delete key in the place that the backspace key occupies on the standard PC keyboard. When pressed, it is supposed to delete the last character entered. In short, it functions the same way that the PC keyboard Backspace key operates. However, some terminal keyboards have a BS (backspace) key as well. To further complicate things, the PC keyboards have a Delete key in addition to the Backspace key. Sounds confusing? Let's consider that in both cases, a backspace key produces an ASCII 8 character and delete produces an ASCII 127 character. It is not the keys that are different - it is the way the keys are interpreted by your typical PC's operating system. In Windows, for instance, backspace deletes the character to the left of the cursor and the Delete key deletes the character to the right. UOS, like VMS, handles the backspace as a special control character and Delete as a delete-the-character-to-the-left. But this would be confusing behavior for most users who were using a PC keyboard. Thus, if UOS is running on a PC, this flag is set and any backspace character is converted to a delete character so that the behavior is what is expected when Backspace is pressed. However, this will disable the line editing feature of Control H that we discussed above. The Delete key will also delete the character to the left, but for a comand-line terminal type input feature, this is not an issue. When we talk about the UOS desktop shell in a future article, the keys will all work like one expects in Windows.
  • TIFF_NoControlT: The Control T character is used to display a status line. If this flag is set, Control T is treated as any other non-special control character. Because this flag can be set by the user, a given user (or the system administrator) can set the flag so that the keyboard - whichever kind it is - will work as expected.
  • TIFF_NoControlC: The Control C character is used to interrupt the currently running program. If this flag is set, Control C is treated as normal input.
  • TIFF_NoControlY: The Control Y character is used to interrupt the currently running program. If this flag is set, Control Y is treated as normal input.

Now we can take a look at the input filter's Read method.

function TDefault_Input_Filter.Read( _Size : cardinal ) : string ;

var Position : integer ;
    Delete_Mode, Insert_Mode : boolean ;
    C, C1, I, Key : Cardinal ;
    S : string ;

begin // TDefault_Input_Filter.Read
    // Setup...
    Term.Device.IO_PID := Term.Kernel.PID ;
    Result := '' ;
    Insert_Mode := True ;
    Delete_Mode := False ;
    if( ( Flags and TIFF_Binary ) = 0 ) then // Cooked input
    begin
        Term.Output_Filter.Flags :=
            Term.Output_Filter.Flags and ( not TOFF_Null ) ; // Reset output
    end ;
We set up the read operation by setting Delete mode to false and insert/overwrite mode to Insert mode (Insert_Mode = true). Position is automatically initialized to 0 by the compiler. This indicates the current position within the input line. Since we have no input yet, the position is 0, which is essentially after the end of the input data at this point. Finally, we reset the output flag in case output was turned off. Control O is used to toggle output on/off. This way the user can interrupt output without interrupting the program. When we reach the next input (which happens when we enter the Read method), we want the output automatically turned back on.

    // Process input...
    while( length( Result ) < _Size ) do // Until max characters are processed
    begin
        if( _Buffer.Length = 0 ) then // Nothing in buffer
        begin
            Term.Kernel.USC.Block( 0, PS_IOW, Term ) ;
            continue ;
        end ;
        Key := _Buffer.Advance( Term.Min_Record_Size ) ; // Get next character
        if( ( Key = _BS ) and ( ( Flags and TIFF_BSasDEL ) <> 0 ) ) then
        begin
            Key := _DEL ;
        end ;
Once we've set up the initial conditions, we loop until we fill have the maximum requested characters (later we will exit under certain cirumstances). If the input buffer is empty, we tell the USC component to block the process in an I/O wait state (IOW). We will discuss blocking/unblocking processes in the future. Assuming that there is input waiting, we get the next character from the buffer via the Advance method. But before we do anything else, we convert a backspace character to a delete character if the TIFF_BSasDEL flag is set.

        if( Delete_Mode ) then
        begin
            if( ( Key <> _DEL ) and ( Key <> _NUL ) ) then // Any other character ends delete mode
            begin
                Echo( '\' ) ;
                Delete_Mode := False ;
            end ;
        end ; // if( Delete_Mode )
Before we process any character, we check to see if we are in delete mode. If so, we have to see if we are continuing the deletion or ending it. We exit delete mode if any character other than Delete or Null is encountered. If that is the case we output the closing backslash and clear the delete mode.

        Echo( Character_Echo( Key ) ) ;

        // Process character
        if( ( Flags and TIFF_Binary ) <> 0 ) then // Uncooked input
        begin
            Add( chr( Key ) ) ;
        end else
Next we call the Echo routine to handle echoing the character. The value we pass to the Echo routine is a string that we get from the Character_Echo function. We will discuss Echo and Character_Echo later in the article.
If the filter is in binary input mode, we don't process (cook) the character and simply add it to the result string by calling the Add method (which we will discuss later in the article). The rest of the method is for cooking the input.

        begin
            // Cooked input processing
            case Key of
                _Control_A, _Control_D, _Control_E, _Control_F, _Control_H :
                    if( ( Flags and TIFF_NoLineEdit ) = 0 ) then // Line editing
                    begin
                        case Key of
                            _Control_A : Insert_Mode := not Insert_Mode ; // Toggle insert/override for line edit
                            _Control_E : // Move to EOL
                                begin
                                    Move_To_EOL ;
                                    Position := length( Result ) ;
                                end ;
                            _Control_D : // Move cursor/position left
                                if( Position > 0 ) then
                                begin
                                    S := Character_Echo( ord( Result[ Position ] ) ) ;
                                    if( ( Term.Terminal_Flags and TF_Video ) <> 0 ) then
                                    begin
                                        for I := 1 to length( S ) do
                                        begin
                                            Echo( BS ) ;
                                        end ;
                                    end else
                                    begin
                                        Echo( S ) ;
                                    end ;
                                    dec( Position ) ;
                                end ;
                            _Control_F : // Move cursor/position right
                                if( Position < length( Result ) ) then
                                begin
                                    inc( Position ) ;
                                    Echo( Character_Echo( ord( Result[ Position ] ) ) ) ;
                                end ;
                            _Control_H : // Move to BOL
                                begin
                                    if( ( Term.Terminal_Flags and TF_Video ) <> 0 ) then
                                    begin
                                        while( Position > 0 ) do
                                        begin
                                            S := Character_Echo( ord( Result[ Position ] ) ) ;
                                            for I := 1 to length( S ) do
                                            begin
                                                Echo( BS ) ;
                                            end ;
                                            dec( Position ) ;
                                        end ;
                                    end else
                                    begin
                                        while( Position > 0 ) do
                                        begin
                                            Echo( Character_Echo( ord( Result[ Position ] ) ) ) ;
                                            dec( Position ) ;
                                        end ;
                                    end ;
                                    Position := 0 ;
                                end ;
                        end ; // case Key
                    end else
                    begin
                        Add( chr( Key ) ) ;
                    end ; // if( ( Flags and TIFF_NoLineEdit ) = 0 )
Cooking the input character involves a big case statement. The first case is for the characters that are used for line editing. If the NoLineEdit flag is set, we simply add the character to the result via the Add routine. Otherwise, we handle each of the characters as appropriate. Control A toggles the Insert_Mode value between true and false - thus, switching back and forth between insert and overwrite mode.

Control E moves to the end of the input line (in case the current position is not at the end already). We simply write out the rest of the input line from the current position to the end of the input. When done, the current position on the terminal will also be at the end of the input line. We then set the position to the end of the input line.

Control D moves the current position one character position to the left. First we get the echoed text for the character from Character_Echo. We have to do this because we don't know how many characters are echoed for this one input character (for instance, a control-character with the circumflex/letter combination). What we do next depends upon whether the terminal is a video or hardcopy terminal. In the case of video, we simply move the cursor left by the number of positions in S (ie the number of characters in the string), by outputting backspace characters. In the case of a hardcopy terminal, we echo the skipped character echo. In either case, we decrement the input position by 1.

Control F moves the current position one character position to the right. If we are already at the end of the input line, we have nothing to do. Otherwise, we output the character at that position. On a video terminal, this will overwrite the same character(s) that were already there, which essentially moves the cursor to the next character position. In either case, we increment the input position by 1.

Control H moves the cursor to the start of the line (assuming that the BSasDEL flag isn't set). On a video terminal, this could easily done by outputting a CR (Carriage Return). However, if the line starts with a prompt this would move the position to the beginning of the prompt instead of the beginning of the input. Therefore, we backup to the start of the input. On hardcopy terminals, we output all the preceeding characters in reverse order to indicate backing up past them. In either case, we set the input position to 0.

On a hardcopy terminal, the use of the line editing characters can result in an output that looks quite garbled. This is a consequence of keeping the user aware of the context of where he is in the line and the unchangeable nature of hardcopy output. Fortunately, if the user wants to see the whole line as it exists in the input buffer, he can use Control R to refresh the line on the terminal (see discussion below).

                _Control_J : 
                    begin
                        Result := Result + chr( Key ) ; // Delimiter always at the end
                        exit ;
                    end ;
                _Control_M :
                    begin
                        Result := Result + chr( Key ) ; // Delimiter always at the end
                        exit ;
                    end ;
On VMS, Control J (Linefeed) is one of the line-editing characters, used to delete a word. We will differ from VMS in this case, and treat it as a line delimiter. For delimiters, we add the delimiter to the result and exit the Read method.
Control M (Enter or Return) is also treated as a delimiter.

                _Control_R : // Redisplay line
                    begin
                        Echo( CRLF ) ;
                        for I := 1 to length( Result ) do
                        begin
                            Echo( Character_Echo( ord( Result[ I ] ) ) ) ;
                        end ;

                        // Reposition cursor...
                        if( ( Term.Terminal_Flags and TF_Video ) <> 0 ) then
                        begin
                            Echo( CR ) ;
                            for I := 1 to Position - 1 do
                            begin
                                Echo( Character_Echo( ord( Result[ I ] ) ) ) ;
                            end ;
                        end ;
                    end ;
As mentioned above, Control R is used to refresh the input line so that a user on a hardcopy terminal can see what the line looks like after it has been altered. First we return to the start of the line. We output a Carriage Return and Linefeed so that we are on the next line and can show the whole input line. The reason is that the current line may have been so compromised by stray output that there is no way to redraw the line properly. Stray output can come from many possible sources: another process broadcasting text to the terminal (this can happen if a system message is sent out to the terminal), or line noise on a dial-up line, or the user switches the terminal to local echo and back, or the user presses control T, and so forth. Thus the entire input buffer needs to be redrawn on a new line to ensure it is properly displayed. Then, for a video terminal, we reposition to the cursor by returning and then outputting the input buffer up to the current position.

                _Control_U :
                    begin
                        if( ( Term.Terminal_Flags and TF_Video ) <> 0 ) then
                        begin
                            Erase_Line ;
                        end else
                        begin
                            Echo( CRLF ) ;
                        end ;
                        Result := copy( Result, Position, length( Result ) ) ;
                        Position := 0 ;
                        if( ( Term.Terminal_Flags and TF_Video ) <> 0 ) then
                        begin
                            for I := 1 to length( Result ) do
                            begin
                                Echo( Character_Echo( ord( Result[ I ] ) ) ) ;
                            end ;
                            for I := 1 to length( Result ) do
                            begin
                                S := Character_Echo( ord( Result[ I ] ) ) ;
                                for I1 := 1 to length( S ) do
                                begin
                                    Echo( BS ) ;
                                end ;
                            end ;
                        end ;
                    end ;
Control U deletes all input to the left of the current position. If the terminal is video, we erase the input line via the Erase_Line local function. In the case of hardcopy, we move to the next line. Next we remove all characters left of the current position from the result buffer, and then reset the current position to 0. Note that if the current position is at the end of the line, the effect is to delete the entire input line, exactly like the Control X character (see below). In the case of the video terminal, we rewrite the line to reflect the only remaining characters.

                _Control_X :
                    begin
                        if( ( Term.Terminal_Flags and TF_Video ) <> 0 ) then
                        begin
                            Erase_Line ;
                        end else
                        begin
                            Echo( CRLF ) ;
                        end ;
                        Position := 0 ;
                        Result := '' ;
                    end ;
Control X deletes the entire input line. For video terminals, we call Erase_Line. Otherwise we move to the next line. Then we reset the position to 0 and clear the input result buffer.

                _Control_Z, _ESC :
                    begin
                        Result := Result + chr( Key ) ;
                        exit ;
                    end ;
Control Z and escape are both treated as delimiters. We add the delimeter to the end of the result and exit. Note that the delimiter is always added to the end, regardless of the current input position in the line. Note also that a delimiter doesn't overwrite any characters even if we are in overwrite mode.

                _DEL :
                     begin
                         if( Position > 0 ) then // Something to delete
                         begin
                             if( ( Term.Terminal_Flags and TF_Video ) <> 0 ) then
                             begin
                                 // Erase deleted character...
                                 S := Character_Echo( ord( Result[ Position ] ) ) ;
                                 C1 := length( S ) ;
                                 for I := 1 to C1 do
                                 begin
                                     Echo( BS + ' ' + BS ) ;
                                 end ;

                                 // Rewrite remainder of line to shift characters left...
                                 C := 0 ;
                                 for I := Position + 1 to length( Result ) do
                                 begin
                                     S := Character_Echo( ord( Result[ I ] ) ) ;
                                     Echo( S ) ;
                                     C := C + length( S ) ;
                                 end ;

                                 // Erase anything left after end of remainder of line...
                                 for I := 1 to C1 do
                                 begin
                                     Echo( ' ' ) ;
                                 end ;

                                 // Reposition...
                                 for I := 1 to C + C1 do
                                 begin
                                     Echo( BS ) ; // Backup to position
                                 end ;

                                 // Pysically delete the character...
                                 delete( Result, Position, 1 ) ;
                                 dec( Position ) ;
                             end else
Deletion is handled quite differently for video and hardcopy terminals. First, we'll address the video terminal processing. We get the string corresponding to the echoed characters for the character being deleted, and then we use the backspace/space/backspace sequence to erase that charater from the screen. We also save the length of the character(s) echoed for that character in variable Cl. Then we loop through the rest of the input characters to the right of the current position (if any), writing them in their new positions. This effectively shifts everything on the right to the left. Why do we erase the deleted character if we are just going to write over it with what is to the right? Because we can't guarantee there is anything to the right - for instance, if the current position is at tht end of the line. Note that we add up the number of characters that are rewritten/shifted in the variable C. Then we erase whatever if left after the end of the line. For instance, let's say that we have the following input line:
12^BXYZ
Then, let's say that we backup to the control B (^B) and delete it. The "XYZ" would be shifted left two positions, which would leave the line looking like this:
12XYZYZ
In this case, we need to write two spaces at the end of the line to erase the errant remaining "YZ" so that the line now looks like this:
12XYZ
We then use the values C plus Cl to backup to the current position. Remember that we are positioned somewhere after the end of the input line, so we have to backup over the number of spaces used to erase the old characters, and the number of characters that exist after the input position. When we are done, the cursor will be at the proper input position. Finally, we physically delete the character from the result buffer and decrement the current position.

Now let's look at the process for hardcopy terminals:

                            begin
                                if( not Delete_Mode ) then
                                begin
                                    Delete_Mode := True ;
                                    Echo( '\' ) ;
                                end ;
                                Echo( Character_Echo( ord( Result[ Position ] ) ) ) ; // Show deleted character
                                delete( Result, Position, 1 ) ;
                                dec( Position ) ;
                                if( length( Result ) = 0 ) then // Last character in buffer deleted
                                begin
                                    Echo( '\' ) ;
                                    Delete_Mode := False ;
                                end ;
                            end ;
                        end ; // if( _Buffer.Length > 0 )
                    end ; // case _DEL
If we aren't currently in delete mode, we enter delete mode and output the backslash that indicates a deletion is starting. In either case, we echo the deleted character. Then we delete the character from the result buffer, and decrement the current position. If we have deleted the last character (nothing else in the buffer) then we exit delete mode and output the terminating backslash. After all, it makes no sense to stay in delete mode after the input buffer is empty.

                else Add( chr( Key ) ) ;
            end ; // case Key
        end ;
    end ; // while( _Size > 0 )
end ; // TDefault_Input_Filter.Read
For all other characters not handled above, we simply add the character to the result buffer.

    procedure Erase_Line ; // Erase input line

    var I, I1 : integer ;
        S : string ;

    begin
        Move_To_EOL ;
        for I := 1 to length( Result ) do
        begin
            S := Character_Echo( ord( Result[ I ] ) ) ;
            for I1 := 1 to length( S ) do
            begin
                Echo( BS + ' ' + BS ) ;
            end ;
        end ;
    end ;
The Erase_Line local function is called to erase everything on the current line, after the current position, for a video terminal. First we use Move_To_EOL to move to the end of the input line and then we loop through the buffer, and use the Backspace/Space/Backspace sequence to delete each character that was output on the line.

    procedure Move_To_EOL ;

    var I : integer ;

    begin
        for I := Position to length( Result ) do
        begin
            Echo( Character_Echo( ord( Result[ I ] ) ) ) ;
        end ;
    end ;
This local function moves the video terminal's cursor to the end of the input line.

    procedure Echo( const S : string ) ;

    begin
        if( S = '' ) then
        begin
            exit ;
        end ;
        if( ( Flags and TIFF_Noecho ) = 0 ) then // Echo is on
        begin
            Term.Output_Filter.Write( S, False ) ;
        end ;
    end ; // TDefault_Input_Filter.Read.Echo
This function writes a character to the terminal. If a null string is passed, we exit. Otherwise, we write the text to the terminal's output filter, if the Noecho flag isn't on (meaning that we are supposed to echo characters).

    procedure Add( const Key : Ansichar ) ;

    var C, I : integer ;
        S, S1, T : string ;

    begin
        C := 0 ;
        if( Insert_Mode or ( Position + 1 > length( Result ) ) ) then
        begin
            insert( Key, Result, Position + 1 ) ;
            if( ( Term.Terminal_Flags and TF_Video ) <> 0 ) then
            begin
                for I := Position + 2 to length( Result ) do
                begin
                    T := Character_Echo( ord( Result[ I ] ) ) ;
                    Echo( T ) ;
                    C := C + length( T ) ;
                end ;
            end ;
        end else
The local Add procedure is used to put a character into the result buffer. If we are in insert mode or at the end of the input buffer, we simply insert the character into the proper location in the string. If the terminal is a video terminal, we then write the rest of the line from the current position, including the new character. This has the effect of inserting the new character and shifting the rest of the line to the right. As we write it out, we add up the number of positions written in variable C.

        begin // Overwrite mode
            S := Character_Echo( ord( Result[ Position + 1 ] ) ) ;
            S1 := Character_Echo( Ord( Key ) ) ;
            Result[ Position + 1 ] := Key ;
            if( ( Term.Terminal_Flags and TF_Video ) <> 0 ) then
            begin
                if( length( S ) <> length( S1 ) ) then // Need to rewrite end of line
                begin
                    for I := Position + 2 to length( Result ) do
                    begin
                        T := Character_Echo( ord( Result[ I ] ) ) ;
                        Echo( T ) ;
                        C := C + length( T ) ;
                    end ;
                    for I := 1 to length( S ) - length( S1 ) do
                    begin
                        Echo( ' ' ) ;
                        inc( C ) ;
                    end ;
                end ; // if( length( S ) <> length( S1 ) )
            end ;
        end ;
In the case of overwrite mode, we change the character at the current position to the new one. First we get the echoed characters for the character being overwritten. Then we update the buffer. If the terminal is in video mode, we rewrite the rest of the line, including the new character, keeping count of the number of characters output. Then we write out spaces at the end of the line, where the number of spaces is the difference between the number of characters echoed for the old character and for the new one. Consider if we overwrite a control character (which has been echoed as two characters) with a character that is echoed as a single character. In such a case, there will be one left-over character at the end of the line that we need to clear with a space. For each of those spaces, we increment the count we're keeping in variable C.

                if( ( Term.Terminal_Flags and TF_Video ) <> 0 ) then
        begin
            while( C > 0 ) do // Backup to position
            begin
                Echo( BS ) ;
                dec( C ) ;
            end ;
        end ;
        inc( Position ) ;
    end ; // .Add
If video mode is set, we need to position the cursor by sending backspaces equal in number to the characters we counted above.
Finally, we increment the input position so we are positioned after the last character typed.

    function Character_Echo( Key : cardinal ) : string ;
    // Return what to echo for a given input key

    begin
        Result := '' ;
        if( ( Flags and TIFF_NoControl ) <> 0 ) then
        begin
            case Key of
                _Control_A, _Control_D, _Control_E, _Control_F, _Control_H :
                    begin
                        if( ( Flags and TIFF_NoLineEdit ) <> 0 ) then // No line editing
                        begin
                            Result := chr( Key ) ;
                        end ;
                    end ;
                else Result := chr( Key ) ;
            end ;
        end else
This function returns the characrers that are what is to be echoed back to the terminal when a given character is received from the terminal. The first thing we check is if the TIFF_NoControl flag is set. If so, we process control characters. If line editing is disabled, we echo any of the line editing control characters as-is. All other characters are echoed as-is.

        begin
            case Key of
                _DEL: ; // Nothing is echoed
                _ESC : Result := '$' + CRLF ;
                _Control_A.._Control_Z:
                    case Key of
                        _Control_A, _Control_D, _Control_E, _Control_F :
                            begin
                                if( ( Flags and TIFF_NoLineEdit ) <> 0 ) then
                                begin
                                    Result := '^' + chr( 64 + Key ) ;
                                end ;
                            end ;
                        _Control_H :
                            begin
                                if( ( Flags and TIFF_NoLineEdit ) <> 0 ) then
                                begin
                                    Result := BS ;
                                end ;
                            end ;
                        _Control_J : Result := LF + CR ; // Delimiter
                        _Control_G, _Control_I, _Control_L, _NUL : Result := chr( Key ) ; // Just echo character
                        _Control_M : Result := CRLF ;
                        _Control_O :
                            begin
                                if( ( Term.Output_Filter.Flags and TOFF_Null ) = 0 ) then
                                begin
                                    Result := '^O' + CRLF ;
                                end ;
                            end ;
                        _Control_U, _Control_X :
                            if( ( Term.Terminal_Flags and TF_Video ) = 0 ) then
                            begin
                                Result := '^' + chr( 64 + Key ) + CRLF ;
                            end ;
                        _Control_Z : Result := 'EXIT' + CRLF ;
                        else Result := '^' + chr( 64 + Key ) ;
                    end ;
                else Result := chr( Key ) ; // Echo everything else as is
            end ; // case Key
        end ;
    end ; // Character_Echo
If TIFF_NoControl is not set, we handle things slightly differently. We never echo anything for the delete character. For control A/D/E/F/H, we echo nothing if line editing is enabled. Otherwise we echo a circumflex and the corresponding letter - except for Control H, which is echoed as itself (aka backspace). For null, tab (control I), bell (control G), and form feed (control L) we echo the character as-is (null does nothing and the others perform functions on the terminal). Control M (carriage return) and control J (linefeed) are delimiters and we echo both CR and LF in either case. For Control O, if the output is off, we echo nothing; otherwise we echo "^O". Control U/X echo nothing if the terminal is in video mode, or the circumflex and letter if hardcopy. Control Z echoes "EXIT" and CRLF. Any other control codes are echoed with the circumflex and letter. And all other non control characters are echoed literally. Note that the escape character is echoed as a dollar sign ($) - which is how VMS and RSTS/E echoed it. It is also a delimiter, so we echo a CRLF as well.

You may note that we didn't have any special processing for Control C, Control Y, Control Q, Control S, Control O, or Control T. That is because these are handled elsewhere, which we will discuss in the next article.

This has been a long article, because the input filter processing is complex. Although we've examined how we cook terminal input, we haven't yet seen how characters get into the buffer in the first place. In the next article, we will examine that process.