Google
 

Trailing-Edge - PDP-10 Archives - DEC_CMS-20_V1.0_SRC - cms/sources/comand.bli
There are no other files named comand.bli in the archive.
module comand (	! Command language processor for all commands
		ident = '1',
		%if
		    %bliss(bliss32)
		%then
		    language(bliss32),
		    addressing_mode(external=long_relative,
				    nonexternal=long_relative)
		%else
		    language(bliss36)
		%fi
		) =
begin

!
!			  COPYRIGHT (C) 1982 BY
!	      DIGITAL EQUIPMENT CORPORATION, MAYNARD, MASS.
!
! THIS SOFTWARE IS FURNISHED UNDER A LICENSE AND MAY BE USED AND  COPIED
! ONLY  IN  ACCORDANCE  WITH  THE  TERMS  OF  SUCH  LICENSE AND WITH THE
! INCLUSION OF THE ABOVE COPYRIGHT NOTICE.  THIS SOFTWARE OR  ANY  OTHER
! COPIES  THEREOF MAY NOT BE PROVIDED OR OTHERWISE MADE AVAILABLE TO ANY
! OTHER PERSON.  NO TITLE TO AND OWNERSHIP OF  THE  SOFTWARE  IS  HEREBY
! TRANSFERRED.
!
! THE INFORMATION IN THIS SOFTWARE IS SUBJECT TO CHANGE  WITHOUT  NOTICE
! AND  SHOULD  NOT  BE  CONSTRUED  AS  A COMMITMENT BY DIGITAL EQUIPMENT
! CORPORATION.
!
! DIGITAL ASSUMES NO RESPONSIBILITY FOR THE USE OR  RELIABILITY  OF  ITS
! SOFTWARE ON EQUIPMENT WHICH IS NOT SUPPLIED BY DIGITAL.
!

!++
! Facility:  CMS Library Processor
!
! Abstract:
!
!	The purpose of this module is to hide the syntax of the
!	commands from the routines that perform command actions.
!
!
! Environment:  Transportable
!
! Author:  Earl Van Horn	Creation Date:  April, 1979
!
!--
!
! Table of Contents:
!
forward routine
    append_block : novalue,	! Append a block to a linked list.
    comand,			! Gets a command and parses it.
    curcom : novalue,		! Re-supplies parsing of current command.
    find_com_gram,		! Finds a (sub-)command entry in the
				! grammar table.
    qualifiers,			! Recognizes one or more qualifiers.
    scan_qual_codes : novalue,	! Find a series of qualifier codes in the
				! grammar table.
    user_remark ;		! Recognizes a user remark.

!
! Include Files:
!

%if %bliss(bliss32) %then
library 'sys$library:starlet';
%fi

%if %bliss(bliss36) %then
require 'jsys:';
%fi

library 'XPORT:' ;
require 'BLISSX:' ;
require 'COMUSR:' ;		! Declarations for the user of this module.
require 'LANGME:' ;		! Declarations of mechanisms internal to
				! the language processor.
require 'ZONUSR:' ;		! Declarations for user of zones.

!
! Macros:
!

!
! Equated Symbols:
!

!
! Global and Own Storage:
!
global

    ! Zone for allocating command processor stuff.
    comzon : new_zone ;

own	! Storage allowing CURCOM to return the parsing of the current command.
	! The data structures pointed to are allocated in COMZON.

    command_code,		! Integer code for the current command name.
    a_first_command_qualifier,	! Address of first qualifier block in the list
				! of command qualifiers.  K_NULL means there
				! are no command qualifiers.
    a_first_parameter,		! Address of first parameter block in the list
				! of command parameters.  K_NULL means there
				! are no parameters.
    a_remark_descriptor,	! Address of remark descriptor.  K_NULL means
				! the command does not take a remark.
    sub_command_code,		! Integer code for the sub-command.  Zero means
				! the command had none.
    successful_parse : initial(false) ;	! True means successful parse ocurred.

!
! External References:
!

external literal
    s_ambig,
    s_extra,
    s_invqual,
    s_notoption,
    s_quotenot;

external
    gratab : gratab_vector,		! Grammar table.
    usr_cmd : desc_block;		!user command entered

