{*--------------------------------------*
 | Pasmat Comment Formatting Procedures |
 *--------------------------------------*}
{$K0}  {$K7}  {$K12}  {$K13}  {$K14}  {$K15}
module comment;
 {$L-}
 {$I PMDEFS.INC}
 {$L+}

  external procedure abort(line: integer;
			   kind: abortkind);
  external procedure comentoverflow;
  external procedure getchar;
  external procedure getfiles;
  external procedure indentplus(delta, line: integer);
  external procedure printline(indent: integer);
  external procedure space(n: integer);
  external procedure symbolput(thischar: char);
  external procedure undent;
  external procedure writea(ch: char);

{$p------------------------*
 | Block Comment Character |
 *-------------------------*}


  procedure blkcomchar(character: char);

    begin  {Write a character for a block comment. The comment
	    formatting must be terminated with a call to
	    adjustblkcoment. The comment is copied exactly, and if it
	    will not fit within the outlinelen a message will be
	    printed.}
      if endfile then
	abort(linenumber, syntax);
      if formatting then
	if newinputline then
	  begin
	  if writecol > outlinelen then
	    comentoverflow;
	  printline(column);
	  firstinputline := false;
	  newinputline := false;
	  end
	else
	  writea(character);
    end;  {blkcomchar}
{$p----------------------------*
 | Statement Comment Character |
 *-----------------------------*}


  procedure breakstatcoment;

    var
      extralen: integer;  {length from last break}
      comindent: integer;  {amount to indent the extra}

    begin  {Break a statement comment at the last break. Assumes
	    (statbreak <> 0) and (charcount - statbreak < bufsize)}
      extralen := charcount - statbreak + 1;
      if writecol - extralen > maxlinelen then
	abort(linenumber, comformat)
      else
	begin  {we can at least write it}
	if writecol - extralen > outlinelen then
	  comentoverflow;
	comindent := outlinelen - extralen;
	if comindent < 0 then
	  comindent := 0
	else if comindent > indent then
	  comindent := indent;
	with unwritten[statbreak mod bufsize] do
	  begin
	  actionis := beginline;
	  spacing := comindent;
	  end;
	currentline := currentline + 1;
	writecol := comindent + extralen;
	end;
    end;  {breakstatcoment}


  procedure statcomchar(character: char);

    begin  {Take a statement character and format it. assumes that
	    statbreak and statblank are initialized before the first
	    character and are unchanged thereafter. The procedure
	    adjuststatcoment must be called after the comment is done}
      if endfile then
	abort(linenumber, syntax);
      if formatting then
	if (character = ' ') or (character = chr(tab)) then
	  begin
	  if not statblanks then
	    begin
	    if (writecol > outlinelen) and (statbreak <> 0) then
	      breakstatcoment;
	    writea(' ');
	    statbreak := charcount;
	    statblanks := true;
	    end;
	  end
	else
	  begin
	  writea(character);
	  statblanks := false;
	  end;
    end;  {statcomchar}
{$p-----------------------*
 | Do compiler directives |
 *------------------------*}


  procedure docompilerdirectives(block: boolean);

    begin  {scan off compiler directives}
      while (ch <> '[') and (ch <> '*') and (ch <> '}') do
	begin
	if block then
	  blkcomchar(ch)
	else
	  statcomchar(ch);
	getchar;
	end;
    end;  {docompilerdirectives}
{$p----------------------*
 | doformatterdirectives |
 *-----------------------*}


  procedure doformatterdirectives(block: boolean  {block comment} ;
				  cline: boolean  {control line} );

    var
      optchar: char;  {which option specified}


    procedure copyachar;

      begin  {copy a character and get a new one}
	if cline then
	  begin
	  if length(clinearg) = 0 then
	    ch := '}'
	  else
	    begin
	    ch := clinearg[1];
	    delete(clinearg, 1, 1)
	    end
	  end
	else
	  begin
	  if block then
	    blkcomchar(ch)
	  else
	    statcomchar(ch);
	  getchar;
	  end;
      end;  {copyachar}


    procedure switchdir(var switch: boolean);

      begin  {read and set a switch directive, if char is not + or -,
	      the value is unchanged}
	if ch = '+' then
	  begin
	  switch := true;
	  copyachar
	  end
	else if ch = '-' then
	  begin
	  switch := false;
	  copyachar
	  end;
      end;  {switchdir}


    procedure numdir(var value: integer;
		     min, max: integer  {limits} );

      var
	tempval: integer;  {value being accumulated}

      begin  {Read a numeric directive and set value. If the value is
	      out of bounds it is set to the limit value}
	if ch = '=' then
	  copyachar;
	if (ch >= '0') and (ch <= '9') then
	  begin
	  tempval := 0;
	  repeat
	    if tempval <= (maxint - 9) div 10 then
	      tempval := tempval * 10 + (ord(ch) - ord('0'));
	    copyachar;
	  until (ch < '0') or (ch > '9');
	  if tempval < min then
	    tempval := min;
	  if tempval > max then
	    tempval := max;
	  value := tempval;
	  end;
      end;  {numdir}

    begin  {doformatterdirectives: read a formatter directive and set
	    flags and value appropriately}
      copyachar;
      repeat
	if ch in validdirectives then
	  begin
	  optchar := ch;
	  copyachar;
	  case optchar of
	    'b', 'B':
	      switchdir(bunching);
	    'c', 'C':
	      numdir(comentspaces, 0, maxlinelen);
	    'f', 'F':
	      switchdir(newformatting);
	    'l', 'L':
	      switchdir(litcopy);
	    'o', 'O':
	      begin
	      numdir(outlinelen, 1, maxlinelen);
	      onehalfline := outlinelen div 2;
	      fiveeighthline := (5 * outlinelen) div 8;
	      threefourthline := (3 * outlinelen) div 4;
	      end;
	    'p', 'P':
	      switchdir(portabilitymode);
	    'q', 'Q':
	      switchdir(silentmode);
	    'r', 'R':
	      switchdir(ucreswords);
	    's', 'S':
	      numdir(statsperline, 1, maxlinelen);
	    't', 'T':
	      begin
	      numdir(tabspaces, 0, maxlinelen);
	      continuespaces := (tabspaces + 1) div 2;
	      end;
	    'u', 'U':
	      switchdir(ucidents);
	    end;  {case}
	  end
	else if not (ch in [']', '*', '}']) then
	  copyachar;
      until ch in [']', '*', '}'];
      if ch = ']' then
	copyachar;
    end;  {doformatterdirectives}

