SMOLNET PORTAL home about changes
MODULE Entab;
  IMPORT In, Out, Tabs;

CONST
  NEWLINE = 10;
  TAB = 9;
  BLANK = 32;

PROCEDURE Entab();
VAR
  c : CHAR;
  col, newcol : INTEGER;
  tabstops : Tabs.TabType;
BEGIN
  Tabs.SetTabs(tabstops);
  col := 1;
  REPEAT
    newcol := col;
    In.Char(c);
    IF In.Done THEN (* NOTE: We check that the read was successful! *)
      WHILE (ORD(c) = BLANK) DO
        newcol := newcol + 1;
        IF (Tabs.TabPos(newcol, tabstops)) THEN
          Out.Char(CHR(TAB));
          col := newcol;
        END;
        (* NOTE: Get the next char, check the loop condition
           and either iterate or exit the loop *)
        In.Char(c);
      END;
      WHILE (col < newcol) DO
        Out.Char(CHR(BLANK)); (* output left over blanks *)
        col := col + 1;
      END;
      (* NOTE: Since we may have gotten a new char in the first WHILE
         we need to check again if the read was successful *)
      IF In.Done THEN
        Out.Char(c);
        IF (ORD(c) = NEWLINE) THEN
          col := 1;
        ELSE
          col := col + 1;
        END;
      END;
    END;
  UNTIL In.Done # TRUE;
END Entab;

BEGIN
  Entab();
END Entab.

2.1 Putting Tabs Back
=======================================================

[Page 31](https://archive.org/stream/softwaretoolsinp00kern?ref=ol#page/31/mode/1up)

Implementing **entab** in Oberon-7 is straight forward.
Like my [Detab](Detab.Mod) implementation I am using
a second modules called [Tabs](Tabs.Mod). This removes
the need for the `#include` macros used in the K & P version.
I have used the same loop structure as K & P this time.
The difference in my `WHILE` loops though is separating the
character read from the `WHILE` conditional test.  This is
something that is common in "C" which can cause issues and
in Oberon-7 doesn't make sense at all. Oberon's `In.Char()`
is not a function returning a value C, it is a procedural
call setting C and exposing state via `In.Done`. I've
chosen to put the next call to `In.Char()` at the bottom
of my `WHILE` loop because it is clear that it is the last
think done before ether iterating again or exiting the loop.
Other than that the Oberon version looks much like the Pascal.

Program Documentation
---------------------

[Page 32](https://archive.org/stream/softwaretoolsinp00kern?ref=ol#page/32/mode/1up)

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

PROGRAM

  entab	convert runs of blanks into tabs

USAGE

  entab

FUNCTION

  entab copies its input to its output, replacing strings of
  blanks by tabs so the output is visually the same as the
  input, but contains fewer characters. Tab stops are assumed
  to be set every four columns (i.e. 1, 5, 9, ...), so that
  each sequence of one to four blanks ending on a tab stop
  is replaced by a tab character

EXAMPLE

  Using -> as visible tab:

    entab
      col  1   2   34  rest
    ->col->1->2->34->rest

BUGS

  entab is naive about backspaces, vertical motions, and
  non-printing characters. entab will convert  a single blank
  to a tab if it occurs at a tab stop. The entab is not an
  exact inverse of detab.

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Pascal Source
-------------

[Page 33](https://archive.org/stream/softwaretoolsinp00kern?ref=ol#page/33/mode/1up)

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

{-- entab -- replace blanks by tabs and blanks }
program entab (input, output);
const
  ENDFILE = -1;
  NEWLINE = 10; { ASCII value }
  BLANK = 32; { ASCII value }
type
  character = -1..127 { ASCII, plus ENDFILE }

{ getc -- get one character from standard input }
function getc(var c : character) : character;
var
  ch : char;
begin
  if (eof) then
    c := ENDFILE
  else if (eoln) then begin
    readln;
    c := NEWLINE;
  end
  else begin
    read(ch);
    c := ord(ch)
  end;
  getc := c;
end;

{ putc -- put one character on the standard output }
procedure putc (c : character);
begin
  if (c = NEWLINE) then
    writeln;
  else
    write(chr(c))
end;

prodecure entab;
const
  MAXLINE = 1000; { or whatever }
type
  tabtype = array [1..MAXLINE] of boolean;
var
  c : character;
  col, newcol : integer;
  tabstops : tabtype;
#include "tabpos.p"
#include "settabs.p"
begin
  settabs(tabstops);
  col := 1;
  repeat
    newcol := col;
    while (getc(c) = BLANK) do begin
      newcol := newcol + 1;
      if (tabpos(newcol, tabstops)) then begin
        putc(TAB);
        col := newcol
      end
    end;
    while (col < newcol) do begin
      putc(BLANK);	{output leftover blanks }
      col := col + 1
    end;
    if (c <> ENDFILE) then begin
      putc(c);
      if (c = NEWLINE) then
        col := 1
      else
        col := col + 1
    end
  until (c = ENDFILE)
end;

begin { main program }
  entab;
end.

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Supporting Source
-----------------

[Page 25](https://archive.org/stream/softwaretoolsinp00kern?ref=ol#page/25/mode/1up)


tabpos.p, included by detab.pas
-------------------------------

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

{ tabpos -- return true if col is a tab stop }
function tabpos (col: integer; var tabstops : tabtype) : boolean;
begin
    if (col > NEWLINE) then
        tabpos := true;
    else
        tabpos := tabstops[col]
end;

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

settabs.p, included by detab.pas
--------------------------------

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

{ settabs -- set initial tab stops }
procedure settabs (var tabstops: tabtype);
const
    TABSPACE = 4; { 4 spaces per tab }
var
    i : integer;
begin
    for i := 1 to MAXLINE do
        tabstops[i] := (i mod TABSPACE = 1)
end;

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Response: text/plain
Original URLgopher://sdf.org/0/users/rsdoiel/blog/2020/10/31/Entab.Mod
Content-Typetext/plain; charset=utf-8