external routine
    abbrev,				! Find all matches for an abbreviation.
    ask,				! Get a response from the user.
    bldcom,				!
    bug,				! Report a bug.
    codspl,				! Get spelling of a keyword.
    enquot,				! Enclose in quotation marks.
    ers,				! Report a user mistake.
    fresad : novalue,			! Free a string allocated by ASK.
    getcom,				! Obtain the user's command string.
    nxtgra,				! Find the next entry in GRATAB.
    nxtlex,				! Identify the next lexeme.
    peekch,				! Test the next command character.
    prevbl,				! Was previous char. a space or tab?
    reclog,				! Recognize a /log or /nolog
    recpar,				! Recognize a parameter.
    rectst,				! Recvnize a special test qualifier.
    recval,				! Recognize a qualifier value.
    zalloc,				! Allocate storage.
    zparam : novalue ;			! Set zone parameters
routine append_block(a_a_first_block, a_new_block) : novalue =

!++
! Functional Description:
!
!	This routine appends a new block to the end of a linked chain of
!	blocks.  The link address is assumed to be the first item in each block.
!	A link address of K_NULL ends the chain.  If the chain is empty, the
!	new block becomes the first block.  The link address of the new
!	block is not disturbed.
!
! Formal Parameters:
!
!	a_a_first_block:The address of a fullword containing the address
!			of the first block of the chain to be appended to.
!			If the fullword is K_NULL, the new block is the first
!			block of the chain, and the fullword is set to
!			its address.
!	a_new_block:	The address of a block to be appended to the chain.
!
! Implicit Inputs:
!
!	None
!
! Implicit Outputs:
!
!	None
!
! Routine Value:
! Completion Codes:
!
!	None
!
! Side Effects:
!
!	None
!
!--

    begin	! APPEND_BLOCK
    $field
	next = [$address] ;		! The address of the next block is
					! always the first item in a block.
    bind
	a_first_block = .a_a_first_block,
	new_block = .a_new_block: block field(next) ;

    if .a_first_block eql k_null
    then
	a_first_block = new_block
    else
	begin	! Not the first.
	local
	    r_this_block : ref block field(next) ;

	r_this_block = .a_first_block ;
	while .r_this_block[next] neq k_null do
	    r_this_block = .r_this_block[next] ;

	r_this_block[next] = new_block ;
	end ;	! Not the first.

    end ;	! APPEND_BLOCK
global routine comand(a_com, a_sub,
			a_a_first_qual, a_a_first_param, a_a_remark) =

