program PlaMin(input, output);
{
    expr = prod + prod + prod + ... + prod
    prod = term . term . term . ... . term
    term = tag | (expr) | tag' | (expr)'
}
label
  777, 666, 9999, 8888;
type
  variables = (va,vb,vc,vd,ve,vf,vg,vh,vi,vj,
               vk,vl,vm,vn,vo,vp,vq,vr,vs,vt,vu,vv,vw,vx,vy,vz);
  SP = ^data;
  data = record
    vars: set of variables;
    notvars: set of variables;
    next: SP;
  end {record P};
var
  tracing: boolean;
  saved: char;
  hangfire: boolean;
  vlast: variables;
  Expr: SP;
  S: ARRAY [0..15] OF SP;
  where: integer;
  AlwaysTrue, AlwaysFalse: SP;
{--------------------------------------------------------------------------}
procedure PrintS(s: SP);
var
  final, v: variables;
begin
  if s=NIL then WriteLn('<NIL???>')
  else if s=AlwaysTrue then WriteLn('True')
  else if s=AlwaysFalse then WriteLn('False')
  else if (s^.vars=[]) and (s^.notvars=[]) then begin
    Write('True');
  end else begin
    final := vlast;
    while not((final in s^.vars) or (final in s^.notvars)) do begin
      final := PRED(final);
    end {while};
    for v := va to final do begin
      if v in s^.vars then begin
        (*Write('v', ORD(v));*)
        Write(CHR(ORD('a')+ORD(v)));
        if v <> final then Write('.');
      end {if};
      if v in s^.notvars then begin
        (*Write('v', ORD(v), '''');*)
        Write(CHR(ORD('a')+ORD(v)), '''');
        if v <> final then Write('.');
      end {if};
    end {for};
  end {if};
end {PrintS};
procedure PrintSP(s: SP);
begin
  if s=NIL then Write('<NIL???>')
  else if s=AlwaysTrue then Write('True')
  else if s=AlwaysFalse then Write('False')
  else
  while s <> NIL do begin
    PrintS(s);
    if s^.next <> NIL then Write (' + ');
    s := s^.next;
  end {while};
end {PrintSP};
{--------------------------------------------------------------------------}
procedure Concat(var head: SP; tail: SP);
var
  s: SP;
begin
  if head=NIL then head := tail else begin
    s := head;
    while s^.next <> NIL do s := s^.next;
    s^.next := tail;
  end {if};
end {Concat};
function CopyOf(expr: SP): SP;
var
  prod, result: SP;
begin
  if expr=AlwaysTrue then result := AlwaysTrue
  else if expr=Alwaysfalse then result := AlwaysFalse
  else begin
    result := NIL;
    while expr <> NIL do begin
      NEW(prod);
      prod^.vars := expr^.vars;
      prod^.notvars := expr^.notvars;
      prod^.next := result;
      result := prod;
      expr := expr^.next;
    end {while};
  end {if};
  CopyOf := result;
end {CopyOf};
{--------------------------------------------------------------------------}
procedure RemoveDups(var s: SP);
var
  expr1, expr2, set1, set2: SP;
begin
  {------------------------    Cast out duplicates    ------------------------}
  { does it by clearing them to blanks - really ought to tidy up as well... }
  { However, this reduces the complexity somewhat }
  expr1 := s;
  while expr1 <> NIL do begin
    expr2 := expr1;
    set1 := expr1;
    while expr2 <> NIL do begin
      set2 := expr2;
        if (set1<>set2) and (set1^.vars=set2^.vars) and (set1^.notvars=set2^.notvars) then
        begin
          if (set1^.vars <> []) or (set1^.notvars <> []) then begin
            if tracing then begin
              Write('Apply  A + A = A to '); PrintS(set1);
              Write(' + '); PrintS(set2); WriteLn;
            end {tracing};
          end {if};
          set1^.vars := []; set1^.notvars := [];
        end {if};
      expr2 := expr2^.next;
    end {while};
    expr1 := expr1^.next;
  end {while};
end {RemoveDups};
function RemoveComplements(var s: SP): boolean;
label 99;
var
  expr1, expr2: SP;
  v: variables;
  tot: integer;
begin
  {----------------     Apply rule A + A' = AlwaysTrue    -----------------}
  { If any found, entire expression is also always true (a + TRUE + b... }
  expr1 := s;
  while expr1 <> NIL do begin
    { First extract all likely sets }
    tot := 0;
    for v := va to vlast do begin
      if v in expr1^.vars then tot := tot+1;
      if v in expr1^.notvars then tot := tot+1;
    end;
    if tot=1 then begin
      expr2 := expr1;
      while expr2<>NIL do begin
        if (expr2^.vars=expr1^.notvars) and
         (expr1^.vars=expr2^.notvars) then begin
          if tracing then begin
            Write('Apply  A + A'' = True to '); PrintS(expr1);
            Write(' + '); PrintS(expr2);
            WriteLn(' = True');
          end {tracing};
          {DISPOSESP(s)}
          s := AlwaysTrue;
          RemoveComplements := True;
          goto 99
        end {if};
        expr2 := expr2^.next;
      end {while};
    end {if};
    expr1 := expr1^.next;
  end {while};
RemoveComplements := False;
99:
end {RemoveComplements};
procedure Optimise(var s: SP);
label 99;
var
  last, dummy, expr, expr1, expr2, set1, set2: SP;
  intersection: set of variables;
  v: variables;
begin
  if (s=AlwaysTrue) or (s=AlwaysFalse) then goto 99;
  if tracing then begin
    Write('Problem: reduce '); PrintSP(s); WriteLn;
  end {tracing};
  NEW(dummy); dummy^.vars := []; dummy^.notvars := []; dummy^.next := s;
  last := dummy;
  expr := s;
  {--------------     Apply rule X'.X = 0      ---------------------------}
  while expr <> NIL do begin
    if expr^.vars * expr^.notvars <> [] then begin
      last^.next := expr^.next;
      if tracing then begin
        Write('Apply A.A'' = False to '); PrintS(expr); WriteLn;
      end {tracing};
    end else begin
      last := expr;
    end {if};
    expr := last^.next;
  end {while};
  s := dummy^.next; DISPOSE(dummy);
  {----------------      Apply rule P.Q + P.Q' = P     --------------------}
  expr1 := s;
  while expr1 <> NIL do begin
    expr2 := expr1;
    set1 := expr1;
    while expr2 <> NIL do begin
      set2 := expr2;
      if not((set1^.vars=set2^.vars) and
             (set1^.notvars=set2^.notvars)) then begin
        intersection := set2^.notvars*set1^.vars;
        if (set1^.vars-intersection = set2^.vars) and
           (set1^.notvars = set2^.notvars-intersection) and
           (* A + A' !!! ... *) (set1^.vars<>intersection) then begin
          if tracing then begin
            Write('Apply A.B + A.B'' = A to '); PrintS(Set1);
            Write(' + '); PrintS(Set2);
          end {tracing};
          set1^.vars := set1^.vars-intersection;
          set2^.vars := [];
          set2^.notvars := [];
          if tracing then begin
            Write(' = '); PrintS(Set1);
            WriteLn;
          end {tracing};
        end else begin
          intersection := set1^.notvars*set2^.vars;
          if (set2^.vars-intersection = set1^.vars) and
             (set2^.notvars = set1^.notvars-intersection) and
             (set2^.vars <> intersection) then begin
            if tracing then begin
              Write('Apply A''.B + A.B = B to '); PrintS(Set2);
              Write(' + '); PrintS(Set1);
            end {tracing};
            set2^.vars := set2^.vars-intersection;
            set1^.vars := [];
            set1^.notvars := [];
            if tracing then begin
              Write(' = '); PrintS(Set2);
              WriteLn;
            end {tracing};
          end {if};
        end {if}
      end {if};
      expr2 := expr2^.next;
    end {while};
    expr1 := expr1^.next;
  end {while};
  RemoveDups(s);
  {----------------      Apply rule A + AB = A    --------------------}
  expr1 := s;
  while expr1 <> NIL do begin
    expr2 := expr1;
    set1 := expr1;
    while expr2 <> NIL do begin
      set2 := expr2;
      if (set1^.vars=[]) and (set1^.notvars=[]) then begin
      end else if (set2^.vars=[]) and (set2^.notvars=[]) then begin
      end else begin
        if not((set1^.vars=set2^.vars) and
               (set1^.notvars=set2^.notvars)) then begin
(* was set1<>set2 - may change back... *)
          if (set1^.vars*set2^.vars = set1^.vars) and
          (set1^.notvars*set2^.notvars = set1^.notvars) then begin
            if tracing then begin
              Write('Applyi A + AB = A to '); PrintS(set1);
              Write(' + (redundant) '); PrintS(set2); WriteLn;
            end {tracing};
            set2^.vars := [];
            set2^.notvars := [];
          end else if (set1^.vars*set2^.vars = set2^.vars) and
{ Anti-symmetric }
          (set1^.notvars*set2^.notvars = set2^.notvars)  then begin
            if tracing then begin
              Write('Apply A + AB = A to '); PrintS(set2);
              Write(' + (redundant) '); PrintS(set1); WriteLn;
            end {tracing};
            set1^.vars := [];
            set1^.notvars := [];
          end {if};
        end {if};
      end {if};
      expr2 := expr2^.next;
    end {while};
    expr1 := expr1^.next;
  end {while};
  RemoveDups(s);
  if RemoveComplements(s) then goto 99;
  {----------------      Apply rule A + A'B = A + B    --------------------}
  expr1 := s;
  while expr1 <> NIL do begin
    expr2 := expr1;
    set1 := expr1;
    while expr2 <> NIL do begin
      set2 := expr2;
      if set1 = set2 then begin
      end else if (set1^.vars=[]) and (set1^.notvars=[]) then begin
      end else if (set2^.vars=[]) and (set2^.notvars=[]) then begin
      end else begin
        if (set1^.vars<>set2^.vars) or (set1^.notvars<>set2^.notvars) then begin
(* Lines below should be identical to those above - unfortunately,
    we may be suffering from a compiler bug...
        if not((set1^.vars=set2^.vars) and
               (set1^.notvars=set2^.notvars)) then begin
*)
for v := va to vlast do begin
  if (v in set1^.vars) and (v in set2^.notvars) then begin
    if ((set1^.vars-[v])*set2^.vars = set1^.vars-[v]) and
       (set1^.notvars*set2^.notvars-[v]=set1^.notvars) then begin
            if tracing then begin
              Write('Apply) A + A''B = A + B to '); PrintS(set1);
              Write(' + '); PrintS(set2);
            end {tracing};
            set2^.notvars := set2^.notvars-[v];
            if tracing then begin
              Write(' = '); PrintS(set1);
              Write(' + '); PrintS(set2); WriteLn;
            end {tracing};
    end;
  end else if (v in set1^.notvars) and (v in set2^.vars) then begin
    if ((set1^.notvars-[v])*set2^.notvars = set1^.notvars-[v]) and
       (set1^.vars*set2^.vars-[v]=set1^.vars) then begin
            if tracing then begin
              Write('Apply A'' + AB = A'' + B to '); PrintS(set2);
              Write(' + '); PrintS(set1);
            end {tracing};
            set2^.vars := set2^.vars-[v];
            if tracing then begin
              Write(' = '); PrintS(set1);
              Write(' + '); PrintS(set2); WriteLn;
            end {tracing};
    end;
  end {if};
  if RemoveComplements(s) then goto 99;
{ Anti-symmetric }
  if (v in set2^.vars) and (v in set1^.notvars) then begin
    if ((set2^.vars-[v])*set1^.vars = set2^.vars-[v]) and
       (set2^.notvars*set1^.notvars-[v]=set2^.notvars) then begin
            if tracing then begin
              Write('Apply A + A''B = A + B to '); PrintS(set2);
              Write(' + '); PrintS(set1);
            end {tracing};
            set1^.notvars := set1^.notvars-[v];
            if tracing then begin
              Write(' = '); PrintS(set2);
              Write(' + '); PrintS(set1); WriteLn;
             end {tracing};
    end;
  end else if (v in set2^.notvars) and (v in set1^.vars) then begin
    if ((set2^.notvars-[v])*set1^.notvars = set2^.notvars-[v]) and
       (set2^.vars*set1^.vars-[v]=set2^.vars) then begin
            if tracing then begin
              Write('Apply A'' + AB = A'' + B to '); PrintS(set1);
              Write(' + '); PrintS(set1);
            end {tracing};
            set1^.vars := set1^.vars-[v];
            if tracing then begin
              Write(' = '); PrintS(set2);
              Write(' + '); PrintS(set1); WriteLn;
            end {tracing};
    end;
  end {if};
  if RemoveComplements(s) then goto 99;
end {for};
        end {if};
      end {if};
      expr2 := expr2^.next;
    end {while};
    expr1 := expr1^.next;
  end {while};
  RemoveDups(s);
  {------------------------    Cast out blanks    ------------------------}
  NEW(dummy); dummy^.vars := []; dummy^.notvars := []; dummy^.next := s;
  last := dummy;
  expr := s;
  while expr <> NIL do begin
    if (expr^.vars = []) and (expr^.notvars = []) then begin
(*      WriteLn('Removing ''+ false'' from expression');*)
      last^.next := expr^.next;
    end else begin
      last := expr;
    end {if};
    expr := last^.next;
  end {while};
  s := dummy^.next; DISPOSE(dummy);
  if tracing then begin
    Write('Ans: '); PrintSP(s); WriteLn;
  end {tracing};   
99:
  if s=NIL then s := AlwaysFalse;
end {Optimise};
procedure AndTerm(var prod: SP; term: SP);
var
  result, temp, temp2: SP;
begin
  if tracing then begin
    Write('Apply distributive law to ('); PrintSP(term); Write(') . (');
    PrintSP(prod); Write(') = ');
  end {tracing};
  (*      tricky!!! A few NEWs here. Also, do we dispose args? *)
  if term=AlwaysTrue then result := prod
  else if term=AlwaysFalse then result := AlwaysFalse
  else if prod=AlwaysTrue then result := term
  else if prod=AlwaysFalse then result := AlwaysFalse
  else begin
    result := NIL;
    if prod=NIL then WriteLn('Warning - you are anding the empty set!');
    while term <> NIL do begin
      temp := CopyOf(prod);
      {perform AND operation into temp}
      temp2 := temp;
      while temp2 <> NIL do begin
        temp2^.vars := temp2^.vars+term^.vars;
        temp2^.notvars := temp2^.notvars+term^.notvars;
        temp2 := temp2^.next;
      end {while};
      Concat(result, temp);
      term := term^.next;
    end {while};
  end {if};
  prod := result;
  if tracing then begin Write(' = '); PrintSP(result); WriteLn; end {tracing};
end {AndTerm};
function DeMorgan(prod: SP): SP;
var
  v: variables;
  single,result: SP;
begin
(*  Optimise(prod);*) { Assert no Q and Q' in same cell... }
  (* temp remove *)
(****** ARGH!!!! This is hellishly expensive. Should be simple test! *BUG**)
  if tracing then begin
    Write('Apply De-Morgan to ('); PrintS(prod); Write(')''');
  end {tracing};
  if prod=AlwaysTrue then result := AlwaysFalse
  else if prod=AlwaysFalse then result := AlwaysTrue
  else begin
    result := NIL;
    for v := va to vlast do begin
      if v in prod^.notvars then begin
        NEW(single);
        single^.vars := [v];
        single^.notvars := [];
        single^.next := result;
        result := single;
      end else if v in prod^.vars then begin
        NEW(single);
        single^.notvars := [v];
        single^.vars := [];
        single^.next := result;
        result := single;
      end {if};
    end {for};
  end {if};              
  DeMorgan := result;
  if tracing then begin Write(' = '); PrintSP(result); WriteLn; end {tracing};
end {DeMorgan};
procedure Complement(var s: SP);
var
  expr, sum, prod, result: SP;
begin
  if tracing then begin Write('Complement '); PrintSP(s); WriteLn; end {tracing};
  if s=AlwaysTrue then s := AlwaysFalse
  else if s=AlwaysFalse then s := AlwaysTrue else begin
    NEW(result);
    result^.vars := [];
    result^.notvars := [];
    result^.next := NIL;   { kludge - ought to drop off }
    expr := s;
    while expr <> NIL do begin
      prod := expr;
      sum := DeMorgan(prod);
      AndTerm(result, sum);
      Optimise(result); { Probably redundant ??? - Worried, Croydon. }
(*** TRY PUTTING THIS LINE IN - SEE IF IT HELPS ***BUG***)
      expr := expr^.next;
    end {while};
    s := result;
  end {if};
  if tracing then begin Write('Result of complementing is '); PrintSP(s); WriteLn; end {tracing};
end {Complement};
{--------------------------------------------------------------------------}
function GetCh: char;
var
  ch: char;
begin
  if eof then goto 9999
  else if hangfire then begin
    hangfire := false;
    ch := saved;
  end else if eoln then begin
    ch := ' ';
    Readln;
  end else Read(ch);
  GetCh := ch;
end {GetCh};
procedure BackSpace(ch: char);
begin
  hangfire := true;
  saved := ch;
end {backSpace};
function Parse(ch: char): boolean;
var
  c: char;
begin
  repeat c := GetCh until c <> ' ';
  if c=ch then Parse := true else begin
    Parse := false;
    BackSpace(c);
  end {if};
end {Parse};
function ParseTag: variables;
var
  c: char;
  ordinal: integer;
  v: variables;
begin
(*  if ((Parse('V')) or (Parse('v'))) then begin
         if Parse('0') then ParseTag := va
    else if Parse('1') then ParseTag := v1
    else if Parse('2') then ParseTag := v2
    else if Parse('3') then ParseTag := v3
    else if Parse('4') then ParseTag := v4
    else if Parse('5') then ParseTag := v5
    else if Parse('6') then ParseTag := v6;
  end [if]******;
*)
  repeat c := GetCh until c <> ' ';
  if c in ['a'..'z'] then begin
    ordinal := ORD(c)-ORD('a');
    for v := va to vlast do begin
      if ORD(v)=ordinal then ParseTag := v;
    end {for};
  end else begin
    BackSpace(c); goto 666;
  end {if};
  
end {ParseTag};
{--------------------------------------------------------------------------}
function ParseExpr: SP; forward;
function ParseTerm: SP;
var
  expr: SP; term: variables;
begin
  if Parse('(') then begin
    expr := ParseExpr;
    if Parse(')') then begin end else goto 666;
    while Parse('''') do begin
      Complement(expr);
    end {while};
  end else begin
    term := ParseTag;
    NEW(expr);
    expr^.next := NIL;
    if Parse('''') then begin
      expr^.notvars := [term];
      expr^.vars := [];
    end else begin
      expr^.vars := [term];
      expr^.notvars := [];
    end {if};
  end {if};
  ParseTerm := expr;
end {ParseTerm};
function ParseProd: SP;
label
  1;
var
  term, prod: SP;
begin
  prod := ParseTerm;
  while parse('.') do begin
    term := ParseTerm;
    if term=AlwaysFalse then begin
      {DISPOSESP(prod)}
      prod := AlwaysFalse;
      goto 1
    end;
    AndTerm(prod, term); { And the term with each element in prod }
  end {while};
1:ParseProd := prod;
end {ParseProd};
function ParseExpr{: SP};
label
  1;
var
  prod, result: SP;
begin
  result := NIL;
  repeat
    prod := ParseProd;
    if prod=AlwaysTrue then begin
      {DISPOSESP(result)}
      result := AlwaysTrue;
      goto 1
    end;
    if prod <> AlwaysFalse then Concat(result, prod);
  until not Parse('+');
1:Optimise(result);
  ParseExpr := result;
end {ParseExpr};
{--------------------------------------------------------------------------}
begin
{ WriteLn('Please enter a boolean expression using + and . and ''');
  Writeln('Variables are single lower-case letters, e.g.');
  WriteLn('Terminate the expression with a ''?''');
  WriteLn('Expr: (a+(b.c'').(b+c))''?');
} NEW(AlwaysTrue); NEW(AlwaysFalse);
  { The contents of these are NEVER looked at; only the pointers to them }
  hangfire := false;
  vlast := vz;
  goto 777;
666: WriteLn ('Error - line ignored'); ReadLn; hangfire := false;
777:
  tracing := false;
  where := 0;
  repeat
    Expr := ParseExpr;
    Optimise(Expr); {ARGH!!!!}
    PrintSP(Expr);
    if Parse('?') then begin
      WriteLn(' +'); BackSpace('?');
    end else begin
      WriteLn(' ;');
    end;
    S[where] := Expr;
    where := where+1;
{   Complement(expr);
    Optimise(Expr);
    Write('which is equivalent to ('); PrintSP(Expr); WriteLn(')''');
} until not Parse('?');
  if Parse(';') then goto 8888 else goto 666;
9999:
  WriteLn('end of file');
8888:
end {PlaMin}.
