Делаем Icon литературным

Некоторые программисты, использующие Haskell, знают, что есть такой его вариант как «литературный» (или «грамотный») Haskell. Чем же он отличается от обычного?

Да тем, что комментарии и код как бы меняются местами — большую значимость имеют сами комментарии, а код заключается в блоки, оформленные как многострочные или однострочные строчки с ремарками (т.е. комментариями).

Скорее всего, мое объяснение того, что называется «литературным» Haskell несколько коряво. Да это так! И поэтому как бы, я должен немного пояснить свои рассуждения.

В общем, один известный программист предложил когда-то концепцию так называемого «литературного программирования», в котором программный код дополняется подробным объяснением логики его работы на естественном языке. Для того, чтобы это было возможным, комментариям в исходном коде придается бóльшее значение, чем самому коду. Чтобы исходные коды не мешали нормальному восприятию комментариев, специальное оформление комментариев было удалено, а сам код подвергся заключению в специальные блоки, которые на этапе обработки извлекаются специальной программой и далее просто транслируются.

При чем тут Icon? Дело в том, что для Icon не существует «литературного» варианта, но его можно легко реализовать… Итак, код идеи:

global syms,lines,blocks,incl,cline

   procedure init()
   syms := 0
   lines := 0
   blocks := 0
   incl := 0
   cline := 0
   end

   procedure include(l)
   local i,j,f,acc
   acc := ""
   every i := !l do {
      f := open(i,"r")
      every j := !f do {
         acc ||:= j || "\n"
      }
      close(f)
   }
   return acc 
   end 

   procedure recognize(line)
   local lex,pline
   pline := ""
   lex := split(line," ")
   case lex[1] of {
      ">" : pline ||:= line[3:0]
      "" : cline := 1
      "" : {
            cline := 0
            blocks +:= 1
       }
      "$include" :  {
          pline := include(lex[2:0])
          incl +:= 1
       }
      default : {
        if cline = 1 then {
            pline := line
         }
      }
   }
   return pline
   end

   procedure split(line,dlms)
   local w
   /dlms := ' \t'
   w := []
   line ? repeat {
      tab(upto(~dlms))
      put(w,tab(many(~dlms))) | break
   }
   return w
   end

   procedure process(fin,fout)
   local ifile,ofile,line,pline
   ifile := open(fin, "r")
   ofile := open(fout, "cw")
   while line := !ifile do {
      pline := recognize(line)
      if pline ~== "" then {
         if pline ~== "end" then write(ofile,pline) else write(ofile,pline||"\n")
      }
      syms +:= *line
      lines +:= 1
   }
   close(ifile)
   close(ofile)
   end

   procedure stat()
   local s
   s := "Processed: " || syms || " symbol(s), " || lines || " line(s), " || blocks || " block(s), " || incl || " file inclusion(s)."
   return s 
   end

   procedure literate(ifile,ofile)
   init()
   process(ifile,ofile)
   write(stat())
   end

procedure main(argv)
if *argv ~= 2 then write("usage: literate infile outfile") else literate(argv[1],argv[2])
end

Как это работает: создаем файл-«затравку», в котором код размещаем либо после символа > (после которого идет пробел) или же помещаем его в блок <begin><end> (тэги размещаются на новой строке, после чего с новой строки идет код), скармливаем «затравку» этой программке и получаем нормальный код на Icon, правда без комментариев. Эта программка имеет два аргумента командной строки: имя входного файла и имя выходного файла.

Сама программа состоит из нескольких элементарных процедур: процедура-инициализатор (выставляет начальные значения количества обработанных символов, строк, блоков и количество включенных файлов, а также значение флага «начала-конца» блока (это переменная cline)), процедура — распознаватель элементов «литературного» Icon (опознает блоки, код и команду включения файлов), процедура разбивки строки по определенному разделителю (в нашем случае, это пробел) на список элементов (этот список используется внутри процедуры — распознавателя). Но и эти процедуры играют вспомогательную роль, основным же элементом программы являются процедуры process и stat.

Первая процедура проходит исходный файл, применяя к нему вспомогательные процедуры, и генерирует новый файл избавленный от всего лишнего. Вторая процедура выдает краткую статистику по количеству обработанных единиц входного файла. Финальная процедура literate служит контейнером для объединения всех упомянутых функциональных блоков в единую систему. Как видно, все это довольно просто.

В заключение я показываю пример «литературного кода» и расскажу про кое-какую интересную штучку, вмонтированную в этот простейший «препроцессор»:

 this is factorial example
 > procedure fact(x)
 > if x < 2 then return 1 else return x * fact(x - 1)

  iterative example
 
 > procedure factorial(x)
 > local p,i
 > p := 1
 > every i := 1 to x do {
 > p *= i
 > }
 > return p
 > end

Результирующий файл (как вы уже догадались) будет выглядеть так:

 procedure fact(x)
 if x < 2 then return 1 else return x * fact(x - 1)
 end

 procedure factorial(x)
 local p,i
 p := 1
 every i := 1 to x do {
      p *= i
 }
 return p
 end

Указав во входном файле директиву $include file.icn, можно на место этой директивы поместить весь код находящийся в файле file.icn (разумеется, можно включить несколько файлов, указав пути к ним через пробел). Однако, стоит помнить о том, что код для вставки не должен быть "литературным", поскольку не подвергается обработке этой программкой.

Добавить комментарий