!++
! Functional Description:
!
!	COMAND obtains the user's command line and parses it, checking
!	for syntax errors.  If the user made no mistakes, the
!	constituents of the command are passed back to the caller
!	using the fullwords supplied as parameters, and TRUE is returned.
!
!	The parameters and qualifiers of the command are represented by lists
!	of data blocks allocated by COMAND and defined in COMUSR.REQ.
!	The text of each parameter and the value, if any, of each qualifier,
!	are character strings denoted by descriptors in the corresponding
!	data blocks.  In addition, certain parameters and qualifier values are
!	described by parse trees using tree node blocks defined in COMUSR.REQ.
!
!	Each parameter, qualifier value, and user remark has leading and
!	trailing spaces and tabs removed before being passed back to the caller.
!	In addition, the lexical rules of the command language are
!	enforced, which means that most constructs also have no embedded spaces
!	or tabs.
!
!	The following constructs are converted to upper case
!	before being passed back to the caller:
!
!		generation-ref
!		element-ref
!		extended-file-name
!		file-spec
!
!	The following constucts are checked for correct form:
!
!		generation-ref
!		element-ref
!		extended-file-name
!
!	Note that file specifications are not checked for correct form.
!
!	In Release Zero, the only construct for which a tree is provided
!	is the generation reference.  See COMUSR.REQ for information on
!	the form of this tree.
!
!	If the user made some mistakes, one or more messages describing them
!	are written to the terminal error stream, and FALSE
!	is returned.  In this case, the fullwords supplied as parameters
!	have undefined content.
!
! Formal Parameters:
!
!	a_com :		Address of a fullword in which the integer code
!			for the command will be stored.  These codes are
!			defined in COMUSR.REQ.
!	a_sub :		Address of a fullword in which the integer code
!			for the sub-command will be stored.  These codes are
!			defined in COMUSR.REQ.  If the command has no
!			sub-command, zero is stored.
!	a_a_first_qual:	Address of a fullword in which COMAND will store either
!			the address of the first qualifier block for the
!			command as a whole, or K_NULL if the command has no
!			qualifiers.  Qualifier blocks are defined in COMUSR.REQ.
!	a_a_first_param:Address of a fullword in which COMAND will store either
!			the address of the first parameter block for the
!			command, or K_NULL if the command has no
!			parameters.  Parameter blocks are defined in COMUSR.REQ.
!	a_a_remark:	Address of a fullword in which COMAND will store either
!			the address of a descriptor for the user's remark, or
!			K_NULL if the command cannot have a user remark.  Note
!			that a missing remark in a command that can have one
!			is reported as a remark of zero length.
!
! Implicit Inputs:
!
!	The GRATAB and SPLTAB vectors, declared in module GRAMAR.
!
! Implicit Outputs:
!
!	None
!
! Routine Value:
! Completion Codes:
!
!	TRUE is returned if the user made no mistake, and otherwise FALSE.
!
! Side Effects:
!
!	Many data structures are allocated in zone COMZON, using ZALLOC.
!
!	Also, the values returned by a successful parse are stored in own
!	variables declared in the head of this module.  These values enable
!	CURCOM to return the same values returned by COMAND.
!
!--

    begin	! COMAND.
    bind
	com = .a_com,
	sub = .a_sub,
	r_first_qual = .a_a_first_qual : ref qualifier_block,
	r_first_param = .a_a_first_param : ref parameter_block,
	r_remark = .a_a_remark : ref desc_block ;

    local
	dummy,				! Receives a parameter qualifier list
					! that must be empty.
	first_com_qua_x,		! Index of first command qualifier
					! item in GRATAB.
	last_com_qua_x,			! Index of last command qualifier
					! item in GRATAB.
	optional,			! Means an optional parameter group
					! is being processed.
	option_present,			! Means the user has supplied something
					! for an optional parameter group.
	r_param : ref parameter_block,	! Address of current parameter block.
	repeating,			! Means a repeatable parameter group
					! is being processed.
	repeat_x,			! Index of the start of the repeatable
					! group in GRATAB.
	this_x;				! Index of current item in GRATAB.

    routine bug_in_table(a_trouble, a_rest) : novalue =
	bug(cat(.a_trouble, ' before processing ', .a_rest)) ;
	! A_REST is passed to avoid an up-level reference.

    ! Initialize each output parameter execpt COM.
    sub = 0 ;
    r_first_qual = k_null ;
    r_first_param = k_null ;
    r_remark = k_null ;

    ! Initialize the zone for command processor data.
    %if not %bliss(bliss16)
    %then zparam(comzon, 1000, 500, 0) ;
    %fi ! Otherwise use the defaults.

    ! Find the GRATAB entry for this (sub-)command.
    if not find_com_gram(usr_cmd, this_x)
    then
	return false ;

    ! Set the COM and SUB output parameters.
    com = .gratab[.this_x] ;
    this_x = .this_x + 1 ;
    if .gratab[.this_x] geq k_sub_command_1
		and .gratab[.this_x] leq k_sub_command_n
    then
	begin	! Sub-command.
	sub = .gratab[.this_x] ;
	this_x = .this_x + 1 ;
	end ;	! Sub-command.

    ! See if the (sub-)command as a whole can have any qualifiers.
    scan_qual_codes(this_x, first_com_qua_x, last_com_qua_x) ;

    ! Process all the qualifiers typed just after the (sub-)command.
    if not qualifiers(usr_cmd, .first_com_qua_x, .last_com_qua_x, 0, -1,
			r_first_qual, dummy)
    then
	return false ;
    if .dummy neq k_null
    then
	bug(lit('Parameter qualifier list is not empty')) ;

    ! Interpret the grammar table for parameters and a user remark.
    optional = false ;
    option_present = false ;
    repeating = false ;
    while .gratab[.this_x] neq k_end_com_mark do
	begin	! Interpret one item and advance to the next.

	selectone .gratab[.this_x] of
	    set	! One item in grammar table.

	    [k_beg_opt_mark]:
		begin	! Begin optional.
		if .optional
		then
		    bug_in_table(lit('Begin option mark'), usr_cmd) ;
		optional = true ;
		option_present = (.usr_cmd[desc_len] gtr 0 
			 	  and not peekch(usr_cmd, lit('"'))) ;
		end ;	! Begin optional

	    [k_end_opt_mark]:
		begin	! End optional.
		if not .optional
		then
		    bug_in_table(lit('End option mark'), usr_cmd) ;
		optional = false ;
		option_present = false ;
		end ;	! End optional.

	    [k_beg_rep_mark]:
		begin	! Begin repeating.
		if .repeating
		then
		    bug_in_table(lit('Begin repeat mark'), usr_cmd) ;
		repeating = true ;
		repeat_x = .this_x ;
		end ;	! Begin repeating.

	    [k_end_rep_mark]:
		if not .repeating
		then
		    bug_in_table(lit('End repeat mark'), usr_cmd) ;
		! REPEATING is set to FALSE below.

	    [k_end_gram_mark]:
		bug_in_table(lit('End of grammar'), usr_cmd) ;

	    [k_beg_com_mark]:
		bug_in_table(lit('Command mark'), usr_cmd) ;

	    [k_remark_syn]:
		begin	! Process remark.
		if not user_remark(usr_cmd, r_remark)
		then
		    return false ;
		end ;	! Process remark.

	    [otherwise]:
		begin	! Handle a parameter entry and its qualifier entries.
		if (if not .optional then true else .option_present)
		then
		    begin	! Process a parameter.

		    ! Recognize and parse the parameter.
		    if not recpar(usr_cmd, .gratab[.this_x], r_param)
		    then
			return false ;

		    ! Process all qualifiers just after the parameter.
			begin	! Receive qualifiers.
			local
			    a_first_par_qua,	! Address of first qualifier
						! block for this parameter.
			    first_par_qua_x,	! Index of first qualifier code
						! in the GRATAB array, else 0.
			    last_par_qua_x ;	! Index of last qualifier code
						! in the GRATAB array, else 0.

			this_x = .this_x + 1 ;
			scan_qual_codes(this_x, first_par_qua_x,
						last_par_qua_x) ;
			if not qualifiers(usr_cmd,
					.first_com_qua_x, .last_com_qua_x,
					.first_par_qua_x, .last_par_qua_x,
					r_first_qual, a_first_par_qua)
			then
			    return false
			else
			    r_param[par_a_qual] = .a_first_par_qua ;
						! PAR_A_QUAL may not be a
						! fullword.
			this_x = .this_x - 1 ;	! Increment to next GRATAB
						! entry is always done below.
			end ;	! Receive qualifiers.

		    ! Add the parameter to the list.
		    append_block(r_first_param, .r_param) ;

		    end ;	! Process a parameter.
		end ;	! Handle a parameter entry and its qualifier entries.
	    tes ;	! One item in grammar table.

	! Advance to the next item in the grammar table.
	if .gratab[.this_x] neq k_end_rep_mark
	then
	    this_x = .this_x + 1
	else
	    begin	! End of one repeat pass.
	    if .usr_cmd[desc_len] eql 0 or peekch(usr_cmd, lit('"'))
	    then
		begin	! No more repititions.
		this_x = .this_x + 1 ;
		repeating = false ;
		end	! No more repititions.
	    else
		this_x = .repeat_x + 1 ;	! Go back for another pass.
	    end ;	! End of one repeat pass.

	end ;	! Interpret one item and advance to the next.

    ! All input should have been processed.
    if .usr_cmd[desc_len] gtr 0
    then
	begin	! Report superfluity.
	ers(s_extra,cat(usr_cmd, (' is superfluous'))) ;
	return false ;
	end ;	! Report superfluity.

    ! Set the own variables for CURCOM.
    command_code = .com ;
    sub_command_code = .sub ;
    a_first_command_qualifier = .r_first_qual ;
    a_first_parameter = .r_first_param ;
    a_remark_descriptor = .r_remark ;
    successful_parse = true ;

    true			! Successful parse.
    end ;	! COMAND.
