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

Glossary/Index


Download sources
Download binaries

UCL Command Abbreviation

In this article, we continue our discussion of UCL command processing in the Process procedure. In the previous article, we left off with the check for a label and the check for interactive mode.

            // Resolve symbol...
            S1 := LIB_Get_Wildcard_Symbol( Sym ) ;
            if( S1 <> '' ) then
            begin
                if( copy( S1, 1, 1 ) = '$' ) then // Start with $ = run
                begin
                    Sym := 'run' ;
                    Parser.Put_Token( S ) ;
                    S := copy( S1, 2, length( S1 ) ) ;
                end else
                begin
                    Sym := S1 ;
                end ;
            end ;
UCL can translate symbols into commands, either UCL commands or programs to run. UCL allows abbreviation of commands by including an asterisk within the symbol name. For instance, with "PR*INT" as an example symbol name, the user can enter "PR", "PRI", "PRIN", or "PRINT" which all match this symbol. If the symbol translation returns a non-null value (meaning that the symbol is defined and has a value), we check to see if the first character in the symbol is a dollar sign ($). If so, that is a short-hand indication that the text following the dollar sign is a program name that should be run. In such as case, we prefix the rest of the line with the "run" command and then continue processing the command. If the first character is not a dollar sign, we replace Sym with the value of the symbol and continue on. This is really nothing more than another type of symbol substitution, but in this case no apostrophe or ampersand is needed. When a symbol begins a command, its value is automatically substituted. The reason for this is so that commands can be defined that a user can enter without having to know anything about UCL, symbol substitution, or the way that a particular UOS system is configured. In the above example, the user can simply type "PRINT X", and UCL will translate it into a appropriate command - for instance, something like "RUN SYS$LIBRARY:PRINT.EXE X".

Of special note is the new starlet call: LIB_Get_Wildcard_Symbol. This operates like the LIB_Get_Symbol call, except that it matches on abbreviated symbols (ie those with an asterisk in the name).

function LIB_Get_Wildcard_Symbol( const Name : string ; Table : longint = 0 ) : string ;

var SysRequest : PString2I1_Request ;

begin
    // Setup...
    Result := '' ;
    getmem( SysRequest, sizeof( SysRequest^ ) ) ;
    fillchar( SysRequest^, sizeof( SysRequest^ ), 0 ) ;
    SysRequest.Request.Subsystem := UOS_Subsystem_SSC ;
    SysRequest.Request.Request := UOS_SSC_Get_Wildcard_Symbol ;
    SysRequest.Request.Length := sizeof( SysRequest^ ) - sizeof( TSystem_Request ) ;

    Set_String( Name, SysRequest.String1 ) ;
    SysRequest.Integer1 := Table ;

    // Get size of symbol...
    Call_To_Ring0( integer( SysRequest ) ) ;
    if( SysRequest.Request.Status <> UE_Success ) then // Failed
    begin
        exit ;
    end ;

    // Receive value...
    setlength( Result, SysRequest.String2.Length ) ;
    if( SysRequest.String2.Length > 0 ) then
    begin
        SysRequest.String2.Buffer := int64( PChar( Result ) ) ;
        Call_To_Ring0( integer( SysRequest ) ) ;
        if( SysRequest.Request.Status <> UE_Success ) then // Failed
        begin
            Result := '' ;
            exit ;
        end ;
    end ;
end ;
The code for LIB_Get_Wildcard_Symbol is pretty much the same as all of the other system calls - specifically LIB_Get_Symbol - so we won't comment on it here.

Now let's look at the code in SSC_Main. The current case for UOS_SSC_Get_Symbol is modified to respond to both that call and the new one:

        UOS_SSC_Get_Symbol, UOS_SSC_Get_Wildcard_Symbol:

Next we add a new parameter to the Get_Symbol method. In the above case, the modified call looks like this:

                    S := Get_Symbol( PS2I1.Integer1, PID, PAnsiChar( Work ), SReq.Request = UOS_SSC_Get_Wildcard_Symbol ) ;

Here is the new Get_Symbol method declaration:

function TUOS_SSC.Get_Symbol( LNTable : cardinal ; PID : TPID ;
    Name : PChar ; Wildcard : boolean = False ) : TUOS_String ;
Note that we default the new parameter to be false if it isn't specified. This keeps any existing code that calls the method from breaking.

Although, because the new parameter is defaulted, we don't need to specify a wildcard in other calls to Get_Symbol, we will go ahead and update the calls within this method like so:

            begin
                // Check process first...
                Result := Get_Symbol( LNM_PROCESS, PID, Name, Wildcard ) ;
                if( Result <> nil ) then
                begin
                    exit ;
                end ;

                // Check job second...
                Result := Get_Symbol( LNM_JOB, PID, Name, Wildcard ) ;
                if( Result <> nil ) then
                begin
                    exit ;
                end ;

                // Check groups third...
                Result := Get_Symbol( LNM_GROUP, PID, Name, Wildcard ) ;
                if( Result <> nil ) then
                begin
                    exit ;
                end ;

                // Check system fourth...
                Result := Get_Symbol( LNM_SYSTEM, PID, Name, Wildcard ) ;
                if( Result <> nil ) then
                begin
                    exit ;
                end ;

                // Finally check cluster...
                Result := Get_Symbol( LNM_CLUSTER, PID, Name, Wildcard ) ;
                exit ;
            end ;

Finally, near the end of the method we add the following:

    if( Wildcard ) then
    begin
        Index := Table.Wildcard_Index( PChar( N ) ) ;
        if( Index <> -1 ) then
        begin
            Temp.P := Table.Get_Index( Index, Len ) ;
            _Temp.Len := Len ;
            Result := Temp ;
            exit ;
        end ;
    end ;
If a wildcard lookup is requested, we call the Wildcard_Index method of the table. If it is found, we return the value of the wildcard match.

function TSymbol_Table.Wildcard_Index( Name : PChar ) : longint ;

var I, P : integer ;
    N, S : string ;

begin
    N := Edit( Name, 512 ) ;
    for I := 0 to Count - 1 do
    begin
        S := Edit( List[ I ], 512 ) ;
        P := pos( '*', S ) ;
        if( P > 0 ) then // Wildcard present
        begin
            system.delete( S, P, 1 ) ;
            if(
                ( copy( S, 1, length( N ) ) = N )
                and
                ( length( N ) <= length( S ) )
                and
                ( length( N ) >= P - 1 )
              ) then
            begin
                Result := I ;
                exit ;
            end ;
        end else
        if( S = Name ) then
        begin
            Result := I ;
            exit ;
        end ;
    end ; // for I := 0 to Count - 1
end ; // TSymbol_Table.Wildcard_Index
The new method in TSymbol_Table loops through the symbol names, checks for an asterisk (indicating a wildcard), then sees if the passed name matches at least to the asterisk, and for as many characters afterward as are passed. If found, we return the index. Otherwise we see if there is an exact match. Note that this means that if there are multiple matches in the symbol table, the first one encountered will match. This will end up being the first one that was added to the table (and not subsequently deleted). Obviously, this is a more costly operation than a normal symbol lookup and although it is available to any application - of all the application code that we will look at - UCL is the only one that uses it.

In the next article, we will continue our review of UCL command processing, with a look at asynchronous system traps.

 

Copyright © 2019 by Alan Conroy. This article may be copied in whole or in part as long as this copyright is included.