Pull to refresh

Старые секреты быстрой отладки: анимация исходного кода

Reading time 7 min
Views 15K
Вечер пятницы часто оказывается вечером воспоминаний, и не только о прошедшей неделе, но и о гораздо более ранних событиях. В эту пятницу вспомнил об одной интересной программе для MS DOS (а также для Mac OS, UNIX и VAX/VMS) – Паскаль-интерпретаторе и IDE Dr. Pascal. Подробную информацию о возможностях и отзывы можно найти на сохраненном в архиве сайте изготовителя Visible Software (США), а я ограничусь только наиболее запомнившимися мне идеями, причем эти идеи, на мой взгляд, даже сегодня не утратили актуальности. Прежде всего вспоминается картинка:

image

Позже мы вернемся к этой картинке, а пока начнем с начала, т.е. с основного меню:

image

Как видим, меню достаточно обычное для DOS-программы. Загружаем файл широко известной задачки

«8 ферзей»:
program EightQueens(output);
{ Place 8 hostile queens on a chessboard, such that none can be captured. }
{ From Wirth:  Algorithms + Data Structures = Programs, page 146. }
  var
    i: integer;
    RowFree: array [1..8] of boolean;
    UpDiag: array [2..16] of boolean; { diagonal to upper right }
    DownDiag: array [-7..7] of boolean; { diagonal to lower right }
    QueenIn: array [1..8] of integer;


  procedure print;
  { Write out one solution }
    var
      k: integer;
    begin { print }
      for k := 1 to 8 do
        write(QueenIn[k]: 4);
      writeln;
    end { print };


  procedure try(col: integer);
  { Try to place a queen in this column }
    var
      row: integer;
    begin { try }
      for row := 1 to 8 do
        if RowFree[row] and UpDiag[col+row] and DownDiag[col-row] then
          begin
            QueenIn[col] := row;
            RowFree[row] := false;
            UpDiag[col+row] := false;
            DownDiag[col-row] := false;
            if col < 8
              then try(col+1)
              else print;
            RowFree[row] := true;
            UpDiag[col+row] := true;
            DownDiag[col-row] := true;
          end;
    end { try };

  begin { EightQueens }
    for i := 1 to 8 do
      RowFree[i] := true;
    for i := 2 to 16 do
      UpDiag[i] := true;
    for i := -7 to 7 do
      DownDiag[i] := true;
    try(1)
  end { EightQueens }.


и нажимаем F9 (Run). Исполнение программы отображается на экране:

image

Внизу результаты, выводимые программой (output), выше слева фрагмент кода исполняемой на данном шаге процедуры (или функции), где стрелочкой отмечен исполняемый оператор, справа – значения актуальных в данный момент переменных, выше – то же самое для вызвавшей процедуры. При этом переход с оператора на оператор происходит автоматически с заданной пользователем задержкой – такое «кино» останавливается по окончании загруженной в IDE программы (в нашем случае «8 ферзей») или по команде Freeze, которую можно отдать, нажав соответствующую функциональную клавишу. Если программа не кончилась, то дальше можно двигаться пошагово, как в других отладчиках, нажимая стрелку вниз, или вернуться в «кино», нажав F8 (Continue). Во второй строке сверху экрана отображается цепочка вызовов процедур. Особо стоит отметить, что упомянутые выше «актуальные в данный момент переменные» Доктор выбирает сам, пользователю нужно только загрузить программу и нажать Run. Судя по отзывам, такое предельно простое управление оказалось очень удобным для вводных студенческих курсов по основам программирования, для чего Dr. Pascal, собственно, и предназначен. Однако в инструкции пользователя отмечается, что и для продвинутого профессионального программиста быстрая возможность одним движением посмотреть, как работает небольшая программа, может оказаться полезной. Тут возникает интересный вопрос: а насколько небольшая?

Я взял Виртовский интерпретатор PascalS, написанный на Паскале. По сравнению с ферзями это программа, объемом около 2000 строк исходного кода, гораздо сложнее. Доктору она в изначальном виде оказалась не по силам, поэтому я разрезал ее на две части.