global routine curcom(a_com, a_sub,
		a_a_first_qual, a_a_first_param, a_a_remark) : novalue =

!++
! Functional Description:
!
!	This routine returns the same results as COMAND, provided COMAND
!	successfully parsed the command and returned TRUE.  Otherwise a
!	bug is reported.
!
! Formal Parameters:
!
!	See COMAND in this module.
!
! Implicit Inputs:
!
!	Own variables in this module in which the results returned by
!	COMAND are saved.  Note that some of these variables are
!	addresses of structures that COMAND allocates in COMZON, so
!	COMZON must not have been freed before this routine is called.
!
! Implicit Outputs:
!
!	None.
!
! Routine Value:
! Completion Codes:
!
!	None
!
! Side Effects:
!
!	None
!
!--

    begin	! CURCOM
    bind
	com = .a_com,
	sub = .a_sub,
	r_first_qual = .a_a_first_qual : ref qualifier_block,
	r_first_param = .a_a_first_param : ref parameter_block,
	r_remark = .a_a_remark : ref desc_block ;

    ! Make sure this call is legal.
    if not .successful_parse
    then
	bug(lit('Illegal call to CURCOM')) ;

    ! Copy the results to the caller's fullwords.
    com = .command_code ;
    sub = .sub_command_code ;
    r_first_qual = .a_first_command_qualifier ;
    r_first_param = .a_first_parameter ;
    r_remark = .a_remark_descriptor ;

    end ;	! CURCOM