{$P------------------------*
 | Command Line Directives |
 *-------------------------*}


  procedure commanddirectives;

    begin  {read a command line and process directives}
      getfiles;
      if length(clinearg) > 0 then
	doformatterdirectives(false, true);
    end;  {commanddirectives}

{$P-----------------*
 | Comment Handling |
 *------------------*}


  procedure docoment(block: boolean;  {true if block comment}
		     initcol: lineindex;  {starting column}
		     initchar: char  {starting char} );
 {Handles all comments.
  Comments are split into two classes which are handled separately.
  Comments which begin a line are treated as "block comments" and
  are not formatted.  At most, it will be folded to fit on the
  output line.
  Comments which follow other statements on a line are formatted
  like any other statement.}

{$p---------------------*
 | Adjust Block Comment |
 *----------------------*}


    procedure adjustblkcoment(startcol, startchar: integer);

      var
	comlength: integer;  {length of comment if on one line}
	comindent: integer;  {amount to indent comment}

      begin  {if the comment is all on one line, adjust it to line up
	      with the indentation if possible, otherwise just try to
	      fit it somehow. In any case, if the comment extends
	      beyond the allowable length, bitch about it.}
	if formatting then
	  begin
	  if firstinputline then
	    begin
	    comlength := writecol - startcol;
	    comindent := outlinelen - comlength;
	    if comindent < 0 then
	      comindent := 0
	    else if comindent > statindent then
	      comindent := statindent;
	    unwritten[startchar mod bufsize].spacing := comindent;
	    writecol := comindent + comlength;
	    end;
	  if writecol > outlinelen then
	    comentoverflow;
	  end;  {if formatting}
      end;  {adjustblkcoment}