Первая часть подготавливает файлы данных:
{%F- no reformatting }
{%O+}
program Pas1 (input,output,paskey,pasksy,spsfile,enterf,symsetf{,textf});

const
   nkw = 27;     (*no. of key words*)
   alng =  10;   (*no. of significant chars in identifiers*)
type
   symbol = (intcon,realcon,charcon,string,
                  notsy,plus,minus,times,idiv,rdiv,imod,andsy,orsy,
                  eql,neq,gtr,geq,lss,leq,
                  lparent,rparent,lbrack,rbrack,comma,semicolon,period,
                  colon,becomes,constsy,typesy,varsy,functionsy,
                  proceduresy,arraysy,recordsy,programsy,ident,
                  beginsy,ifsy,casesy,repeatsy,whilesy,forsy,
                  endsy,elsesy,untilsy,ofsy,dosy,tosy,downtosy,thensy);
       alfa = packed array [1..alng] of char;
       object = (konstant,variable,type1,prozedure,funktion);
       types = (notyp,ints,reals,bools,chars,arrays,records);
  keytype = array [1..nkw] of alfa;
  ksytype = array [1..nkw] of symbol;
  spstype = array [char] of symbol;
  symset = set of symbol;
  entertype = record
               fx0: alfa; fx1: object;
               fx2: types; fx3: integer;
              end;

   var
       key: keytype;
       ksy: ksytype;
       sps: spstype; (*special symbols*)
       syset : symset;
       pasksy  : file of ksytype;
       paskey  : file of keytype;
       spsfile : file of spstype;
       enterf  : file of entertype;
       symsetf : file of symset;
{       textf   : text;}

procedure enter(x0: alfa; x1: object;
                x2: types; x3: integer);

var
  EnterRec : EnterType;

begin
  with EnterRec do
     begin  fx0 := x0; fx1 := x1;
        fx2 := x2;  fx3 := x3
     end;
  write ( enterf, EnterRec );
end (*enter*) ;