routine find_com_gram(user_string, a_this_x) =

!++
! Functional Description:
!
!	This routine obtains the command lexeme from the user string,
!	and if the command has sub-commands, it obtains the sub-command
!	lexeme.  It finds the entry for the command, or for the command and its
!	sub-command, in the GRATAB table, and delivers the index of this entry.
!
!	If the user made any mistakes, he is told about them, and FALSE
!	is returned.
!
! Formal Parameters:
!
!	user_string:	Address of descriptor of the user's command string.
!			The descriptor is changed to identify the remainder
!			of the command string, following the command and
!			any sub-command.
!	a_this_x:	Address of a fullword in which the routine will store
!			the index of the GRATAB entry for this (sub-)command.
!			The index is that of the item containing the
!			command code.
!
! Implicit Inputs:
!
!	The GRATAB and SPLTAB vectors declared in module GRAMAR.
!
! Implicit Outputs:
!
!	None
!
! Routine Value:
! Completion Codes:
!
!	TRUE if the user made no mistake, otherwise FALSE.
!
! Side Effects:
!
!	None
!
!--

    begin	! FIND_COM_GRAM

    map
	user_string : ref desc_block;

    bind
	this_x = .a_this_x ;

    local
	all_matches : desc_block,	! All sub-commands that match what the
					! user typed, separated by spaces.
	found,				! Means a command has has been found
					! that matches what the user typed.
	i,				! Index of current GRATAB item.
	lexeme : desc_block,		! Descriptor of command or
					! sub-command lexeme.
	r_com_spell : ref desc_block ;	! Address of descriptor of full command.

    ! Get the command lexeme.
    if
	not nxtlex(.user_string, k_normal_lex, k_null, lexeme)
    then
	bug(cat('NXTLEX returned to COMAND for ', .user_string)) ;

    ! Find the first grammar table entry for this command.  The command is
    ! known to be correct and unambiguous because it has been checked by
    ! COMCOD, which is called when dispatching to the caller of COMAND.
    this_x = 0 ;			! Start at the beginning of GRATAB.
    found = false ;
    while nxtgra(this_x) do
	begin	! Examine one entry in the grammar table.
	
	! Get the full spelling of the command.
	r_com_spell = codspl(.gratab[.this_x]) ;

	! See if the user's lexeme abbreviates this command.
	if 0 eql ch$diff(.r_com_spell[desc_ptr],
	       ch$find_sub(len_comma_ptr(.r_com_spell), len_comma_ptr(lexeme)))
	then
	    begin	! Found the command.
	    found = true ;
	    exitloop ;
	    end ;	! Found the command.

	end ;	! Examine one entry in the grammar table.

    if not .found
    then
	bug(cat('COMAND could not find ', lexeme)) ;

    ! .THIS_X now indicates the desired entry, or a candidate if the command
    ! has sub-commands.

    if .gratab[.this_x + 1] geq k_sub_command_1 and
		.gratab[.this_x + 1] leq k_sub_command_n
    then
	begin	! The command has sub-commands.

	! Get the sub-command lexeme
	! (see if we need to prompt)
	if
	    .user_string[desc_len] eql 0
	then
	    begin

	    local
		sub_string : ref desc_block;

	    do
		sub_string=ask(prompt_lit('Option'))
	    until
		.sub_string[desc_len] neq 0;

	    !Add the string to the command line text
	    bldcom(.sub_string)
	    end;

	if
	    not nxtlex(.user_string, k_normal_lex, lit('option'), lexeme)
	then
	    return false ;

	! Find out which sub-command(s) it matches.
	i = .this_x ;
	$str_desc_init(descriptor = all_matches, string = (0, k_null)) ;
	do
	    begin	! Examine one sub-command entry in the grammar table.

	    ! Make sure we are still looking at the same command.
	    if .gratab[.i] neq .gratab[.this_x]
	    then
		exitloop ;

	    ! See if the lexeme matches this sub-command.
	    if abbrev(lexeme, codspl(.gratab[.i + 1]), all_matches, comzon)
	    then
		this_x = .i ;	! A new candidate.

	    end		! Examine one sub-command entry in the grammar table.
	while nxtgra(i) ;

	if .all_matches[desc_len] eql 0
	then
	    begin	! No matching sub-command.
	    ers(s_notoption,cat(lexeme, ' is not an option of ',
			.r_com_spell)) ;
	    return false ;
	    end ;	! No matching sub-command.

	if not ch$fail(ch$find_ch(len_comma_ptr(all_matches), %c' '))
	then
	    begin	! Ambiguous sub-command.
	    ers(s_ambig,cat(lexeme, ' could mean: ', all_matches));
	    return false ;
	    end ;	! Ambiguous sub-command.

	! .THIS_X now designates the one matching sub-command entry in GRATAB.
	end ;	! The command has sub-commands.

    true
    end ;	! FIND_COM_GRAM
