Google
 

Trailing-Edge - PDP-10 Archives - decuslib20-07 - decus/20-0172/scannr.bli
There is 1 other file named scannr.bli in the archive. Click here to see a list.
!<BLF/lowercase_user>
!<BLF/uppercase_key>
!
!
MODULE scannr (					!
!
!

%IF %BLISS (BLISS32)
%THEN
		ADDRESSING_MODE (EXTERNAL = LONG_RELATIVE, 	!
		NONEXTERNAL = LONG_RELATIVE) ,
%FI

		IDENT = '07'
		) =
BEGIN

!++
! Facility:
!	Lexical scanner for BLISS formatter
!
! Abstract:
!
!	This module reads a source file of BLISS symbols and
!	returns each one on demand. The tokens are returned in
!	a global block called "token". The tokens recognized
!	are defined in the required file 'TOKTYP.BLI'.
!	In the process of scanning the input file, the scanner
!	may at times direct its attention to alternative input
!	streams: either to a require file of control comment lines,
!	or (in the case of the SYNONYM control line) to a point
!	internal to an input line. To do this, the scanner
!	maintains a multi-level context which can be switched as
!	required. The context, when switched, is saved in a stack
!	whose pointer is named "stk". Routines SCN$PUSH and SCN$POP
!	handle the switching.
!
! Environment:
!	BLISS Formatter ("PRETTY")
!
! Modifications:
!
! 01-04	-Numerous bug fixes and added facilities.
! 05	-Multiple contexts added, to implement SYNONYM control.
! 06	-support multiple operating systems' command lines
! 07	-Use XPORT I/O and better command lines
!--

!<Blf/page>
!
! Table of contents:
!--

FORWARD ROUTINE
    nxch,					! Next character from input stream
    readaline,					! Reads records of pure text
    scn$fin_verb : NOVALUE,			!
    scn$getsym : NOVALUE,			! Central routine to get next symbol
    scn$init,					! Initialization routine
    scn$mbstrt : NOVALUE,			! Macro bodies are left unformatted
    scn$mfin : NOVALUE,				!
    scn$plit : NOVALUE,				! alter plit count
    scn$pop : NOVALUE,
    scn$push : NOVALUE,
    scn$set_in_unit : NOVALUE,
    scn$strt_verb : NOVALUE,			! User-formatted
    scn$verbatim;				! Logical 'OR' of verbatim flags

!
! Include files:
!--

REQUIRE 'BLFCSW';				!	Defines control switches, i.e. 'sw_...'

REQUIRE 'BLFMAC';				! Defines macros 'lex', 'msg', 'write'

REQUIRE 'BLFIOB';				! defines in_iob, etc.

REQUIRE 'SCNBLK';				! Defines variables pertaining to scanning context

REQUIRE 'TOKTYP';				! Defines 'token' and the token type values 's_...'

REQUIRE 'UTLCOD';				! Defines error codes, i.e. 'er_...'

!
! Macros:
!--

!
! Equated symbols:
!--