{$p------------------------*
 | Adjust Statment Comment |
 *-------------------------*}


    procedure adjuststatcoment;

      begin  {called after the last character of a statment comment has
	      been written to ensure that it all fits on a line}
	if formatting then
	  if writecol > outlinelen then
	    if statbreak = 0 then
	      if writecol <= maxlinelen then
		comentoverflow
	      else
		abort(linenumber, comformat)
	    else
	      breakstatcoment;
      end;  {adjuststatcoment}

{$p--------------*
 | Block Comment |
 *---------------*}


    procedure blkcoment;

      var
	comcolstart: integer;  {start of comment}
	comcharstart: integer;  {start of comment in buffer}

      begin  {format a block comment: If the comment is all on one input
	      line it will be indented to the current statement level
	      unless it won't fit, in which case it is shifted left
	      until it will fit. If any part of a block comment will
	      not fit in the output line, the output line will be
	      extended and a message printed.}
	printline(initcol - 1);
	comcolstart := writecol;
	comcharstart := charcount;
	firstinputline := true;
	blkcomchar('{');
	getchar;
	if ch = '$' then
	  docompilerdirectives(true);
	if ch = '[' then
	  doformatterdirectives(true, false);
	if initchar = '{' then
	  while ch <> '}' do
	    begin
	    blkcomchar(ch);
	    getchar
	    end
	else
	  repeat
	    while ch <> '*' do
	      begin
	      blkcomchar(ch);
	      getchar
	      end;
	    getchar;
	    if ch <> ')' then
	      blkcomchar('*');
	  until ch = ')';
	blkcomchar('}');
	adjustblkcoment(comcolstart, comcharstart);
      end;  {blkcoment}

{$p-----------*
 | Statcoment |
 *------------*}


    procedure statcoment;

      begin  {Format a statement comment: These are inserted in the line
	      at the place found, and subsequent lines are indented to
	      the start of the comment. If the start of the comment is
	      too far to the right, it will be indented on the next
	      line. Text will be moved as necessary to fill lines. All
	      breaks will be at blanks, and if it is not possible to
	      break a comment properly the output line will be extended
	      and a message printed}
	{initialize statcomchar}
	statbreak := 0;
	statblanks := false;
	indentplus(writecol + comentspaces + 1 - indent, linenumber);
	if (indent > threefourthline) and (tabspaces <
	   comentspaces) then
	  begin
	  undent;
	  indentplus(tabspaces, linenumber);
	  end;
	if writecol < (outlinelen - comentspaces - 1) then
	  space(comentspaces);
	statcomchar('{');
	getchar;
	if ch = '$' then
	  docompilerdirectives(false);
	if ch = '[' then
	  doformatterdirectives(false, false);
	if initchar = '{' then
	  while ch <> '}' do
	    begin
	    statcomchar(ch);
	    getchar
	    end
	else
	  repeat
	    while ch <> '*' do
	      begin
	      statcomchar(ch);
	      getchar
	      end;
	    getchar;
	    if ch <> ')' then
	      statcomchar('*');
	  until ch = ')';
	statcomchar('}');
	adjuststatcoment;
	undent;
	blankline := false;
      end;  {statcoment}

{$p----------------------*
 | Main Body of Docoment |
 *-----------------------*}

    begin  {docoment}
      newinputline := false;
      if block then
	blkcoment
      else
	statcoment;
      formatting := newformatting;
      newinputline := false;
      getchar;
      while ((ch = ' ') or (ch = chr(tab))) and not newinputline do
	getchar;
      if newinputline then  {start new line if comment is last on line}
	if formatting then
	  begin
	  space(0);
	  writecol := outlinelen;
	  symbolbreak := 0;
	  end  {comment at end of line} ;
      symbolfound := false;
      lastsym := coment;
    end;  {docoment}

{$p-----------------*
 | Start of Comment |
 *------------------*}


  procedure comentchar;

    begin  {possible start of comment}
      if ch = '(' then
	begin  {see if comment or just open paren}
	symbolput('(');
	if ch = '*' then
	  begin
	  symlen := 0;
	  docoment(newinputline, column - 1, ch)
	  end
	else
	  begin
	  newinputline := false;
	  sym := openparen;
	  symbolfound := true
	  end;
	end
      else
	docoment(newinputline, column, ch);
    end;  {comentchar}

modend .