routine qualifiers(a_user_string,
		first_com_qua_x, last_com_qua_x,
		first_par_qua_x, last_par_qua_x,
		a_a_first_com_q, a_a_first_par_q) =

!++
! Functional Description:
!
!	This routine recognizes the qualifiers and their values, if any, that
!	occur next in the user's input string, and advances the string
!	descriptor past the information recognized.  Each qualifier
!	is represented by a qualifier block, which is appended to
!	one of two chains, depending on whether the qualifier applies
!	to the command as a whole, or to the most recent parameter.
!
!	The qualifiers allowed at this point in the input string are
!	defined by two pairs of indexes in the GRATAB array.  One pair
!	defines the command qualifiers, and the other defines the
!	parameter qualifiers.
!
!	If a qualifier appears more than once in a command, the most recent
!	occurrence is used.  There is no significance to the order of
!	qualifiers in their chains.
!
! Formal Parameters:
!
!	a_user_string:	Address of a descriptor for the string to be
!			examined for leading qualifiers.  This descriptor
!			will be advanced by NXTLEX over the qualifiers
!			processed.
!	first_com_qua_x:Index of the first in a series of adjacent GRATAB
!			entries that contain integer codes for the command
!			qualifiers allowed at this point.
!	last_com_qua_x:	Index of the last entry in the above series.  If the
!			last index is less than the first index, no command
!			qualifiers are allowed.
!	first_par_qua_x:Index of the first in a series of adjacent GRATAB
!			entries that contain integer codes for the parameter
!			qualifiers allowed at this point.
!	last_par_qua_x: Index of the last entry in the above series.  If the
!			last index is less than the first index, no parameter
!			qualifiers are allowed.
!	a_a_first_com_q:Address of a fullword containing the address of the
!			first of a chain of qualifier blocks for the command
!			qualifiers already recognized.  The form of the
!			qualifier blocks is declared in COMUSR.REQ.  If
!			additional command qualifers are found in the user
!			string, their blocks will be appended to this
!			chain.  If the fullword contains K_NULL, no command
!			qualifiers have been found yet.  If the routine
!			now finds some command qualifiers, the chain will
!			be started, i.e., the contents of the fullword will
!			be replaced by the address of the first of a chain
!			of qualifier blocks.
!	a_a_first_par_q:Address of a fullword in which this routine will
!			store either K_NULL or the address of the first of a
!			chain of qualifier blocks for the parameter qualifiers
!			found in the user string.
!
! Implicit Inputs:
!
!	GRATAB declared in module GRAMAR
!
! Implicit Outputs:
!
!	None
!
! Routine Value:
! Completion Codes:
!
!	FALSE if the user made a mistake, otherwise TRUE.
!
! Side Effects:
!
!	Qualifier blocks are allocated in COMZON.
!
!--

    begin	! QUALIFIERS
    bind
	user_string = .a_user_string : desc_block,
	r_first_com_qua = .a_a_first_com_q : ref qualifier_block,
	r_first_par_qua = .a_a_first_par_q : ref qualifier_block ;
    local
	all_matches : desc_block,	! The matching qualifiers, separated
					! by spaces.
	first_qua_x,			! Index of first GRATAB entry scanned.
	found_special,			! RECTST found special test qualifier.
	found_log,			! RECLOG found /log or /nolog
	is_command_qual,		! Means qualifier is command qualifier.
	last_qua_x,			! Index of last GRATAB entry scanned.
	p_slash, 			! Char. pointer to slash in user string.
	qualifier_code,			! Integer code for a qualifier found.
	r_qua : ref qualifier_block,	! Address of qualifier block for the
					! qualifier currently being recognized.
	user_spell : desc_block ;	! Descriptor of a slash followed by
					! the user's spelling of the qualifier's
					! name.
    label
	process_one_qualifier ;

    ! Initialize.
    r_first_par_qua = k_null ;

    ! Process each leading qualifier in the user string.
    while peekch(user_string, lit('/')) do