LITERAL
    true = 1 EQL 1,
    false = 1 NEQ 1,
    form_feed = 12,
    quote = %C'''',
    eof = -2,
    newline = -1,
    tab_char = 9;

!
! Own storage:
!--

OWN
    all_white,					! True until printable char found
    						! unprocessed character in 'buf'
    exp_verbatim,				! Explicit verbatim flag
    mac_verbatim,				! Implicit verbatim flag used in macro-bodies
    plit_count,					! Count of nested PLITs
    plit_verbatim,				! implicit verbatim flag used in PLIT- bodies
    state,					! State of finite state machine = scanner.
    temp : VECTOR [CH$ALLOCATION (buf_len)];	! Temporary buffer for macros

OWN 						! Variables used in control of PLIT formatting
    line_broken,				! first line of PLIT body written
    set_linebreak;				! Prepared to write first line of plit body

OWN 						! Variables pertaining to scanner input state
    alt_state : BLOCK [scn_blk_size] FIELD (in_field),
    in_state : BLOCK [scn_blk_size] FIELD (in_field),
    inp_iob_addr : INITIAL (in_iob),		! Either in_ or req_ IOB address
    STACK : VECTOR [3],				! for scanner state pointers
    stack_level : INITIAL (0),
    stk : REF BLOCK FIELD (in_field) INITIAL (in_state);

GLOBAL
    token : tok_block;

!
! External references:
!--

EXTERNAL ROUTINE				! In module...
    ctl$switch,					! CONTRL
    lst$line : NOVALUE,				! LSTING
    lst$on,
    out$break : NOVALUE,			! OUTPUT
    out$eject : NOVALUE,
    out$force : NOVALUE,
    out$gag : NOVALUE,
    out$ntbreak : NOVALUE,
    out$on,
    out$remark : NOVALUE,
    out$tok : NOVALUE,
    utl$error : NOVALUE;			! UTILIT
ROUTINE nxch = 					!

!++
! Functional description:
!
!	This routine returns each character from the input stream
!	sequentially. One 'newline' pseudo character is returned
!	between records.
!
! Formal parameters:
!
!	None
!
! Implicit inputs:
!
!	Current scanner state block and its pointer, sp
!
! Implicit outputs:
!
!	New scanner state.
!
! Routine value:
!
!	The next character from the input stream, or 'newline'
!
! Side effects:
!
!	This routine may trigger a read from the input file
!	Cp is left pointing to the character following the one returned.
!
!--

    BEGIN

    IF .set_linebreak
    THEN
	BEGIN
	! In a PLIT body, the first line is formatted and the rest are
	! left alone until the closing ')'. The breakoff of the first
	! line is done here when the end-of-line has already been parsed.
	! First make sure we don't lose a remark at this point.
	IF .token [tok_type] EQL remark THEN out$remark ();
	scn$mbstrt (s_plit);			! Break up line & get next
	line_broken = true;
	set_linebreak = false;
	END;

    IF .stk [rem] EQL 0
    THEN 					! All characters in this record
    						! have been returned
	BEGIN					! Return a 'newline' pseudo char
	CH$RCHAR_A (stk [cp]);
	stk [rem] = -1;

	IF .line_broken				!
	THEN
	    line_broken = .plit_verbatim
	ELSE
	    set_linebreak = .plit_count GTR 0;

	RETURN stk [chr] = newline;
	END;					! Return a 'newline' pseudo char

    IF .stk [rem] LEQ -1
    THEN

	IF readaline () EQL -1			! That's all, folks
	THEN
	    RETURN eof
	ELSE
	    IF .plit_count GTR 0
	    THEN
		BEGIN
		out$ntbreak ();			! Make sure the line gets broken
		plit_verbatim = true;
		END;

    IF .stk [len] EQL 0
    THEN
	BEGIN
	CH$RCHAR_A (stk [cp]);
	stk [rem] = -1;

	IF .line_broken THEN line_broken = .plit_verbatim ELSE (set_linebreak = .plit_count GTR 0);

	RETURN stk [chr] = newline;
	END
    ELSE
	BEGIN

	IF (stk [chr] = CH$RCHAR_A (stk [cp])) EQL tab_char
	THEN
	    stk [col] = ((.stk [col] + 7)/8)*8 + 1
	ELSE stk [col] = .stk [col] + 1;

	stk [rem] = .stk [rem] - 1;
	RETURN .stk [chr];
	END;

    END;					! End of routine 'nxch'
ROUTINE readaline = 				!

!++
! Functional description:
!
!	This routine reads the next record from the input file.
!
! Formal parameters:
!
!	None
!
! Implicit inputs:
!
!	None
!
! Implicit outputs:
!
!	Len - the length of the input record in characters
!	Cp - a character pointer to the first character
!	rem - the number of chars remaining in the line
!
! Routine value:
!
!	-1 On end of file, else 0
!
! Side effects:
!
!	When EOF occurs on "req_iob", input from "in_iob" is resumed.
!
!--

    BEGIN

    MAP
	inp_iob_addr : REF $xpo_iob ();

    IF scn$verbatim ()				! Nobody else is printing
    THEN

	IF CH$NEQ (9, .token [tok_cp], 		!
		9, CH$PTR (UPLIT ('!!ERROR!!')))
	THEN
	    BEGIN

	    IF out$on ()
	    THEN
		! ...............................................
		$xpo_put (			! Here is where lines of text
		    string = (.stk [len], CH$PTR (stk [buf])), 	! are written
		    iob = out_iob);		! in verbatim mode.
		! ...............................................

	    IF lst$on () THEN lst$line (.stk [len], CH$PTR (stk [buf]));

	    END;

    ! ...........................................
    $xpo_get (					! Here is where lines
	iob = .inp_iob_addr);			! of text are read in.
    ! ...........................................
    stk [len] = .inp_iob_addr [iob$h_string];	! Note the line length
    CH$MOVE (.stk [len], 			! Move the line into stack buffer
	.inp_iob_addr [iob$a_string], 		!
	stk [cp] = CH$PTR (stk [buf]));

    IF .inp_iob_addr [iob$v_eof]		! check for end-of-file
    THEN
	RETURN -1;

    stk [rem] = .stk [len];
    stk [col] = 0;
    all_white = true;				! Assume line is whitespace.
    RETURN 0;
    END;					! End of routine 'readaline'
GLOBAL ROUTINE scn$fin_verb : NOVALUE = 	!

!++
! Functional description:
!
!	This routine is called when either
!	a) a !<BLF/format> is found in the input text
!	b) a !<Blf/page> is found in the input text.
! Formal parameters:
!
!	None
!
! Implicit inputs:
!
!	None
!
! Implicit outputs:
!
!	None
!
! Routine value:
!
!	None
!
! Side effects:
!
!	None
!
!--

    BEGIN
    exp_verbatim = false;
    END;					! End of routine 'scn$fin_verb'
GLOBAL ROUTINE scn$getsym (in_file) : NOVALUE = 	!

!++
! Functional description:
!
!	This routine is called to return the next symbol from
!	the input stream in the global block 'token'.
!	The plan is to simulate a finite state machine (FSM).
!	The outermost loop controls state transitions.
!	State 0 is the initial state and is memoryless. It is
!	called whenever a token is desired with no memory
!	of the tokens which preceeded it, for example, it is
!	not called when we know we are in the middle of a block
!	comment. The convention for reading characters is that
!	each state assumes 'stk [chr]' contains the first unprocessed character.
!	A token string may end in a newline. Since a character must
!	be read after recognition, this will overwrite the buffer
!	containing the token string just recognized. Therefore,
!	the token string is moved into an auxilliary buffer if
!	it has been recognized by hitting the newline.
!
!	State transitions:
!
!	0- 1,3,5,6,7
!	1- 2,4
!	2- 2,0
!	3- 4
!	4- 0
!	5- 0
!	6- 0
!	7- 0
!
! Formal parameters:
!
!	None
!
! Implicit inputs:
!
!	None
!
! Implicit outputs:
!
!	None
!
! Routine value:
!
!	None
!
! Side effects:
!
!	None
!
!--

!<Blf/page>
    BEGIN

    LABEL
	loop;

    BIND
	ctrl_z = 26;				! ASCII code for control-z

    OWN
	end_com_pending : INITIAL (false),	! True after !+, false until then or after !-
	tok_buf : VECTOR [CH$ALLOCATION (buf_len)];	! Auxilliary buffer to prevent overwrite

loop :
    BEGIN

    WHILE 1 DO
	BEGIN					! FSM dispatch

	CASE .state FROM 0 TO 7 OF
	    SET

	    [0] :
		BEGIN				! State 0

		!+
		! State 0 is the state that dispatches to all
		! other states. It decides, based on the
		! the first character it sees what kind of
		! lexeme to attempt to recognize.
		!-

		WHILE .stk [chr] LSS %C'!' OR 	!
		    .stk [chr] GTR %O'175' DO 	! All nonprintable characters
		    BEGIN

		    IF .stk [chr] EQL eof OR .stk [chr] EQL ctrl_z
		    THEN

			IF .inp_iob_addr NEQ in_iob
			THEN
			    BEGIN
			    $xpo_close (	!
				iob = .inp_iob_addr);	! Close require file
			    scn$set_in_unit (in_iob);	! switch back to main file
			    END
			ELSE
			    BEGIN
			    token [tok_len] = 0;
			    token [tok_type] = s_end_of_file;
			    LEAVE loop;
			    END

		    ELSE

			IF .stk [chr] EQL newline
			THEN
			    BEGIN
			    token [tok_type] = s_newline;

			    IF .in_file THEN stk [chr] = nxch ();

			    LEAVE loop;
			    END
			ELSE

			    IF .stk [chr] EQL form_feed
			    THEN
				BEGIN
				token [tok_type] = s_newpage;
				stk [chr] = nxch ();
				LEAVE loop;
				END;

		    stk [chr] = nxch ();
		    END;

		token [tok_cp] = CH$PLUS (.stk [cp], -1);

		SELECTONE .stk [chr] OF
		    SET

		    [%C'%'] :
			state = 1;

		    [%C'A' TO %C'Z', %C'a' TO %C'z', %C'$', %C'_'] :
			state = 3;

		    [%C'0' TO %C'9'] :
			state = 5;

		    [%C'!'] :
			state = 6;

		    [quote] :
			state = 7;
!<Blf/page>
		    [OTHERWISE] :
			BEGIN			! Delimiter or error

			LOCAL
			    type;

			CASE .stk [chr] FROM %C'(' TO %C'^' OF
			    SET

			    [%C'('] :
				type = s_lparen;

			    [%C')'] :
				type = s_rparen;

			    [%C'*'] :
				type = s_multiply;

			    [%C'+'] :
				type = s_plus;

			    [%C'-'] :
				type = s_minus;

			    [%C','] :
				type = s_comma;

			    [%C'.'] :
				type = s_dot;

			    [%C'/'] :
				type = s_divide;

			    [%C':'] :
				type = s_colon;

			    [%C';'] :
				type = s_semicolon;

			    [%C'<'] :
				type = s_langle;

			    [%C'='] :
				type = s_equal;

			    [%C'>'] :
				type = s_rangle;

			    [%C'['] :
				type = s_lbracket;

			    [%C']'] :
				type = s_rbracket;

			    [%C'^'] :
				type = s_circumflex;

			    [INRANGE] :
				type = 0;

			    [OUTRANGE] :
				type = 0;
			    TES;

			stk [chr] = nxch ();

			IF .type NEQ 0
			THEN
			    BEGIN
			    token [tok_len] = 1;
			    token [tok_type] = .type;
			    LEAVE loop;
			    END
			ELSE
			    utl$error (er_ill_sym);

			END;			! Delimiter or error
		    TES;

		END;				! State 0
!<Blf/page>
	    [1] :

		!+
		! State 1 has seen %. Either it starts an embedded
		! comment, a name, or is the % token.
		!_

		BEGIN
		stk [chr] = nxch ();

		IF .stk [chr] EQL %C'('
		THEN
		    BEGIN

		    !+
		    ! Start of an embedded comment
		    !-

		    state = 2;
		    token [tok_type] = start_embedded;
		    token [tok_len] = 2;
		    stk [chr] = nxch ();
		    LEAVE loop;
		    END
		ELSE

		    IF .stk [chr] GEQ %C'A' AND .stk [chr] LEQ %C'Z'	!
			OR .stk [chr] GEQ %C'a' AND .stk [chr] LEQ %C'z'	!
		    THEN
			state = 4
		    ELSE
			BEGIN
			token [tok_type] = s_percent;
			state = 0;
			token [tok_len] = 1;
			LEAVE loop;
			END;

		END;
!<Blf/page>
	    [2] :

		!+
		! State 2 has seen %(. It must find either a )% to
		! end the embedded comment, or a newline to end
		! this piece of it. If it finds the newline, it must
		! continue to scan for the )%.
		!-

		BEGIN

		LOCAL
		    last;

		!+
		! Scan for ")%" or newline
		!-

		last = %C'(';
		token [tok_cp] = CH$PLUS (.stk [cp], -1);	! Mark start of the field

		WHILE .stk [chr] NEQ eof DO
		    BEGIN

		    UNTIL .stk [chr] EQL %C'%' OR .stk [chr] EQL newline DO
			BEGIN
			last = .stk [chr];
			stk [chr] = nxch ();
			END;

		    IF .stk [chr] EQL %C'%' AND .last EQL %C')'
		    THEN
			BEGIN
			state = 0;
			token [tok_len] = CH$DIFF (.stk [cp], .token [tok_cp]);
			token [tok_type] = end_embedded;
			stk [chr] = nxch ();
			LEAVE loop;
			END
		    ELSE

			IF .stk [chr] EQL newline
			THEN
			    BEGIN
			    token [tok_type] = mid_embedded;
			    token [tok_len] = MAX (0, CH$DIFF (.stk [cp], .token [tok_cp]) - 1);
			    CH$FILL (%C' ', buf_len, CH$PTR (tok_buf));
			    CH$MOVE (.token [tok_len], .token [tok_cp], CH$PTR (tok_buf));
			    token [tok_cp] = CH$PTR (tok_buf);
			    stk [chr] = nxch ();
			    LEAVE loop;
			    END
			ELSE
			    stk [chr] = nxch ();	! Try next character

		    END;			! Of block in 'DO'

		END;				! Scan for ")%" or newline
!<Blf/page>
	    [3] :

		!+
		! State 3 has seen a character that can start
		! a name.
		!-

		BEGIN
		stk [chr] = nxch ();
		state = 4;
		END;

	    [4] :

		!+
		! State 4 is invoked to finish a name
		!-

		BEGIN

		WHILE .stk [chr] GEQ %C'A' AND .stk [chr] LEQ %C'Z'	!
		    OR .stk [chr] GEQ %C'a' AND .stk [chr] LEQ %C'z'	!
		    OR .stk [chr] GEQ %C'0' AND .stk [chr] LEQ %C'9'	!
		    OR .stk [chr] EQL %C'_'	!
		    OR .stk [chr] EQL %C'$'	!
		DO
		    stk [chr] = nxch ();

		token [tok_type] = s_name;
		token [tok_len] = MAX (0, CH$DIFF (.stk [cp], .token [tok_cp]) - 1);
		state = 0;
		LEAVE loop;
		END;				! State 4

	    [5] :

		!+
		! State 5 is invoked to finish a numeric literal
		!-

		BEGIN

		DO
		    stk [chr] = nxch ()
		WHILE .stk [chr] GEQ %C'0' AND .stk [chr] LEQ %C'9';

		state = 0;
		token [tok_len] = MAX (0, CH$DIFF (.stk [cp], .token [tok_cp]) - 1);
		token [tok_type] = s_numeric;
		LEAVE loop;
		END;				! State 5
!<Blf/page>
	    [6] :

		!+
		! State 6 is invoked to finish a comment
		!-

		BEGIN

		LOCAL
		    lcp,			! Local character pointer
		    comment_kind;

		state = 0;
		lcp = .token [tok_cp];

		IF (.stk [col] EQL 1) OR 	!
		    ((.stk [col] EQL 2) AND 	!
		    (CH$RCHAR (CH$PTR (stk [buf])) EQL form_feed))	! Comment starts in col 1
		THEN
		    comment_kind = full_line_com
		ELSE

		    IF .all_white
		    THEN
			BEGIN

			!+
			! Block comment or remark
			!-

			SELECTONE CH$RCHAR (CH$PLUS (.lcp, 1)) OF
			    SET

			    [%C'+'] :
				BEGIN
				end_com_pending = true;
				comment_kind = start_block_com;
				END;

			    [%C'-', %C'_'] :
				BEGIN
				end_com_pending = false;
				comment_kind = end_block_com;
				END;

			    [%C'.'] : 		! Always a remark
				comment_kind = remark;

			    [OTHERWISE] : 	! Nondescript

				IF .end_com_pending
				THEN
				    comment_kind = mid_block_com
				ELSE
				    BEGIN

				    !+
				    ! Guess whether remark or block comment
				    !-

				    LOCAL
					rem_col;

				    rem_col = ctl$switch (sw_rem_tabs)*8 + 1;

				    IF .rem_col - 16 LEQ .stk [col]	!
				    THEN
					comment_kind = remark
				    ELSE
					comment_kind = mid_block_com;

				    END;

			    TES;

			END
		    ELSE
			comment_kind = remark;

		DO
		    stk [chr] = nxch ()
		WHILE .stk [chr] NEQ newline;

		token [tok_len] = MAX (0, CH$DIFF (.stk [cp], .token [tok_cp]) - 1);
		token [tok_type] = .comment_kind;
		CH$FILL (%C' ', buf_len, CH$PTR (tok_buf));
		CH$MOVE (.token [tok_len], .token [tok_cp], CH$PTR (tok_buf));
		token [tok_cp] = CH$PTR (tok_buf);
		stk [chr] = nxch ();		! Triggers a read
		LEAVE loop;
		END;				! State 6
!<Blf/page>
	    [7] :

		!+
		! State 7 is invoked to finish a string
		!-

		BEGIN

		LOCAL
		    lstch;			! last char read

		!+
		! Find the end of a string. Ignore paired quotes found.
		!-

		lstch = .stk [chr];

		WHILE .stk [chr] NEQ eof DO
		    BEGIN

		    SELECTONE .stk [chr] OF
			SET

			[quote] :

			    IF .lstch EQL quote
			    THEN
				lstch = 0
			    ELSE
				BEGIN
				lstch = .stk [chr];
				token [tok_len] = MAX (0, CH$DIFF (.stk [cp], .token [tok_cp]));
				END;

			[newline] :
			    BEGIN

			    IF .lstch NEQ quote THEN utl$error (er_quote);

			    EXITLOOP;
			    END;

			[OTHERWISE] :
			    BEGIN

			    IF .lstch EQL quote THEN EXITLOOP;

			    lstch = .stk [chr];
			    END;
			TES;

		    stk [chr] = nxch ();
		    END;

		CH$MOVE (.token [tok_len], .token [tok_cp], CH$PTR (tok_buf));
		token [tok_cp] = CH$PTR (tok_buf);
		state = 0;
		token [tok_type] = s_string;
		LEAVE loop;
		END				! State 7
	    TES;

	END;					! FSM dispatch

    END;					! Loop
    all_white = .stk [col] LEQ 1;
    END;					! End of routine 'scn$getsym'
GLOBAL ROUTINE scn$init = 			!

!++
! Functional description:
!
!	This routine initializes the scanner
!
! Formal parameters:
!
!	None
!
! Implicit inputs:
!
!	None
!
! Implicit outputs:
!
!	None
!
! Routine value:
!
!	True for success, false for failure
!
! Side effects:
!
!	None
!
!--

    BEGIN
    stk [cp] = CH$PTR (stk [buf]);		! Set up char. pointer
    stk [len] = buf_len;
    mac_verbatim = plit_verbatim = exp_verbatim = false;	! Set formatting mode to automatic
    plit_count = 0;
    set_linebreak = line_broken = false;

    IF readaline () EQL -1 THEN RETURN false;	! Empty file

    !+
    !	Set internal state of the scanner
    !	To start looking for the first lexeme
    !-

    stk [chr] = nxch ();
    state = 0;
    RETURN true;
    END;					! End of routine 'scn$init'
GLOBAL ROUTINE scn$mbstrt (type) : NOVALUE =

!++
! Functional description:
!
!	This routine begins the non-formatted processing of a macro body
!	or a PLIT-body.
!	It is called when the preceding "=" has been found in the macro
!	definition. The rest of the line on which the "=" occurs is
!	treated as if it were a complete line in itself, to be sure
!	of finding the terminating "%" at the right time. To do this,
!	the rest of the line is overlaid onto the text already
!	processed and the buffer pointers and lengths recomputed.
!	Then all subsequent lines (including the present one) up to the
!	"%" are simply copied to the output file by "readaline" before
!	the next line is read in.
!	The final line of the macro- body is split after the
!	"%" is found in the routine which calls "scn$mfin"
!	(i.e. "do_macro" or "do_kwmacro".)
!
! Formal parameters:
!
!	None
!
! Implicit inputs:
!
!	"buf" contains the input text line; "len" is its length;
!	"cp" and "endbuf" are current and final pointers into "buf".
!
! Implicit outputs:
!
!	The implicit inputs are reformatted and recomputed as
!	described above.
!
! Routine value:
!
!	None
!
! Side effects:
!
!	None
!
!--

    BEGIN

    IF NOT scn$verbatim ()
    THEN
	BEGIN
	out$break ();				! Start macro-body on new line

	!+
	! Overlay the rest of the line of text on itself.
	! If empty, get the next line.
	!-

	IF .stk [rem] LSS 0
	THEN
	    readaline ()			! Get another line
	ELSE
	    BEGIN
	    ! Split the current line after the '='.
	    stk [cp] = CH$PLUS (.stk [cp], -1);	! Move cp left of next char
	    stk [len] = .stk [rem] + 1;
	    CH$MOVE (.stk [len], .stk [cp], CH$PTR (temp));	!
	    stk [cp] = CH$PTR (stk [buf]);
	    CH$MOVE (.stk [len], CH$PTR (temp), .stk [cp]);
	    stk [cp] = CH$PLUS (.stk [cp], 1);	! Restore cp relative position
	    END;

	END;

    IF .type EQL s_macro THEN mac_verbatim = true;

    IF .type EQL s_plit THEN plit_verbatim = true;

    END;					! End of routine 'scn$mbstrt'
GLOBAL ROUTINE scn$mfin (type) : NOVALUE = 	!

!++
! Functional description:
!
!	This routine is called when the "%" is found in the context
!	of a macro-body. Implicit non-formatting of the text is terminated.
!
!
!
! Formal parameters:
!
!	None
!
! Implicit inputs:
!
!	None
!
! Implicit outputs:
!
!	None
!
! Routine value:
!
!	None
!
! Side effects:
!
!	None
!
!--

    BEGIN
    out$ntbreak ();				! This call only resets pointers because
    						! we are still in verbatim mode.

    IF .type EQL s_macro THEN mac_verbatim = false;

    IF .type EQL s_plit THEN plit_verbatim = false;

    IF NOT scn$verbatim ()
    THEN
	BEGIN
	token [tok_len] = MAX (0, CH$DIFF (.stk [cp], CH$PTR (stk [buf])) - 1);	! Treat this line up to the %
	token [tok_cp] = CH$PTR (stk [buf]);	! as a single token
	out$tok ();				! and output it.
	token [tok_len] = 0;			! don't put it out twice
	END;

    END;					! End of routine 'scn$mfin'
GLOBAL ROUTINE scn$plit (n) : NOVALUE =

!++
! Functional description:
!
!	This routine alters the count of PLITs as they are entered
!	and exited, to help control non-formatting of PLIT bodies.
!
! Formal parameters:
!
!	n = + or - 1, as a plit is entered or exited.
!
! Implicit inputs:
!
!	None
!
! Implicit outputs:
!
!	None
!
! Routine value:
!
!	None
!
! Side effects:
!
!	None
!
!--

    BEGIN
    plit_count = .plit_count + .n;
    ! Turn off plit_verbatim flag whenever count goes to zero.
    ! Turn on plit_verbatim at another point (at end of line).

    IF .plit_count EQL 0 AND .line_broken
    THEN
	BEGIN
	scn$mfin (s_plit);
	line_broken = false;
	END;

    ! If we are at the end of a line it's time to break it now.
    set_linebreak = .stk [rem] LSS 0 AND .n GEQ 0 AND .plit_count GTR 0;
    END;					! End of routine 'scn$plit'
GLOBAL ROUTINE scn$pop : NOVALUE = 		!

!++
! Functional description:
!
!	This routine restores the scanning context to its previous
!	state, at the point of the most recent call to scn$push.
!
! Formal parameters:
!
!	None
!
! Implicit inputs:
!
!	None
!
! Implicit outputs:
!
!	None
!
! Routine value:
!
!	None
!
! Side effects:
!
!	None
!
!--

    BEGIN
    stk = .STACK [.stack_level];

    IF .stack_level GTR 0 THEN stack_level = .stack_level - 1;

    END;					! End of routine 'scn$pop'
GLOBAL ROUTINE scn$push (arg) : NOVALUE = 	!

!++
! Functional description:
!
!	This routine provides a push-down stack for the pointers
!	to scanner state blocks. It is only three levels deep,
!	corresponding to the three possible sources of input:
!	1) primary input file, 2) require file, 3) within a
!	SYNONYM definition appearing in either of the above files.
!
! Formal parameters:
!
!	arg = a new state pointer
!
! Implicit inputs:
!
!	stk = the current state pointer
!
! Implicit outputs:
!
!	The new state pointer = arg
!
! Routine value:
!
!	None
!
! Side effects:
!
!	None
!
!--

    BEGIN
    stack_level = .stack_level + 1;
    STACK [.stack_level] = .stk;
    stk = .arg;
    END;					! End of routine 'scn$push'
GLOBAL ROUTINE scn$set_in_unit (arg) : NOVALUE =

!++
! Functional description:
!
!	This routine permits control to direct the input stream from
!	the main file to a REQUIRE file, for the purpose of reading
!	further control directives. When the end of this file is
!	found, the unit is switched back by READALINE.
!	The current character and the input line are saved and restored
!	As the unit switches from one source to the other.
!
! Formal parameters:
!
!	arg = the unit number.
!
! Implicit inputs:
!
!	None
!
! Implicit outputs:
!
!	None
!
! Routine value:
!
!	None
!
! Side effects:
!
!	None
!
!--

    BEGIN

    MAP
	inp_iob_addr : REF $xpo_iob ();

    IF .inp_iob_addr EQL req_iob AND .arg EQL req_iob
    THEN 					! Attempt to stack REQUIRES
	utl$error (er_file_spec);

    IF (inp_iob_addr = .arg) EQL req_iob
    THEN
	BEGIN
	! Back up one character to resume after alt. end-of-file
	! (Cf. Scn$getsym state 0 EOF handling.)
	stk [cp] = CH$PLUS (.stk [cp], -1);
	stk [col] = .stk [col] - 1;
	stk [rem] = .stk [rem] + 1;
	scn$push (alt_state);
	out$gag (true);				! Prevent file from being output
	END
    ELSE
	BEGIN
	scn$pop ();
	out$gag (false);
	END;

    END;					! End of routine 'scn$set_in_unit'
GLOBAL ROUTINE scn$strt_verb : NOVALUE = 	!

!++
! Functional description:
!
!	This routine is called from the scanner when a directive to
!	begin manual formatting is found.
!	Since the directive is a full-line comment, no action
!	to speak of is required here.
! Formal parameters:
!
!	None
!
! Implicit inputs:
!
!	The comment !<BLF/noformat>
!	has appeared in the input stream.
!
! Implicit outputs:
!
!	None
!
! Routine value:
!
!	None
!
! Side effects:
!
!	None
!
!--

    BEGIN
    exp_verbatim = true;
    END;					! End of routine 'scn$strt_verb'
GLOBAL ROUTINE scn$verbatim = 			!

!++
! Functional description:
!
!	This functional routine returns the "or" of the two
!	verbatim (non-formatting) flags, thus is true if
!	formatting has been suppressed.
! Formal parameters:
!
!	None
!
! Implicit inputs:
!
!	None
!
! Implicit outputs:
!
!	None
!
! Routine value:
!    1 or 0
!
!
!	None
!
! Side effects:
!
!	None
!
!--

    BEGIN
    RETURN .mac_verbatim OR .exp_verbatim OR .plit_verbatim;
    END;					! End of routine 'scn$verbatim'
%SBTTL 'Final page of SCANNR.BLI'
END						! End of module 'scannr'

ELUDOM