begin   {main program}
   key[ 1] := 'and       '; key[ 2] := 'array     ';
   key[ 3] := 'begin     '; key[ 4] := 'case      ';
   key[ 5] := 'const     '; key[ 6] := 'div       ';
   key[ 7] := 'do        '; key[ 8] := 'downto    ';
   key[ 9] := 'else      '; key[10] := 'end       ';
   key[11] := 'for       '; key[12] := 'function  ';
   key[13] := 'if        '; key[14] := 'mod       ';
   key[15] := 'not       '; key[16] := 'of        ';
   key[17] := 'or        '; key[18] := 'procedure ';
   key[19] := 'program   '; key[20] := 'record    ';
   key[21] := 'repeat    '; key[22] := 'then      ';
   key[23] := 'to        '; key[24] := 'type      ';
   key[25] := 'until     '; key[26] := 'var       ';
   key[27] := 'while     ';
   ksy[ 1] := andsy;         ksy[ 2] := arraysy;
   ksy[ 3] := beginsy;       ksy[ 4] := casesy;
   ksy[ 5] := constsy;       ksy[ 6] := idiv;
   ksy[ 7] := dosy;          ksy[ 8] := downtosy;
   ksy[ 9] := elsesy;        ksy[10] := endsy;
   ksy[11] := forsy;         ksy[12] := functionsy;
   ksy[13] := ifsy;          ksy[14] := imod;
   ksy[15] := notsy;         ksy[16] := ofsy;
   ksy[17] := orsy;          ksy[18] := proceduresy;
   ksy[19] := programsy;     ksy[20] := recordsy;
   ksy[21] := repeatsy;      ksy[22] := thensy;
   ksy[23] := tosy;          ksy[24] := typesy;
   ksy[25] := untilsy;       ksy[26] := varsy;
   ksy[27] := whilesy;
   rewrite (paskey);
   write (paskey, key);

   ksy[ 1] := andsy;         ksy[ 2] := arraysy;
   ksy[ 3] := beginsy;       ksy[ 4] := casesy;
   ksy[ 5] := constsy;       ksy[ 6] := idiv;
   ksy[ 7] := dosy;          ksy[ 8] := downtosy;
   ksy[ 9] := elsesy;        ksy[10] := endsy;
   ksy[11] := forsy;         ksy[12] := functionsy;
   ksy[13] := ifsy;          ksy[14] := imod;
   ksy[15] := notsy;         ksy[16] := ofsy;
   ksy[17] := orsy;          ksy[18] := proceduresy;
   ksy[19] := programsy;     ksy[20] := recordsy;
   ksy[21] := repeatsy;      ksy[22] := thensy;
   ksy[23] := tosy;          ksy[24] := typesy;
   ksy[25] := untilsy;       ksy[26] := varsy;
   ksy[27] := whilesy;
   rewrite (pasksy);
   write (pasksy, ksy);

   sps['+'] := plus;         sps['-'] := minus;
   sps['*'] := times;        sps['/'] := rdiv;
   sps['('] := lparent;      sps[')'] := rparent;
   sps['='] := eql;          sps[','] := comma;
   sps['['] := lbrack;       sps[']'] := rbrack;
   sps['#'] := neq;          sps['&'] := andsy;
   sps[';'] := semicolon;
   rewrite (spsfile);
   write (spsfile, sps);

  rewrite (enterf);
  enter('          ', variable, notyp, 0);  (*sentinel*)
  enter('false     ', konstant, bools, 0);
  enter('true      ', konstant, bools, 1);
  enter('real      ', type1, reals, 1);
  enter('char      ', type1, chars, 1);
  enter('boolean   ', type1, bools, 1);
  enter('integer   ', type1, ints , 1);
  enter('abs       ', funktion, reals,0);
  enter('sqr       ', funktion, reals,2);
  enter('odd       ', funktion, bools,4);
  enter('chr       ', funktion, chars,5);
  enter('ord       ', funktion, ints, 6);
  enter('succ      ', funktion, chars,7);
  enter('pred      ', funktion, chars,8);
  enter('round     ', funktion, ints, 9);
  enter('trunc     ', funktion, ints, 10);
  enter('sin       ', funktion, reals, 11);
  enter('cos       ', funktion, reals, 12);
  enter('exp       ', funktion, reals, 13);
  enter('ln        ', funktion, reals, 14);
  enter('sqrt      ', funktion, reals, 15);
  enter('arctan    ', funktion, reals, 16);
  enter('eof       ', funktion, bools, 17);
  enter('eoln      ', funktion, bools, 18);
  enter('read      ', prozedure, notyp, 1);
  enter('readln    ', prozedure, notyp, 2);
  enter('write     ', prozedure, notyp, 3);
  enter('writeln   ', prozedure, notyp, 4);
  enter('          ', prozedure, notyp, 0);

  rewrite (symsetf);
  syset := [plus,minus,intcon,realcon,charcon,ident];
  write ( symsetf, syset );
  syset := [ident,arraysy,recordsy];
  write ( symsetf, syset );
  syset := [constsy,typesy,varsy,proceduresy,functionsy,beginsy];
  write ( symsetf, syset );
  syset := [intcon,realcon,charcon,ident,lparent,notsy];
  write ( symsetf, syset );
  syset := [beginsy,ifsy,whilesy,repeatsy,forsy,casesy];
  write ( symsetf, syset );

end.


Тут нужно пояснить, что директивы Dr. Pascal заключаются в фигурные скобки комментария и начинаются с символа «%». Директива {%O+} включает упрощенное наименование файлов, при котором, например, внешний файл, определенный как

pasksy  : file of ksytype;

так и будет называться «pasksy». Как и любой внешний файл, его надо указать в заголовке программы:

program Pas1 (input,output,paskey,

В оставшейся части PascalS также указываем файлы данных:

{%D+}
{%F- no reformatting }
{%O+}
program Pascals(input,output,paskey,pasksy,spsfile,enterf,symsetf);{1.6.75}

Директива %D+ дает возможность программно остановить анимацию вызовом предопределенной процедуры Freeze.

Тело программы PascalS будет выглядеть следующим образом:

begin   {main program}
   assign (input,'QUEENS.PAS');
   reset  (input);
   init;
   block(blockbegsys+statbegsys, false, 1);
   finish;
99: end.

Где процедуры init и finish:
procedure init;
{%s-}

 var
  i : integer;
  EnterRec : EnterType;

begin
 writeln;
 reset (paskey);   read  (paskey, key);
 reset (pasksy);   read  (pasksy, ksy);
 reset (spsfile);  read  (spsfile, sps);
 reset (symsetf);
 read  (symsetf,constbegsys,typebegsys,blockbegsys,facbegsys,statbegsys);
 stantyps := [notyp,ints,reals,bools,chars];
  lc := 0; ll := 0; cc := 0; ch := ' ';
  errpos := 0; errs := []; insymbol;
  t := -1; a := 0; b := 1; sx := 0; c2 := 0;
  display[0] := 1;
 iflag := false; oflag := false;
 if sy <> programsy then freeze{3} else
  begin insymbol;
   if sy <> ident then freeze{2} else
    begin progname := id; insymbol;
     if sy <> lparent then freeze{9} else
      repeat insymbol;
 if sy <> ident then freeze{2} else
 begin if id = 'input     ' then iflag := true else
       if id = 'output    ' then oflag := true else freeze{0};
   insymbol;
 end
      until sy <> comma;
      if sy = rparent then insymbol else freeze{4};
      if not oflag then freeze{20};
    end
  end ;
  reset (enterf);
  while not eof (enterf) do
   begin
    read  (enterf,EnterRec );
    with EnterRec do
     enter (fx0,fx1,fx2,fx3);
   end;
  with btab[1] do
    begin last := t; lastpar := 1; psize := 0; vsize := 0
    end ;
end {init};

procedure finish;
{%s-}

 begin
  if sy <> period then freeze{22};
  {emit(31)};   {halt}
  if btab[2].vsize > stacksize then freeze{49};
 end {finish};


Директива %s- отключает анимацию и показ значений переменных внутри процедуры, в которой она указана.

Сделав указанные изменения, загрузил и выполнил первую часть (Pas1), а потом вторую. PascalS прочитал ферзей и приступил к их трансляции (см. картинку в начале). Следить за непрерывной анимацией такого большого кода, как PascalS, оказалось затруднительно, поэтому в ключевых точках вызвал freeze и пронумеровал вызовы в комментариях. Разобравшись в ситуации, продолжал анимацию командой Continue. Думаю, что в современных IDE современных языков подобные аниматоры были бы полезны.

Описанные здесь «игры» делал давно на 286 CPU под MS DOS 3.2, сейчас только запустил старые файлы, чтобы сделать картинки. В заключение вспомнил интересный факт о распространении Dr. Pascal. Базовая поставка состояла из Руководства пользователя – книжка примерно 200 страниц на хорошей плотной бумаге и дискета. Стоила $99.95US и позиционировалась как low cost software. Лицензии на десятки рабочих мест в университетах стоили гораздо дешевле в пересчете на 1 копию. Но кроме Штатов и, нпр., Австралии, Dr. Pascal был популярен и в Индии. Насколько мне известно, местной компании была продана лицензия на распространение в Индии, и эта компания сама печатала книжки (тоже на английском 1:1 с оригиналом) и писала дискеты. Книжки были на газетной бумаге со слепым текстом, но цена была в пересчете с рупий около $4US. Та же компания тиражировала и другие популярные в то время продукты, типа LOTUS 1-2-3, dBase-4, ChiWriter и т.д. примерно за ту же цену.
Tags:
Hubs:
+33
Comments 33
Comments Comments 33

Articles