process_one_qualifier:
	begin	! Process one qualifier.

	! Recognize and process any special test qualifiers.
	if not rectst(user_string, found_special)
	then
	    return false ;			! User mistake.
	if .found_special
	then
	    leave process_one_qualifier ;	! Look for another slash.
	
	! Recognize and process any log-related qualifiers
	if not reclog(user_string, found_log, 
		.first_com_qua_x, .last_com_qua_x,
		.first_par_qua_x, .last_par_qua_x )
	then
	    return false;			! User mistake
	if .found_log
	then
	    leave process_one_qualifier ;	! Look for another slash

	! Eat up the slash, but remember where it was.
	if not nxtlex(user_string, k_normal_lex, k_null, user_spell)
	then
	    bug(lit('NXTLEX returned to QUALIFIERS')) ;
	p_slash = .user_spell[desc_ptr] ;

	! Get the qualifier name.
	if not nxtlex(user_string, k_normal_lex, lit('qualifier name'),
			user_spell)
	then
	    return false ;

	! Let the descriptor denote the slash followed by the name.
	user_spell[desc_ptr] = ch$plus(.user_spell[desc_ptr], -1) ;
	user_spell[desc_len] = .user_spell[desc_len] + 1 ;

	! Make sure the slash abuts the name.
	if ch$diff(.user_spell[desc_ptr], .p_slash) gtr 0
	then
	    begin	! Move the slash.
	    ch$wchar(%c' ', .p_slash) ;
	    ch$wchar(%c'/', .user_spell[desc_ptr]) ;
	    end ;	! Move the slash.

	! Find an unambiguous match with a qualifier.
	$str_desc_init(descriptor = all_matches, string = (0, k_null)) ;
	incr n from 1 to 2 do
	    begin	! Examine either command or parameter qualifiers.

	    if .n eql 1
	    then
		begin	! Focus on command qualifiers.
		first_qua_x = .first_com_qua_x ;
		last_qua_x = .last_com_qua_x ;
		end	! Focus on command qualifiers.
	    else
		begin	! Focus on parameter qualifiers.
		first_qua_x = .first_par_qua_x ;
		last_qua_x = .last_par_qua_x ;
		end ;	! Focus on parameter qualifiers.

	    ! Examine one kind of qualifier.
	    incr i from .first_qua_x to .last_qua_x do
		begin	! Examine one qualifier.

		! See if the user's qualifier matches this one.
		if abbrev(user_spell, codspl(.gratab[.i]),
				all_matches, comzon)
		then
		    begin	! Matching qualifier.
		    qualifier_code = .gratab[.i] ;
		    is_command_qual = (.n eql 1) ;
		    end ;	! Matching qualifier.

		end ;	! Examine one qualifier.
	    end ;	! Examine either command or parameter qualifiers.

	! Report invalid qualifier.
	if .all_matches[desc_len] eql 0
	then
	    begin	! Can't find it.
	    ers(s_invqual,cat(user_spell, ' is incorrect, or ',
				'perhaps misplaced'));
	    return false ;
	    end ;	! Can't find it.

	! Report ambiguous qualifier abbreviation.
	if not ch$fail(ch$find_ch(len_comma_ptr(all_matches), %c' '))
	then
	    begin	! Ambiguous qualifier.
	    ers(s_ambig,cat(user_spell, ' could mean: ',all_matches)) ;
	    return false ;
	    end	;	! Ambiguous qualifier.

	! See if this qualifier has already been given.
	r_qua = (if .is_command_qual
		 then .r_first_com_qua
		 else .r_first_par_qua) ;
	until
	    if .r_qua eql k_null
	    then true
	    else .r_qua[qua_code] eql .qualifier_code
	do
	    r_qua = .r_qua[qua_a_next] ;

	! Use a previous block for the same qualifier, thus overriding its
	! value, if any.  Otherwise allocate a new block.
	if .r_qua eql k_null
	then
	    begin	! Get and append a new qualifier block.
	    r_qua = zalloc(k_qua_units, comzon) ;
	    r_qua[qua_code] = .qualifier_code ;
	    r_qua[qua_a_next] = k_null ;
	    append_block(if .is_command_qual
			 then r_first_com_qua
			 else r_first_par_qua,
			.r_qua) ;
	    end ;	! Get and append a new qualifier block.
	r_qua[qua_a_tree] = k_null ;	! Abandon any previous tree.

	! Recognize and process any value for this qualifier.
	if not recval(user_string, .r_qua)
	then
	    return false ;

	end ;	! Process one qualifier.

    true
    end ;	! QUALIFIERS
routine scan_qual_codes(a_this_x, a_first_qual_x, a_last_qual_x): novalue =

!++
! Functional Description:
!
!	This routine looks for a series of qualifier codes at a given position
!	in the GRATAB array.  If qualifier codes are present, the given
!	position must contain K_BEG_QUA_MARK.  The series concludes with
!	K_END_QUA_MARK.  The indexes of the first and last qualifer codes
!	are returned.
!
!	If the given position does not contain K_BEG_QUA_MARK, it is assumed
!	that no qualifier codes are present, and 0 and -1 are returned
!	for the first and last indexes, respectively.
!
!
! Formal Parameters:
!
!	a_this_x:	Address of the index in the GRATAB array at which to
!			look for K_BEG_QUA_MARK.  If that code is found, this
!			index will be changed to that of the entry one beyond
!			the corresponding K_END_QUA_MARK.
!	a_first_qual_x:	Address of a fullword where the index of the first
!			qualifier will be returned.  Zero is returned if
!			there are no qualifiers at this point in GRATAB.
!	a_last_qual_x:	Address of a fullword where the index of the last
!			qualifier will be returned.  -1 is returned if 
!			there are no qualifiers at this point in GRATAB.
!
! Implicit Inputs:
!
!	The GRATAB vector declared in module GRAMAR.
!
! Implicit Outputs:
!
!	None
!
! Routine Value:
! Completion Codes:
!
!	None
!
! Side Effects:
!
!	None
!
!--

    begin	! SCAN_QUAL_CODES
    bind
	this_x = .a_this_x,
	first_qual_x = .a_first_qual_x,
	last_qual_x = .a_last_qual_x ;

    if .gratab[.this_x] eql k_beg_qua_mark
    then
	begin	! Some qualifiers.
	this_x = .this_x + 1 ;
	first_qual_x = .this_x ;
	while .gratab[.this_x] neq k_end_qua_mark do
	    this_x = .this_x + 1 ;
	last_qual_x = .this_x - 1 ;
	this_x = .this_x + 1 ;
	end	! Some qualifiers.
    else
	begin	! No qualifiers.
	first_qual_x = 0 ;
	last_qual_x = -1 ;
	end ;	! No qualifiers.

    end ;	! SCAN_QUAL_CODES
routine user_remark(a_user_string, a_a_remark) =

!++
! Functional Description:
!
!	This routine obtains a remark from the user.  If the remainder of
!	the command string begins with a quotation mark, the remark is the
!	next quoted string in the command. (If the command string and its
!	descriptor were most recently processed by NXTLEX, the remainder
!	never begins with a space or tab.)  If the remainder is empty, the
!	user is prompted for remark text, and the text is enclosed in
!	quotation marks (with embedded quotes doubled).
!
!	The text of a remark may be empty.
!
!	If the user made a mistake, FALSE is returned.
!
! Formal Parameters:
!
!	a_user_string:	Address of a descriptor for the string to be
!			examined for a remark.
!	a_a_remark:	Address of a fullword where the address of a
!			descriptor for the remark will be stored.
!
! Implicit Inputs:
!
!	None
!
! Implicit Outputs:
!
!	None
!
! Routine Value:
! Completion Codes:
!
!	TRUE if the user made no mistakes, otherwise FALSE.
!
! Side Effects:
!
!	A descriptor or a SAD block is allocated in COMZON by ZALLOC.
!	The user may be interrogated with ASK.
!
!--

    begin	! USER_REMARK
    bind
	user_string = .a_user_string : desc_block,
	r_remark = .a_a_remark : ref desc_block ;
    local
	char,			! Character being examined.
	p_finish,		! Pointer to last character not space or tab.
	p_start,		! Pointer to first character not space or tab.
	r_remark_text ;		! Address of descriptor of text obtained by
				! prompting.

    ! Get the raw remark.
    if peekch(user_string, lit('"'))
    then
	begin	! Remark in command line.

	! Make sure the remark is preceded by a space or tab.
	if not prevbl(user_string, k_remark_syn)
	then
	    return false ;

	! Allocate a descriptor and make it denote the remark.
	r_remark = zalloc(k_desc_units, comzon) ;
	if not nxtlex(user_string, k_quotation_lex, codspl(k_remark_syn),
			.r_remark)
	then
	    return false ;
	end	! Remark in command line.
    else if .user_string[desc_len] eql 0
    then
	begin	! Prompted remark.
	r_remark_text = ask(prompt_lit('Remark')) ;
	r_remark = enquot(.r_remark_text) ;
	fresad(.r_remark_text) ;
	end	! Prompted remark.
    else
	begin	! Non-quotation is present.
	ers(s_quotenot,cat(user_string,' must be a quoted string')) ;
	return false ;
	end ;	! Non-quotation is present.

    true
    end ;	! USER_REMARK
end				! Module COMAND
eludom